"use client" import { useState, useRef } from "react" import { useMutation, useQueryClient } from "@tanstack/react-query" import { Paperclip, Plus, Trash2, FileIcon, ImageIcon, FileTextIcon, BadgeDollarSignIcon, CalendarIcon, CreditCardIcon, HashIcon, UserIcon, BriefcaseIcon, } from "lucide-react" import { toast } from "sonner" import { ResourcePage } from "@/shared/data-view/resource-page" import { ColumnHeader } from "@/shared/data-view/table-view" import FormDialog from "@/shared/components/form-dialog" import { PaymentMadeForm } from "@/modules/payment-mades/payment-made-form" import { Button } from "@/shared/components/ui/button" import { Card, CardContent } from "@/shared/components/ui/card" import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/shared/components/ui/dialog" import { confirm } from "@/shared/components/confirm-dialog" import { useAuthApi } from "@/shared/useApi" import { PAYMENT_MADE_ROUTES } from "@garage/api" import type { PaymentMadesClient } from "@garage/api" // ── Attachment helpers ── type AttachmentFile = { id: number original_name?: string attachment_path?: string created_at?: string } function getFileIcon(path?: string) { if (!path) return FileIcon const lower = path.toLowerCase() if (/\.(jpg|jpeg|png|gif|webp|svg)$/.test(lower)) return ImageIcon if (/\.pdf$/.test(lower)) return FileTextIcon return FileIcon } // ── Attachments Dialog ── function AttachmentsDialog({ open, paymentId, paymentRef, onClose, }: { open: boolean paymentId: string paymentRef: string onClose: () => void }) { const api = useAuthApi() const queryClient = useQueryClient() const fileInputRef = useRef(null) const [isUploading, setIsUploading] = useState(false) const [sessionFiles, setSessionFiles] = useState([]) const queryKey = [PAYMENT_MADE_ROUTES.INDEX, paymentId, "attachments"] const deleteMutation = useMutation({ mutationFn: (attachmentId: number) => api.paymentMades.deleteAttachment(paymentId, { attachment_id: attachmentId } as any), onSuccess: (_, attachmentId) => { toast.success("Attachment deleted.") setSessionFiles((prev) => prev.filter((f) => f.id !== attachmentId)) queryClient.invalidateQueries({ queryKey }) }, onError: () => toast.error("Failed to delete attachment."), }) const handleDelete = async (attachment: AttachmentFile) => { const confirmed = await confirm({ title: "Delete Attachment", description: `Are you sure you want to delete "${attachment.original_name ?? "this file"}"?`, confirmLabel: "Delete", variant: "destructive", }) if (confirmed) deleteMutation.mutate(attachment.id) } const handleUpload = async (e: React.ChangeEvent) => { const files = e.target.files if (!files || files.length === 0) return setIsUploading(true) const fileArray = Array.from(files) try { const formData = new FormData() fileArray.forEach((file) => formData.append("attachments[]", file)) await toast.promise( api.paymentMades.addAttachment(paymentId, formData), { loading: "Uploading attachment(s)...", success: "Attachment(s) uploaded successfully", error: "Failed to upload attachment(s)", }, ) const now = new Date().toISOString() const uploaded: AttachmentFile[] = fileArray.map((file, i) => ({ id: Date.now() + i, original_name: file.name, attachment_path: file.name, created_at: now, })) setSessionFiles((prev) => [...prev, ...uploaded]) queryClient.invalidateQueries({ queryKey }) } finally { setIsUploading(false) if (fileInputRef.current) fileInputRef.current.value = "" } } const handleClose = () => { setSessionFiles([]) onClose() } return ( !v && handleClose()}> Attachments — {paymentRef}
{sessionFiles.length === 0 ? ( No attachments uploaded in this session. Click "Upload Attachment" to add files. ) : (
{sessionFiles.map((attachment) => { const Icon = getFileIcon(attachment.attachment_path) return (
{attachment.original_name} {attachment.created_at && ( {new Date(attachment.created_at).toLocaleDateString()} )}
) })}
)}
) } // ── Page ── type PaymentMadeItem = { id: number payment_number?: string vendor_name?: string employee_name?: string payment_for?: string payment_made?: string | number payment_mode_name?: string payment_date?: string paid_through?: string notes?: string created_at?: string } export default function PaymentsMadePage() { const [attachmentTarget, setAttachmentTarget] = useState<{ id: string ref: string } | null>(null) return ( <> pageTitle="Payments Made" routeKey={PAYMENT_MADE_ROUTES.INDEX} getClient={(api) => api.paymentMades} headerProps={({ invalidateQuery }) => ({ actions: ( {(resourceId) => ( )} ), })} columns={({ actionsColumn }) => [ { accessorKey: "payment_number", header: ({ column }) => , cell: ({ row }) => { const item = row.original as unknown as PaymentMadeItem return (
{item.payment_number || "—"}
) }, }, { accessorKey: "vendor_name", header: ({ column }) => , cell: ({ row }) => { const item = row.original as unknown as PaymentMadeItem return (
{item.vendor_name || "—"}
) }, }, { accessorKey: "payment_for", header: ({ column }) => , cell: ({ row }) => { const item = row.original as unknown as PaymentMadeItem return (
{item.payment_for || "—"}
) }, }, { accessorKey: "payment_made", header: ({ column }) => , cell: ({ row }) => { const item = row.original as unknown as PaymentMadeItem const amount = item.payment_made ? Number(item.payment_made).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2, }) : "—" return (
{amount}
) }, }, { accessorKey: "payment_mode_name", header: ({ column }) => , cell: ({ row }) => { const item = row.original as unknown as PaymentMadeItem return (
{item.payment_mode_name || "—"}
) }, }, { accessorKey: "payment_date", header: ({ column }) => , cell: ({ row }) => { const item = row.original as unknown as PaymentMadeItem const formatted = item.payment_date ? new Date(item.payment_date).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }) : "—" return (
{formatted}
) }, }, { id: "attachments", header: () => null, cell: ({ row }) => { const item = row.original as any return ( ) }, }, actionsColumn(), ]} /> {attachmentTarget && ( setAttachmentTarget(null)} /> )} ) }