import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../ui/table"; import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../ui/dialog"; import { Label } from "../ui/label"; import { Switch } from "../ui/switch"; import { Badge } from "../ui/badge"; import { Plus, Edit, MoreHorizontal, X } from "lucide-react"; import { toast } from "sonner"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "../ui/pagination"; import { getLabelMultipleOptions, getLabelMultipleOption, createLabelMultipleOption, updateLabelMultipleOption, deleteLabelMultipleOption, } from "../../services/labelMultipleOptionService"; import type { LabelMultipleOptionDto, LabelMultipleOptionCreateInput, LabelMultipleOptionUpdateInput, } from "../../types/labelMultipleOption"; function toDisplay(v: string | null | undefined): string { const s = (v ?? "").trim(); return s ? s : "None"; } export function MultipleOptionsView() { const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [editingOption, setEditingOption] = useState(null); const [deletingOption, setDeletingOption] = useState(null); const [options, setOptions] = useState([]); const [loading, setLoading] = useState(false); const [total, setTotal] = useState(0); const [refreshSeq, setRefreshSeq] = useState(0); const [actionsOpenForId, setActionsOpenForId] = useState(null); const [keyword, setKeyword] = useState(""); const [stateFilter, setStateFilter] = useState("all"); const [pageIndex, setPageIndex] = useState(1); const [pageSize, setPageSize] = useState(10); const abortRef = useRef(null); const keywordTimerRef = useRef(null); const [debouncedKeyword, setDebouncedKeyword] = useState(""); useEffect(() => { if (keywordTimerRef.current) window.clearTimeout(keywordTimerRef.current); keywordTimerRef.current = window.setTimeout(() => setDebouncedKeyword(keyword.trim()), 300); return () => { if (keywordTimerRef.current) window.clearTimeout(keywordTimerRef.current); }; }, [keyword]); const totalPages = Math.max(1, Math.ceil(total / pageSize)); useEffect(() => { setPageIndex(1); }, [debouncedKeyword, stateFilter, pageSize]); useEffect(() => { const run = async () => { abortRef.current?.abort(); const ac = new AbortController(); abortRef.current = ac; setLoading(true); try { const skipCount = (pageIndex - 1) * pageSize; const res = await getLabelMultipleOptions( { skipCount, maxResultCount: pageSize, keyword: debouncedKeyword || undefined, state: stateFilter === "all" ? undefined : stateFilter === "true", }, ac.signal, ); setOptions(res.items ?? []); setTotal(res.totalCount ?? 0); } catch (e: any) { if (e?.name === "AbortError") return; toast.error("Failed to load multiple options.", { description: e?.message ? String(e.message) : "Please try again.", }); setOptions([]); setTotal(0); } finally { setLoading(false); } }; run(); return () => abortRef.current?.abort(); }, [debouncedKeyword, stateFilter, pageIndex, pageSize, refreshSeq]); const refreshList = () => setRefreshSeq((x) => x + 1); const openEdit = (opt: LabelMultipleOptionDto) => { setActionsOpenForId(null); setEditingOption(opt); setIsEditDialogOpen(true); }; const openDelete = (opt: LabelMultipleOptionDto) => { setActionsOpenForId(null); setDeletingOption(opt); setIsDeleteDialogOpen(true); }; return (
setKeyword(e.target.value)} style={{ height: 40, boxSizing: 'border-box' }} className="bg-white border border-gray-300 rounded-md w-40 shrink-0 placeholder:text-gray-500" />
Multiple Option Name Option Code Contents State Order Last Edited Actions {loading ? ( Loading... ) : options.length === 0 ? ( No results. ) : ( options.map((item) => ( {toDisplay(item.optionName)} {toDisplay(item.optionCode)} {item.optionValuesJson && item.optionValuesJson.length > 0 ? item.optionValuesJson.join("; ") : "None"} {item.state ? "Active" : "Inactive"} {item.orderNum ?? "None"} {item.creationTime ? new Date(item.creationTime).toLocaleString() : "None"} setActionsOpenForId(open ? item.id : null)} > )) )}
Showing {total === 0 ? 0 : (pageIndex - 1) * pageSize + 1}- {Math.min(pageIndex * pageSize, total)} of {total}
{ e.preventDefault(); setPageIndex((p) => Math.max(1, p - 1)); }} aria-disabled={pageIndex <= 1} className={pageIndex <= 1 ? "pointer-events-none opacity-50" : ""} /> e.preventDefault()} > Page {pageIndex} / {totalPages} { e.preventDefault(); setPageIndex((p) => Math.min(totalPages, p + 1)); }} aria-disabled={pageIndex >= totalPages} className={pageIndex >= totalPages ? "pointer-events-none opacity-50" : ""} />
{ setPageIndex(1); refreshList(); }} /> { setIsEditDialogOpen(open); if (!open) setEditingOption(null); }} onUpdated={refreshList} /> { setIsDeleteDialogOpen(open); if (!open) setDeletingOption(null); }} onDeleted={refreshList} />
); } function CreateMultipleOptionDialog({ open, onOpenChange, onCreated, }: { open: boolean; onOpenChange: (open: boolean) => void; onCreated: () => void; }) { const [submitting, setSubmitting] = useState(false); const [form, setForm] = useState({ optionCode: "", optionName: "", optionValuesJson: [], state: true, orderNum: null, }); const [newValue, setNewValue] = useState(""); const resetForm = () => { setForm({ optionCode: "", optionName: "", optionValuesJson: [], state: true, orderNum: null, }); setNewValue(""); }; useEffect(() => { if (!open) { resetForm(); } }, [open]); const addValue = () => { const trimmed = newValue.trim(); if (!trimmed) return; if (form.optionValuesJson.includes(trimmed)) { toast.error("Duplicate value", { description: "This value already exists.", }); return; } setForm((p) => ({ ...p, optionValuesJson: [...p.optionValuesJson, trimmed], })); setNewValue(""); }; const removeValue = (index: number) => { setForm((p) => ({ ...p, optionValuesJson: p.optionValuesJson.filter((_, i) => i !== index), })); }; const submit = async () => { if (!form.optionCode.trim() || !form.optionName.trim()) { toast.error("Validation failed", { description: "Option Code and Option Name are required.", }); return; } if (form.optionValuesJson.length === 0) { toast.error("Validation failed", { description: "At least one option value is required.", }); return; } setSubmitting(true); try { await createLabelMultipleOption(form); toast.success("Multiple option created.", { description: "The multiple option has been created successfully.", }); onOpenChange(false); onCreated(); } catch (e: any) { toast.error("Failed to create multiple option.", { description: e?.message ? String(e.message) : "Please try again.", }); } finally { setSubmitting(false); } }; return ( Add New Multiple Option Enter the details for the new multiple option.
setForm((p) => ({ ...p, optionCode: e.target.value }))} />
setForm((p) => ({ ...p, optionName: e.target.value }))} />
setNewValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addValue(); } }} />
{form.optionValuesJson.length > 0 && (
{form.optionValuesJson.map((val, idx) => ( {val} ))}
)}
setForm((p) => ({ ...p, orderNum: e.target.value ? Number(e.target.value) : null }))} />
Enabled
setForm((p) => ({ ...p, state: checked }))} />
); } function EditMultipleOptionDialog({ open, option, onOpenChange, onUpdated, }: { open: boolean; option: LabelMultipleOptionDto | null; onOpenChange: (open: boolean) => void; onUpdated: () => void; }) { const [submitting, setSubmitting] = useState(false); const [form, setForm] = useState({ optionCode: "", optionName: "", optionValuesJson: [], state: true, orderNum: null, }); const [newValue, setNewValue] = useState(""); useEffect(() => { if (open && option) { setForm({ optionCode: option.optionCode ?? "", optionName: option.optionName ?? "", optionValuesJson: option.optionValuesJson ?? [], state: option.state ?? true, orderNum: option.orderNum ?? null, }); setNewValue(""); } }, [open, option]); const addValue = () => { const trimmed = newValue.trim(); if (!trimmed) return; if (form.optionValuesJson.includes(trimmed)) { toast.error("Duplicate value", { description: "This value already exists.", }); return; } setForm((p) => ({ ...p, optionValuesJson: [...p.optionValuesJson, trimmed], })); setNewValue(""); }; const removeValue = (index: number) => { setForm((p) => ({ ...p, optionValuesJson: p.optionValuesJson.filter((_, i) => i !== index), })); }; const submit = async () => { if (!option?.id) return; if (!form.optionCode.trim() || !form.optionName.trim()) { toast.error("Validation failed", { description: "Option Code and Option Name are required.", }); return; } if (form.optionValuesJson.length === 0) { toast.error("Validation failed", { description: "At least one option value is required.", }); return; } setSubmitting(true); try { await updateLabelMultipleOption(option.id, form); toast.success("Multiple option updated.", { description: "The multiple option has been updated successfully.", }); onOpenChange(false); onUpdated(); } catch (e: any) { toast.error("Failed to update multiple option.", { description: e?.message ? String(e.message) : "Please try again.", }); } finally { setSubmitting(false); } }; return ( Edit Multiple Option Update the multiple option details.
setForm((p) => ({ ...p, optionCode: e.target.value }))} />
setForm((p) => ({ ...p, optionName: e.target.value }))} />
setNewValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addValue(); } }} />
{form.optionValuesJson.length > 0 && (
{form.optionValuesJson.map((val, idx) => ( {val} ))}
)}
setForm((p) => ({ ...p, orderNum: e.target.value ? Number(e.target.value) : null }))} />
Enabled
setForm((p) => ({ ...p, state: checked }))} />
); } function DeleteMultipleOptionDialog({ open, option, onOpenChange, onDeleted, }: { open: boolean; option: LabelMultipleOptionDto | null; onOpenChange: (open: boolean) => void; onDeleted: () => void; }) { const [submitting, setSubmitting] = useState(false); const name = useMemo(() => { const n = (option?.optionName ?? "").trim(); return n || option?.optionCode || "this option"; }, [option]); const submit = async () => { if (!option?.id) return; setSubmitting(true); try { await deleteLabelMultipleOption(option.id); toast.success("Multiple option deleted.", { description: "The multiple option has been removed successfully.", }); onOpenChange(false); onDeleted(); } catch (e: any) { toast.error("Failed to delete multiple option.", { description: e?.message ? String(e.message) : "Please try again.", }); } finally { setSubmitting(false); } }; return ( Delete Multiple Option This action cannot be undone.
Are you sure you want to delete {name}?
); }