'use client' import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/navigation' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { useToast } from '@/components/ui/use-toast' import { Loader2, ExternalLink, FileText, Upload, ImagePlus, MailPlus } from 'lucide-react' import { formatCurrency, formatDate } from '@/lib/utils' import type { AgentInboxItemView } from '@/app/(dashboard)/agent-inbox/page' import { assessReceiptQuality } from './receipt-quality' // Mirrors ReceiptExtractionResult, kept local + forgiving since legacy rows // may be sparse. interface ExtractedReceipt { merchant?: { name?: string | null orgNumber?: string | null vatNumber?: string | null isForeign?: boolean } | null receipt?: { date?: string | null time?: string | null currency?: string | null } | null totals?: { subtotal?: number | null vatAmount?: number | null total?: number | null } | null lineItems?: Array<{ description?: string quantity?: number unitPrice?: number | null lineTotal?: number vatRate?: number | null }> | null flags?: { isRestaurant?: boolean isSystembolaget?: boolean isForeignMerchant?: boolean } | null } interface Props { open: boolean onOpenChange: (open: boolean) => void inbox: AgentInboxItemView['inbox_item'] } export default function ReceiptDetailDialog({ open, onOpenChange, inbox }: Props) { const data = (inbox.extracted_data as ExtractedReceipt | null) ?? {} const merchant = data.merchant?.name ?? inbox.document?.file_name ?? 'Okänt kvitto' const currency = data.receipt?.currency ?? 'SEK' const lineItems = data.lineItems ?? [] const flags = data.flags ?? {} // Lazily fetch a signed download URL so we can preview the file. const [downloadUrl, setDownloadUrl] = useState(null) const [loadingUrl, setLoadingUrl] = useState(false) const [uploading, setUploading] = useState(false) const [uploadError, setUploadError] = useState(null) const [requesting, setRequesting] = useState(false) const fileInputRef = useRef(null) const router = useRouter() const { toast } = useToast() const quality = assessReceiptQuality(inbox) useEffect(() => { if (!open || !inbox.document?.id || downloadUrl) return setLoadingUrl(true) fetch(`/api/documents/${inbox.document.id}`) .then((r) => r.json()) .then((body) => { if (body?.data?.download_url) setDownloadUrl(body.data.download_url) }) .catch(() => { /* fall back to no preview */ }) .finally(() => setLoadingUrl(false)) }, [open, inbox.document?.id, downloadUrl]) const mime = inbox.document?.mime_type ?? null const isImage = mime?.startsWith('image/') ?? false const isPdf = mime === 'application/pdf' const handleFilePicked = async (file: File) => { setUploading(true) setUploadError(null) try { const body = new FormData() body.append('file', file) const res = await fetch(`/api/ai/inbox-items/${inbox.id}/attach-file`, { method: 'POST', body, }) const json = await res.json() if (!res.ok) { setUploadError(json?.error ?? 'Kunde inte ladda upp filen.') setUploading(false) return } toast({ title: 'Kvittobild uppladdad' }) // Force the server component to re-run so the new inbox.document // propagates into the card + modal. router.refresh() onOpenChange(false) } catch { setUploadError('Nätverksfel.') } finally { setUploading(false) } } const handleRequestReceipt = async () => { setRequesting(true) try { const res = await fetch(`/api/ai/inbox-items/${inbox.id}/request-receipt`, { method: 'POST', }) const body = await res.json() if (!res.ok) { toast({ title: 'Kunde inte skicka begäran', description: body?.error ?? 'Försök igen.', variant: 'destructive', }) setRequesting(false) return } toast({ title: 'Begäran skickad', description: `Mejl skickat till ${body.data.sent} av ${body.data.total} medlemmar.`, }) } catch { toast({ title: 'Nätverksfel', variant: 'destructive', }) } finally { setRequesting(false) } } return ( {merchant} {data.receipt?.date ? formatDate(data.receipt.date) : 'Okänt datum'} {data.receipt?.time && ` · ${data.receipt.time}`} {data.totals?.total != null && ( · {formatCurrency(data.totals.total, currency)} )}
{/* File preview */}
{!inbox.document ? (

Ingen kvittobild

Utan bildbevis kan bokföringen inte verifieras. Ladda upp kvittot (PDF, JPG, PNG, WebP — max 15 MB).

{ const f = e.target.files?.[0] if (f) handleFilePicked(f) e.target.value = '' }} />
{uploadError && (

{uploadError}

)}
) : loadingUrl ? ( ) : !downloadUrl ? (
Kunde inte ladda filen
) : isImage ? ( // eslint-disable-next-line @next/next/no-img-element {inbox.document.file_name} ) : isPdf ? (