203 lines
6.2 KiB
TypeScript
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;
|
|
|