import { baseUrl } from "@/api/baseUrl"; import { CaseCard, ExportCard } from "@/components/card/ExportCard"; import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import Heading from "@/components/ui/heading"; import { Input } from "@/components/ui/input"; import { Toaster } from "@/components/ui/sonner"; import useKeyboardListener from "@/hooks/use-keyboard-listener"; import { useOverlayState, useSearchEffect } from "@/hooks/use-overlay-state"; import { cn } from "@/lib/utils"; import { DeleteClipType, Export, ExportCase } from "@/types/export"; import OptionAndInputDialog from "@/components/overlay/dialog/OptionAndInputDialog"; import axios from "axios"; import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { isMobile } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { LuFolderX } from "react-icons/lu"; import { toast } from "sonner"; import useSWR from "swr"; function Exports() { const { t } = useTranslation(["views/exports"]); useEffect(() => { document.title = t("documentTitle"); }, [t]); // Data const { data: cases, mutate: updateCases } = useSWR("cases"); const { data: rawExports, mutate: updateExports } = useSWR("exports"); const exports = useMemo( () => (rawExports ?? []).filter((e) => !e.export_case), [rawExports], ); const mutate = useCallback(() => { updateExports(); updateCases(); }, [updateExports, updateCases]); // Search const [search, setSearch] = useState(""); // Viewing const [selected, setSelected] = useState(); const [selectedCaseId, setSelectedCaseId] = useOverlayState< string | undefined >("caseId", undefined); const [selectedAspect, setSelectedAspect] = useState(0.0); useSearchEffect("id", (id) => { if (!exports) { return false; } setSelected(exports.find((exp) => exp.id == id)); return true; }); useSearchEffect("caseId", (caseId: string) => { if (!cases) { return false; } const exists = cases.some((c) => c.id === caseId); if (!exists) { return false; } setSelectedCaseId(caseId); return true; }); // Modifying const [deleteClip, setDeleteClip] = useState(); const [exportToAssign, setExportToAssign] = useState(); const onHandleDelete = useCallback(() => { if (!deleteClip) { return; } axios.delete(`export/${deleteClip.file}`).then((response) => { if (response.status == 200) { setDeleteClip(undefined); mutate(); } }); }, [deleteClip, mutate]); const onHandleRename = useCallback( (id: string, update: string) => { axios .patch(`export/${id}/rename`, { name: update, }) .then((response) => { if (response.status === 200) { setDeleteClip(undefined); mutate(); } }) .catch((error) => { const errorMessage = error.response?.data?.message || error.response?.data?.detail || "Unknown error"; toast.error(t("toast.error.renameExportFailed", { errorMessage }), { position: "top-center", }); }); }, [mutate, setDeleteClip, t], ); // Keyboard Listener const contentRef = useRef(null); useKeyboardListener([], undefined, contentRef); const selectedCase = useMemo( () => cases?.find((c) => c.id === selectedCaseId), [cases, selectedCaseId], ); const resetCaseDialog = useCallback(() => { setExportToAssign(undefined); }, []); return (
setDeleteClip(undefined)} > {t("deleteExport")} {t("deleteExport.desc", { exportName: deleteClip?.exportName })} {t("button.cancel", { ns: "common" })} { if (!open) { setSelected(undefined); } }} > {selected?.name?.replaceAll("_", " ")} {(exports?.length || cases?.length) && (
setSearch(e.target.value)} />
)} {selectedCase ? ( ) : ( )}
); } type AllExportsViewProps = { contentRef: MutableRefObject; search: string; cases?: ExportCase[]; exports: Export[]; setSelectedCaseId: (id: string) => void; setSelected: (e: Export) => void; renameClip: (id: string, update: string) => void; setDeleteClip: (d: DeleteClipType | undefined) => void; onAssignToCase: (e: Export) => void; }; function AllExportsView({ contentRef, search, cases, exports, setSelectedCaseId, setSelected, renameClip, setDeleteClip, onAssignToCase, }: AllExportsViewProps) { const { t } = useTranslation(["views/exports"]); // Filter const filteredCases = useMemo(() => { if (!search || !cases) { return cases || []; } return cases.filter( (caseItem) => caseItem.name.toLowerCase().includes(search.toLowerCase()) || (caseItem.description && caseItem.description.toLowerCase().includes(search.toLowerCase())), ); }, [search, cases]); const filteredExports = useMemo(() => { if (!search) { return exports; } return exports.filter((exp) => exp.name .toLowerCase() .replaceAll("_", " ") .includes(search.toLowerCase()), ); }, [exports, search]); return (
{filteredCases?.length || filteredExports.length ? (
{filteredCases.length > 0 && (
{t("headings.cases")}
{cases?.map((item) => ( { setSelectedCaseId(item.id); }} /> ))}
)} {filteredExports.length > 0 && (
{t("headings.uncategorizedExports")}
{exports.map((item) => ( setDeleteClip({ file, exportName }) } onAssignToCase={onAssignToCase} /> ))}
)}
) : (
{t("noExports")}
)}
); } type CaseViewProps = { contentRef: MutableRefObject; selectedCase: ExportCase; exports?: Export[]; search: string; setSelected: (e: Export) => void; renameClip: (id: string, update: string) => void; setDeleteClip: (d: DeleteClipType | undefined) => void; onAssignToCase: (e: Export) => void; }; function CaseView({ contentRef, selectedCase, exports, search, setSelected, renameClip, setDeleteClip, onAssignToCase, }: CaseViewProps) { const filteredExports = useMemo(() => { const caseExports = (exports || []).filter( (e) => e.export_case == selectedCase.id, ); if (!search) { return caseExports; } return caseExports.filter((exp) => exp.name .toLowerCase() .replaceAll("_", " ") .includes(search.toLowerCase()), ); }, [selectedCase, exports, search]); return (
{selectedCase.name}
{selectedCase.description}
{exports?.map((item) => ( setDeleteClip({ file, exportName }) } onAssignToCase={onAssignToCase} /> ))}
); } type CaseAssignmentDialogProps = { exportToAssign?: Export; cases?: ExportCase[]; selectedCaseId?: string; onClose: () => void; mutate: () => void; }; function CaseAssignmentDialog({ exportToAssign, cases, selectedCaseId, onClose, mutate, }: CaseAssignmentDialogProps) { const { t } = useTranslation(["views/exports"]); const caseOptions = useMemo( () => [ ...(cases ?? []) .map((c) => ({ value: c.id, label: c.name, })) .sort((cA, cB) => cA.label.localeCompare(cB.label)), { value: "new", label: t("caseDialog.newCaseOption"), }, ], [cases, t], ); const handleSave = useCallback( async (caseId: string) => { if (!exportToAssign) return; try { await axios.patch(`export/${exportToAssign.id}/case`, { export_case_id: caseId, }); mutate(); onClose(); } catch (error: unknown) { const apiError = error as { response?: { data?: { message?: string; detail?: string } }; }; const errorMessage = apiError.response?.data?.message || apiError.response?.data?.detail || "Unknown error"; toast.error(t("toast.error.assignCaseFailed", { errorMessage }), { position: "top-center", }); } }, [exportToAssign, mutate, onClose, t], ); const handleCreateNew = useCallback( async (name: string, description: string) => { if (!exportToAssign) return; try { const createResp = await axios.post("cases", { name, description, }); const newCaseId: string | undefined = createResp.data?.id; if (newCaseId) { await axios.patch(`export/${exportToAssign.id}/case`, { export_case_id: newCaseId, }); } mutate(); onClose(); } catch (error: unknown) { const apiError = error as { response?: { data?: { message?: string; detail?: string } }; }; const errorMessage = apiError.response?.data?.message || apiError.response?.data?.detail || "Unknown error"; toast.error(t("toast.error.assignCaseFailed", { errorMessage }), { position: "top-center", }); } }, [exportToAssign, mutate, onClose, t], ); if (!exportToAssign) { return null; } return ( { if (!open) { onClose(); } }} options={caseOptions} nameLabel={t("caseDialog.nameLabel")} descriptionLabel={t("caseDialog.descriptionLabel")} initialValue={selectedCaseId} newValueKey="new" onSave={handleSave} onCreateNew={handleCreateNew} /> ); } export default Exports;