From 6da101148109fd742c8ef33f643851ca5799a49f Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:26:12 +0000 Subject: [PATCH 1/3] Work in progress --- src/routeTree.gen.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index f23bf19..bcbba1d 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -1314,3 +1314,12 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} From b821dd62561f7e25d6e69e271dcab0492b8b136e Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:27:04 +0000 Subject: [PATCH 2/3] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/cases/documents-tab.tsx | 159 ++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 1 deletion(-) diff --git a/src/components/cases/documents-tab.tsx b/src/components/cases/documents-tab.tsx index 1e105b7..c17aed4 100644 --- a/src/components/cases/documents-tab.tsx +++ b/src/components/cases/documents-tab.tsx @@ -18,6 +18,7 @@ import { ExternalLink, Pencil, Share2, + FolderInput, } from "lucide-react"; import { formatDate } from "@/lib/format"; import { toast } from "sonner"; @@ -29,6 +30,14 @@ import { } from "@/components/ui/dialog"; import { ShareDocumentDialog } from "./share-document-dialog"; import JSZip from "jszip"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Checkbox } from "@/components/ui/checkbox"; const MAX_BYTES = 150 * 1024 * 1024; @@ -46,6 +55,10 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) { const [previewLoading, setPreviewLoading] = useState(false); const [shareDoc, setShareDoc] = useState(null); const fileRef = useRef(null); + const [selectedFolders, setSelectedFolders] = useState>(new Set()); + const [selectedDocs, setSelectedDocs] = useState>(new Set()); + const [moveOpen, setMoveOpen] = useState(false); + const [moveDest, setMoveDest] = useState(""); const load = useCallback(async () => { const [{ data: ds, error: de }, { data: fs }] = await Promise.all([ @@ -90,6 +103,12 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) { useEffect(() => { load(); }, [load]); + // Clear selection when folder changes + useEffect(() => { + setSelectedFolders(new Set()); + setSelectedDocs(new Set()); + }, [path]); + // Visible items: subfolders directly under `path` and files whose folder === path const subfolders = folders .filter((f) => { @@ -340,6 +359,84 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) { load(); }; + const toggleFolderSel = (full: string) => { + setSelectedFolders((prev) => { + const next = new Set(prev); + if (next.has(full)) next.delete(full); else next.add(full); + return next; + }); + }; + const toggleDocSel = (id: string) => { + setSelectedDocs((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); else next.add(id); + return next; + }); + }; + + const selectionCount = selectedFolders.size + selectedDocs.size; + + // Destinations: Root + all folders that are NOT a selected folder or descendant of one + const moveDestinations = (() => { + const blocked = (p: string) => { + for (const sel of selectedFolders) { + if (p === sel || p.startsWith(`${sel}/`)) return true; + } + return false; + }; + return ["", ...folders.filter((f) => !blocked(f))].filter( + (dest) => dest !== path, // moving to current folder is a no-op + ); + })(); + + const performMove = async () => { + const dest = moveDest; // "" = root + // Move folders + for (const full of selectedFolders) { + const name = full.split("/").pop() || full; + const newFull = dest ? `${dest}/${name}` : name; + if (newFull === full) continue; + if (folders.includes(newFull)) { + toast.error(`A folder named "${name}" already exists at the destination`); + return; + } + // Update documents in this folder + descendants + const affectedDocs = docs.filter( + (d) => (d.folder || "") === full || (d.folder || "").startsWith(`${full}/`), + ); + for (const d of affectedDocs) { + const next = d.folder === full ? newFull : `${newFull}${d.folder.slice(full.length)}`; + const { error } = await supabase.from("documents").update({ folder: next }).eq("id", d.id); + if (error) { toast.error("Move failed", { description: error.message }); return; } + } + // Update folder rows + const affectedFolders = folders.filter((f) => f === full || f.startsWith(`${full}/`)); + for (const f of affectedFolders) { + const next = f === full ? newFull : `${newFull}${f.slice(full.length)}`; + const { error } = await supabase + .from("document_folders") + .update({ path: next }) + .eq("case_id", caseId) + .eq("path", f); + if (error) { + await supabase.from("document_folders").delete().eq("case_id", caseId).eq("path", f); + await ensureFolder(next); + } + } + } + // Move loose files + for (const id of selectedDocs) { + const { error } = await supabase.from("documents").update({ folder: dest }).eq("id", id); + if (error) { toast.error("Move failed", { description: error.message }); return; } + } + toast.success(`Moved ${selectionCount} item${selectionCount === 1 ? "" : "s"}`); + setMoveOpen(false); + setMoveDest(""); + setSelectedFolders(new Set()); + setSelectedDocs(new Set()); + load(); + }; + const crumbs = path ? path.split("/") : []; return ( @@ -372,6 +469,15 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) { + {selectionCount > 0 && ( + + )}