Files
saas_frontend/src/application/theme/components/AllPalettes.tsx
T
2026-01-19 12:34:21 +05:30

203 lines
6.2 KiB
TypeScript

import React, { useEffect, useState, useMemo } from "react";
import { Plus, Edit2, Trash2 } from "lucide-react";
import { toast } from "react-toastify";
import {CustomButton} from "../../../components/custom";
import DataTable, { type ColumnDef } from "../../../components/custom/CustomTable";
import { CustomActionMenu, CustomActionItem, CustomStatus } from "../../../components/custom";
import { Loader } from "../../../components/custom/CustomLoader";
import { paletteApi } from "../PaletteApi";
import type { ColorPalette } from "../ThemeTypes";
import PaletteForm from "./PaletteForm";
import { useTheme } from "../../../context/ThemeContext";
const AllPalettes: React.FC = () => {
const [palettes, setPalettes] = useState<ColorPalette[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingPalette, setEditingPalette] = useState<ColorPalette | null>(null);
const { refreshTheme } = useTheme();
const fetchPalettes = async () => {
setIsLoading(true);
setErrorMessage("");
try {
const data = await paletteApi.getAllPalettes();
setPalettes(data);
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to fetch palettes";
setErrorMessage(message);
console.error("Failed to fetch palettes", error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchPalettes();
}, []);
const handleCreate = () => {
setEditingPalette(null);
setIsModalOpen(true);
};
const handleEdit = (palette: ColorPalette) => {
setEditingPalette(palette);
setIsModalOpen(true);
};
const handleDelete = async (id: string) => {
if (!window.confirm("Are you sure you want to delete this palette?")) return;
try {
await paletteApi.deletePalette(id);
toast.success("Palette deleted successfully");
fetchPalettes();
} catch (error) {
// toast handled by api client
}
};
const handleSubmit = async (data: any) => {
setIsLoading(true);
try {
if (editingPalette) {
await paletteApi.updatePalette(editingPalette.id, data);
toast.success("Palette updated successfully");
} else {
await paletteApi.createPalette(data);
toast.success("Palette created successfully");
}
setIsModalOpen(false);
fetchPalettes();
// Optionally refresh theme if the updated palette was the active one
refreshTheme();
} finally {
setIsLoading(false);
}
};
const columns = useMemo<ColumnDef<ColorPalette>[]>(
() => [
{
key: "name",
header: "Name",
searchable: true,
render: (row) => (
<div>
<div className="font-medium text-(--text-primary)">{row.name}</div>
{row.description && (
<div className="text-xs text-(--text-secondary)">{row.description}</div>
)}
</div>
),
},
{
key: "is_default",
header: "Default",
render: (row) => (
row.is_default ? (
<CustomStatus status="Default" variant="success" />
) : null
),
},
{
key: "colors",
header: "Preview",
render: (row) => (
<div className="flex items-center gap-1">
<div
className="w-6 h-6 rounded-full border border-(--card-border) shadow-sm"
style={{ backgroundColor: row.colors.primary }}
title="Primary"
/>
<div
className="w-6 h-6 rounded-full border border-(--card-border) shadow-sm"
style={{ backgroundColor: row.colors.sidebar_bg }}
title="Sidebar"
/>
<div
className="w-6 h-6 rounded-full border border-(--card-border) shadow-sm"
style={{ backgroundColor: row.colors.header_bg }}
title="Header"
/>
</div>
),
},
{
key: "actions",
header: "Actions",
render: (row) => (
<CustomActionMenu>
<CustomActionItem onClick={() => handleEdit(row)}>
<Edit2 size={16} className="mr-2" /> Edit Palette
</CustomActionItem>
<CustomActionItem
onClick={() => handleDelete(row.id)}
className={`${row.is_default ? "opacity-50 cursor-not-allowed" : "text-red-600 hover:text-red-700 hover:bg-red-50"}`}
>
<div
className="flex items-center w-full"
onClick={(e) => {
if (row.is_default) {
e.stopPropagation();
return;
}
}}
>
<Trash2 size={16} className="mr-2" /> Delete Palette
</div>
</CustomActionItem>
</CustomActionMenu>
),
},
],
[]
);
return (
<div className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold text-(--text-primary)">Theme Palettes</h1>
{/* <p className="text-sm text-(--text-secondary) mt-1">
Manage system color themes and default palettes.
</p> */}
</div>
<CustomButton onClick={handleCreate} leftIcon={<Plus size={18} />}>
Create Palette
</CustomButton>
</div>
{isLoading ? (
<div className="rounded-lg border border-[var(--card-border)] bg-[var(--card-bg)] p-6">
<Loader />
</div>
) : errorMessage ? (
<div className="rounded-lg border border-red-200 bg-red-50 p-6 text-sm text-red-600">
{errorMessage}
</div>
) : (
<DataTable
data={palettes}
columns={columns}
enableSearchDropdown={true}
search="name"
/>
)}
<PaletteForm
isOpen={isModalOpen}
initialData={editingPalette}
onSubmit={handleSubmit}
onCancel={() => setIsModalOpen(false)}
isLoading={isLoading}
/>
</div>
);
};
export default AllPalettes;