diff --git a/web/public/locales/en/views/exports.json b/web/public/locales/en/views/exports.json index fd6136c63..e0ace7d24 100644 --- a/web/public/locales/en/views/exports.json +++ b/web/public/locales/en/views/exports.json @@ -79,6 +79,9 @@ "addExportDialog": { "title": "Add Export to {{caseName}}", "searchPlaceholder": "Search uncategorized exports", - "empty": "No uncategorized exports match this search." + "empty": "No uncategorized exports match this search.", + "addButton_one": "Add 1 Export", + "addButton_other": "Add {{count}} Exports", + "adding": "Adding..." } } diff --git a/web/src/pages/Exports.tsx b/web/src/pages/Exports.tsx index 539d93207..9c9980593 100644 --- a/web/src/pages/Exports.tsx +++ b/web/src/pages/Exports.tsx @@ -14,7 +14,12 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogTitle, +} from "@/components/ui/dialog"; import Heading from "@/components/ui/heading"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -1024,9 +1029,14 @@ function CaseAddExportDialog({ }: CaseAddExportDialogProps) { const { t } = useTranslation(["views/exports", "common"]); const [search, setSearch] = useState(""); + const [selectedIds, setSelectedIds] = useState([]); + const [isAdding, setIsAdding] = useState(false); + // Reset dialog state whenever the target case changes or the dialog reopens. useEffect(() => { setSearch(""); + setSelectedIds([]); + setIsAdding(false); }, [exportCase?.id]); const filteredExports = useMemo(() => { @@ -1043,51 +1053,73 @@ function CaseAddExportDialog({ ); }, [availableExports, search]); + const toggleSelection = useCallback((exportId: string) => { + setSelectedIds((previous) => + previous.includes(exportId) + ? previous.filter((id) => id !== exportId) + : [...previous, exportId], + ); + }, []); + + const handleAdd = useCallback(async () => { + if (!exportCase || selectedIds.length === 0 || isAdding) { + return; + } + + setIsAdding(true); + try { + await Promise.all( + selectedIds.map((id) => onAssign(id, exportCase.id)), + ); + onClose(); + } finally { + setIsAdding(false); + } + }, [exportCase, isAdding, onAssign, onClose, selectedIds]); + return ( !open && onClose()} > - + {t("addExportDialog.title", { caseName: exportCase?.name })} -
+
setSearch(event.target.value)} /> -
+
{filteredExports.length > 0 ? ( - filteredExports.map((exportItem) => ( -
-
-
- {exportItem.name} -
-
- {exportItem.camera.replaceAll("_", " ")} -
-
- -
- )) +
+
+ {exportItem.name} +
+
+ {exportItem.camera.replaceAll("_", " ")} +
+
+ + ); + }) ) : (
{t("addExportDialog.empty")} @@ -1095,6 +1127,23 @@ function CaseAddExportDialog({ )}
+ + + +
);