import { useEffect, useMemo, useState, type ReactElement } from "react"; // import { useNavigate } from "react-router-dom"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { Layout } from "@/components/layout/Layout"; import { DataTable, FormField, FormSelect, FormTextArea, PrimaryButton, Modal, ActionDropdown, DeleteConfirmationModal, type Column, } from "@/components/shared"; import { documentService } from "@/services/document-service"; import type { DocumentCategory } from "@/types/document"; import { showToast } from "@/utils/toast"; import { Plus, Eye, Edit, Trash2 } from "lucide-react"; import { cn } from "@/lib/utils"; const categorySchema = z.object({ name: z.string().min(1, "Category name is required"), code: z .string() .min(1, "Code is required") .max(10, "Code must be 10 characters or less"), description: z.string().optional(), reviewFrequency: z.string().min(1, "Review frequency is required"), retentionYears: z.string().min(1, "Retention years is required"), requiresTraining: z.boolean().optional(), parentId: z.string().optional(), }); type CategoryFormData = z.infer; const DocumentCategories = (): ReactElement => { // const navigate = useNavigate(); const [categories, setCategories] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [editingCategory, setEditingCategory] = useState(null); const [viewingCategory, setViewingCategory] = useState(null); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [categoryToDelete, setCategoryToDelete] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [error, setError] = useState(null); const { register, handleSubmit, control, reset, setValue, formState: { errors }, } = useForm({ resolver: zodResolver(categorySchema), defaultValues: { name: "", code: "", description: "", reviewFrequency: "12", retentionYears: "7", requiresTraining: false, parentId: "", }, }); const loadCategories = async (): Promise => { try { setIsLoading(true); const response = await documentService.getCategories(); setCategories(response.data || []); } catch (err: any) { setError( err?.response?.data?.error?.message || "Failed to load categories", ); } finally { setIsLoading(false); } }; useEffect(() => { void loadCategories(); }, []); const handleEdit = (category: DocumentCategory) => { setEditingCategory(category); setValue("name", category.name); setValue("code", category.code); setValue("description", category.description || ""); setValue( "reviewFrequency", category.review_frequency_months?.toString() || "12", ); setValue("retentionYears", category.retention_years?.toString() || "7"); setValue("requiresTraining", !!category.requires_training); setValue("parentId", category.parent_id || ""); setIsModalOpen(true); }; const handleView = (category: DocumentCategory) => { setViewingCategory(category); setIsViewModalOpen(true); }; const handleDeleteClick = (category: DocumentCategory) => { setCategoryToDelete(category); setIsDeleteModalOpen(true); }; const handleConfirmDelete = async () => { if (!categoryToDelete) return; try { setIsDeleting(true); await documentService.deleteCategory(categoryToDelete.id); showToast.success("Category deleted successfully"); setIsDeleteModalOpen(false); setCategoryToDelete(null); await loadCategories(); } catch (err: any) { showToast.error( err?.response?.data?.error?.message || "Failed to delete category", ); } finally { setIsDeleting(false); } }; const columns: Column[] = useMemo( () => [ { key: "name", label: "Name", render: (cat) => ( {cat.name} ), }, { key: "code", label: "Code", render: (cat) => ( {cat.code} ), }, { key: "review_frequency_months", label: "Review Frequency", render: (category) => category.review_frequency_months ? `${category.review_frequency_months} months` : "-", }, { key: "parent_id", label: "Parent Category", render: (category) => { const parent = categories.find((c) => c.id === category.parent_id); return ( {parent ? parent.name : "-"} ); }, }, { key: "retention_years", label: "Retention", render: (category) => category.retention_years ? `${category.retention_years} years` : "-", }, { key: "requires_training", label: "Requires Training", render: (category) => (
), }, { key: "description", label: "Description", render: (category) => ( {category.description || "-"} ), }, { key: "actions", label: "Actions", align: "right", render: (category) => ( handleView(category), icon: , }, { label: "Edit Category", onClick: () => handleEdit(category), icon: , }, { label: "Delete", onClick: () => handleDeleteClick(category), icon: , variant: "danger", }, ]} /> ), }, ], [categories], ); const onFormSubmit = async (data: CategoryFormData): Promise => { try { setIsSubmitting(true); const payload = { name: data.name.trim(), code: data.code.trim().toUpperCase(), description: data.description?.trim() || undefined, review_frequency_months: parseInt(data.reviewFrequency), retention_years: parseInt(data.retentionYears), requires_training: data.requiresTraining, parent_id: data.parentId || null, }; if (editingCategory) { await documentService.updateCategory(editingCategory.id, payload); showToast.success("Category updated"); } else { await documentService.createCategory(payload); showToast.success("Category created"); } setIsModalOpen(false); reset(); setEditingCategory(null); await loadCategories(); } catch (err: any) { showToast.error( err?.response?.data?.error?.message || "Failed to process category", ); } finally { setIsSubmitting(false); } }; return ( { setEditingCategory(null); reset(); setIsModalOpen(true); }} > Create Category ), }} >
category.id} emptyMessage="No categories found" isLoading={isLoading} error={error} />
{ setIsModalOpen(false); setEditingCategory(null); }} title={ editingCategory ? "Update Document Category" : "Create Document Category" } maxWidth="lg" >

Add a document category with review, retention, and training requirements.

( )} /> ( )} />
( !editingCategory || c.id !== editingCategory.id, ) .map((c) => ({ value: c.id, label: c.name })), ]} placeholder="Select parent category" /> )} />

Users must acknowledge documents in this category

(
field.onChange(!field.value)} >
)} />
{isSubmitting ? "Processing..." : editingCategory ? "Update Category" : "Create Category"}
{/* View Modal */} { setIsViewModalOpen(false); setViewingCategory(null); }} title="Document Category Details" maxWidth="lg" >

{viewingCategory?.name}

{viewingCategory?.code}

{viewingCategory?.review_frequency_months} months

{viewingCategory?.retention_years} years

{categories.find((c) => c.id === viewingCategory?.parent_id) ?.name || "None (Root Category)"}

{viewingCategory?.description || "No description provided."}

Requires Training

Training acknowledgement is{" "} {viewingCategory?.requires_training ? "enabled" : "disabled"}{" "} for this category.

{ setIsDeleteModalOpen(false); setCategoryToDelete(null); }} onConfirm={handleConfirmDelete} title="Delete Document Category" message="Are you sure you want to delete this category? This action cannot be undone and will fail if the category is currently associated with documents." itemName={categoryToDelete?.name || ""} isLoading={isDeleting} /> ); }; export default DocumentCategories;