diff --git a/apps/Frontend/src/components/claims/claim-document-upload-modal.tsx b/apps/Frontend/src/components/claims/claim-document-upload-modal.tsx index 2bb10d8..89c3c31 100644 --- a/apps/Frontend/src/components/claims/claim-document-upload-modal.tsx +++ b/apps/Frontend/src/components/claims/claim-document-upload-modal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react"; +import React, { useCallback, useRef, useState } from "react"; import { Card, CardContent, @@ -9,7 +9,10 @@ import { import { Button } from "@/components/ui/button"; import { RefreshCw, FilePlus } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; -import { MultipleFileUploadZone } from "../file-upload/multiple-file-upload-zone"; +import { + MultipleFileUploadZone, + MultipleFileUploadZoneHandle, +} from "../file-upload/multiple-file-upload-zone"; export default function ClaimDocumentsUploadMultiple() { const { toast } = useToast(); @@ -22,14 +25,15 @@ export default function ClaimDocumentsUploadMultiple() { const DESCRIPTION = "You can upload up to 10 files. Allowed types: PDF, JPG, PNG, WEBP."; - // Internal state - const [uploadedFiles, setUploadedFiles] = useState([]); + // Zone ref + minimal UI state (parent does not own files) + const uploadZoneRef = useRef(null); + const [filesForUI, setFilesForUI] = useState([]); const [isUploading, setIsUploading] = useState(false); // forwarded to upload zone const [isExtracting, setIsExtracting] = useState(false); - // Called by MultipleFileUploadZone whenever its internal list changes. - const handleFileUpload = useCallback((files: File[]) => { - setUploadedFiles(files); + // Called by MultipleFileUploadZone when its internal list changes (UI-only) + const handleZoneFilesChange = useCallback((files: File[]) => { + setFilesForUI(files); }, []); // Dummy save (simulate async). Replace with real API call when needed. @@ -42,9 +46,11 @@ export default function ClaimDocumentsUploadMultiple() { ); }, []); - // Extract handler — calls handleSave (dummy) and shows toasts. + // Extract handler — reads files from the zone via ref and calls handleSave const handleExtract = useCallback(async () => { - if (uploadedFiles.length === 0) { + const files = uploadZoneRef.current?.getFiles() ?? []; + + if (files.length === 0) { toast({ title: "No files", description: "Please upload at least one file before extracting.", @@ -57,17 +63,15 @@ export default function ClaimDocumentsUploadMultiple() { setIsExtracting(true); try { - await handleSave(uploadedFiles); + await handleSave(files); toast({ title: "Extraction started", - description: `Processing ${uploadedFiles.length} file(s).`, + description: `Processing ${files.length} file(s).`, variant: "default", }); - // If you want to clear the upload zone after extraction, you'll need a small - // change in MultipleFileUploadZone to accept a reset signal from parent. - // We intentionally leave files intact here. + // we intentionally leave files intact in the zone after extraction } catch (err) { toast({ title: "Extraction failed", @@ -80,7 +84,7 @@ export default function ClaimDocumentsUploadMultiple() { } finally { setIsExtracting(false); } - }, [uploadedFiles, handleSave, isExtracting, toast]); + }, [handleSave, isExtracting, toast]); return (
@@ -93,20 +97,21 @@ export default function ClaimDocumentsUploadMultiple() { {/* File Upload Section */}
{/* Show list of files received from the upload zone */} - {uploadedFiles.length > 0 && ( + {filesForUI.length > 0 && (

- Uploaded ({uploadedFiles.length}/{MAX_FILES}) + Uploaded ({filesForUI.length}/{MAX_FILES})

    - {uploadedFiles.map((file, index) => ( + {filesForUI.map((file, index) => (
  • {file.name}
  • @@ -120,7 +125,7 @@ export default function ClaimDocumentsUploadMultiple() {
    - - - ))} -
-
- ) : ( -
- -
+ ) : uploadedFiles.length > 0 ? ( +

- Drag and drop PDF or Image files here -

-

- Or click to browse files + {uploadedFiles.length} file(s) uploaded

+
    + {uploadedFiles.map((file, index) => ( +
  • + {file.name} + +
  • + ))} +
- -

- Allowed types: PDF, JPG, PNG — Max {maxFiles} files, 5MB each -

-
- )} + ) : ( +
+ +
+

{uploadTitle}

+

+ Or click to browse files +

+
+ +
+ )} +
-
- ); -} + ); + } +); + +MultipleFileUploadZone.displayName = "MultipleFileUploadZone"; diff --git a/apps/Frontend/src/components/payments/payment-ocr-block.tsx b/apps/Frontend/src/components/payments/payment-ocr-block.tsx index bf387f8..33aa8cf 100644 --- a/apps/Frontend/src/components/payments/payment-ocr-block.tsx +++ b/apps/Frontend/src/components/payments/payment-ocr-block.tsx @@ -1,9 +1,15 @@ // PaymentOCRBlock.tsx import * as React from "react"; -import { Card, CardContent } from "@/components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Upload, Image as ImageIcon, X, Plus } from "lucide-react"; +import { Image as ImageIcon, X, Plus } from "lucide-react"; import { useMutation } from "@tanstack/react-query"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { toast } from "@/hooks/use-toast"; @@ -16,16 +22,27 @@ import { } from "@tanstack/react-table"; import { QK_PAYMENTS_RECENT_BASE } from "@/components/payments/payments-recent-table"; import { QK_PATIENTS_BASE } from "@/components/patients/patient-table"; +import { + MultipleFileUploadZone, + MultipleFileUploadZoneHandle, +} from "../file-upload/multiple-file-upload-zone"; // ---------------- Types ---------------- type Row = { __id: number } & Record; export default function PaymentOCRBlock() { - // UI state - const fileInputRef = React.useRef(null); - const [uploadedImages, setUploadedImages] = React.useState([]); - const [isDragging, setIsDragging] = React.useState(false); + //Config + const MAX_FILES = 10; + const ACCEPTED_FILE_TYPES = "image/jpeg,image/jpg,image/png,image/webp"; + const TITLE = "Payment Document OCR"; + const DESCRIPTION = + "You can upload up to 10 files. Allowed types: JPG, PNG, WEBP."; + + // FILE/ZONE state + const uploadZoneRef = React.useRef(null); + const [filesForUI, setFilesForUI] = React.useState([]); // reactive UI only + const [isUploading, setIsUploading] = React.useState(false); // forwarded to zone const [isExtracting, setIsExtracting] = React.useState(false); // extracted rows shown only inside modal @@ -86,52 +103,27 @@ export default function PaymentOCRBlock() { // ---- handlers (all in this file) ----------------------------------------- - const handleImageSelect = (e: React.ChangeEvent) => { - const list = Array.from(e.target.files || []); - if (!list.length) return; - if (list.length > 10) { - setError("You can only upload up to 10 images."); - return; - } - setUploadedImages(list); + // Called by zone when its internal list changes (keeps parent UI reactive) + const handleZoneFilesChange = React.useCallback((files: File[]) => { + setFilesForUI(files); setError(null); - }; + }, []); - const handleImageDrop = (e: React.DragEvent) => { - e.preventDefault(); - setIsDragging(false); - const list = Array.from(e.dataTransfer.files || []).filter((f) => - f.type.startsWith("image/") - ); - if (!list.length) { - setError("Please drop image files (JPG/PNG/TIFF/BMP)."); - return; - } - if (list.length > 10) { - setError("You can only upload up to 10 images."); - return; - } - - setUploadedImages(list); - setError(null); - }; - - const removeUploadedImage = (index: number) => { - setUploadedImages((prev) => { - const next = prev.filter((_, i) => i !== index); - if (next.length === 0) { - setRows([]); - setModalColumns([]); - setError(null); - } - return next; - }); - }; + // Remove a single file by asking the zone to remove it (zone exposes removeFile) + const removeUploadedFile = React.useCallback((index: number) => { + uploadZoneRef.current?.removeFile(index); + // zone will call onFilesChange and update filesForUI automatically + }, []); + // Extract: read files from zone via ref and call mutation const handleExtract = () => { - if (!uploadedImages.length) return; + const files = uploadZoneRef.current?.getFiles() ?? []; + if (!files.length) { + setError("Please upload at least one file to extract."); + return; + } setIsExtracting(true); - extractPaymentOCR.mutate(uploadedImages); + extractPaymentOCR.mutate(files); }; const handleSave = async () => { @@ -197,14 +189,13 @@ export default function PaymentOCRBlock() { queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE }), // recent patients list ]); - // ✅ CLEAR UI: remove files and table rows - setUploadedImages([]); + // ✅ CLEAR UI: reset zone + modal + rows + uploadZoneRef.current?.reset(); + setFilesForUI([]); setRows([]); setModalColumns([]); setError(null); - setIsDragging(false); - if (fileInputRef.current) fileInputRef.current.value = ""; - + setIsExtracting(false); setShowModal(false); } catch (err: any) { toast({ @@ -217,107 +208,67 @@ export default function PaymentOCRBlock() { return (
-
-

- Payment Document OCR -

-
- - + + {TITLE} + {DESCRIPTION} + + + {/* Upload block */} -
{ - e.preventDefault(); - setIsDragging(true); - }} - onDragLeave={() => setIsDragging(false)} - onClick={() => { - if (fileInputRef.current) { - fileInputRef.current.value = ""; // ✅ reset before opening - fileInputRef.current.click(); - } - }} - > - {uploadedImages.length ? ( -
- {uploadedImages.map((file, idx) => ( -
-
- -
-

- {file.name} -

-

- {(file.size / 1024 / 1024).toFixed(2)} MB -

-
-
- -
- ))} -
- ) : ( -
- -
-

- Upload Payment Documents -

-

- Drag and drop up to 10 images or click to browse -

- -
-

- Supported formats: JPG, PNG, TIFF, BMP • Max size: 10MB each +

+ + + {/* Show list of files received from the upload zone (UI only) */} + {filesForUI.length > 0 && ( +
+

+ Uploaded ({filesForUI.length}/{MAX_FILES})

+
    + {filesForUI.map((file, idx) => ( +
  • +
    + +
    +

    + {file.name} +

    +

    + {(file.size / 1024 / 1024).toFixed(2)} MB +

    +
    +
    + +
  • + ))} +
)} - { - handleImageSelect(e); - e.currentTarget.value = ""; - }} - className="hidden" - multiple - />
{/* Extract */} -
+
{/* File Upload Zone */} -
-
-

- Upload Patient Document -

-
+
- + + Upload Patient Document + + You can upload 1 file. Allowed types: PDF + + + -
+