diff --git a/bun.lockb b/bun.lockb index 07adc49..90d54de 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 465d66c..a334d6f 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "input-otp": "^1.4.2", "jspdf": "^4.2.1", "jspdf-autotable": "^5.0.7", + "jszip": "^3.10.1", "lucide-react": "^0.575.0", "mailparser": "3.7.1", "papaparse": "^5.5.3", diff --git a/src/components/cases/documents-tab.tsx b/src/components/cases/documents-tab.tsx index 97e0025..1e105b7 100644 --- a/src/components/cases/documents-tab.tsx +++ b/src/components/cases/documents-tab.tsx @@ -28,6 +28,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { ShareDocumentDialog } from "./share-document-dialog"; +import JSZip from "jszip"; const MAX_BYTES = 150 * 1024 * 1024; @@ -135,6 +136,37 @@ export function CaseDocumentsTab({ caseId }: { caseId: string }) { const list = Array.from(files); let ok = 0; for (const f of list) { + // Auto-expand ZIP files into folders/files + if (/\.zip$/i.test(f.name) || f.type === "application/zip" || f.type === "application/x-zip-compressed") { + try { + const zip = await JSZip.loadAsync(await f.arrayBuffer()); + const zipRootName = sanitizeName(f.name.replace(/\.zip$/i, "")); + const zipRoot = baseFolder ? `${baseFolder}/${zipRootName}` : zipRootName; + await ensureFolder(zipRoot); + const entries = Object.values(zip.files); + for (const entry of entries) { + // Skip macOS metadata noise + if (/(^|\/)__MACOSX\//.test(entry.name) || /(^|\/)\.DS_Store$/.test(entry.name)) continue; + const parts = entry.name.split("/").filter(Boolean); + if (entry.dir) { + const sub = parts.map(sanitizeName).join("/"); + if (sub) await ensureFolder(`${zipRoot}/${sub}`); + continue; + } + const fname = parts.pop() || "file"; + const sub = parts.map(sanitizeName).join("/"); + const folderForFile = sub ? `${zipRoot}/${sub}` : zipRoot; + if (sub) await ensureFolder(folderForFile); + const blob = await entry.async("blob"); + const file = new File([blob], fname, { type: blob.type || "application/octet-stream" }); + const success = await uploadFile(file, folderForFile); + if (success) ok++; + } + } catch (err: any) { + toast.error(`${f.name}: could not unzip`, { description: err?.message }); + } + continue; + } // webkitRelativePath supports folder uploads when input has webkitdirectory const rel: string = (f as File & { webkitRelativePath?: string }).webkitRelativePath || ""; let folderForFile = baseFolder; diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index bcbba1d..f23bf19 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -1314,12 +1314,3 @@ 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> - } -}