'use client' import { useCallback, useEffect, useState } from 'react' import type { WorkspaceComponentProps } from '@/lib/extensions/workspace-registry' import type { InvoiceInboxItem, Supplier, InboxItemStatus } from '@/types' import type { InvoiceInboxSettings } from '@/extensions/general/invoice-inbox/types' import { PageHeader } from '@/components/ui/page-header' import { Card, CardContent } from '@/components/ui/card' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { Settings, Inbox, CheckCircle2, AlertTriangle } from 'lucide-react' import InboxItemCard from '@/components/extensions/general/invoice-inbox/InboxItemCard' import InboxUploadZone from '@/components/extensions/general/invoice-inbox/InboxUploadZone' import InboxDetailDialog from '@/components/extensions/general/invoice-inbox/InboxDetailDialog' import InboxSettingsDialog from '@/components/extensions/general/invoice-inbox/InboxSettingsDialog' type TabValue = 'all' | InboxItemStatus const TABS: { value: TabValue; label: string }[] = [ { value: 'all', label: 'Alla' }, { value: 'pending', label: 'Ny' }, { value: 'ready', label: 'Klar' }, { value: 'confirmed', label: 'Bekräftad' }, { value: 'rejected', label: 'Avvisad' }, { value: 'error', label: 'Fel' }, ] const DEFAULT_SETTINGS: InvoiceInboxSettings = { autoProcessEnabled: true, autoMatchSupplierEnabled: true, supplierMatchThreshold: 0.7, inboxEmail: null, } export default function InvoiceInboxWorkspace({ userId }: WorkspaceComponentProps) { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [activeTab, setActiveTab] = useState('all') const [selectedItem, setSelectedItem] = useState(null) const [isUploading, setIsUploading] = useState(false) const [settings, setSettings] = useState(DEFAULT_SETTINGS) const [settingsOpen, setSettingsOpen] = useState(false) const [suppliers, setSuppliers] = useState([]) const fetchItems = useCallback(async () => { try { const res = await fetch('/api/extensions/ext/invoice-inbox/inbox') if (res.ok) { const { data } = await res.json() setItems(data ?? []) } } catch { // Silently fail — user sees empty state } finally { setLoading(false) } }, []) const fetchSettings = useCallback(async () => { try { const res = await fetch('/api/extensions/ext/invoice-inbox/settings') if (res.ok) { const { data } = await res.json() if (data) setSettings(data) } } catch { // Use defaults } }, []) const fetchSuppliers = useCallback(async () => { try { const res = await fetch('/api/suppliers') if (res.ok) { const { data } = await res.json() setSuppliers(data ?? []) } } catch { // ok } }, []) useEffect(() => { fetchItems() fetchSettings() fetchSuppliers() }, [fetchItems, fetchSettings, fetchSuppliers]) function handleUploadComplete(result: InvoiceInboxItem | InvoiceInboxItem[]) { const newItems = Array.isArray(result) ? result : [result] setItems((prev) => [...newItems, ...prev]) for (const item of newItems) { pollItem(item.id) } } async function pollItem(itemId: string) { for (let i = 0; i < 20; i++) { await new Promise((r) => setTimeout(r, 3000)) try { const res = await fetch(`/api/extensions/ext/invoice-inbox/inbox/${itemId}`) if (!res.ok) continue const { data } = await res.json() if (data && data.status !== 'processing') { setItems((prev) => prev.map((it) => (it.id === itemId ? data : it)) ) // Also update the detail dialog if it's open for this item setSelectedItem((current) => current?.id === itemId ? data : current ) return } } catch { // continue polling } } } async function handleConfirm( itemId: string, supplierId?: string, newSupplierData?: import('@/components/extensions/general/invoice-inbox/InboxDetailDialog').NewSupplierData ) { const body: Record = {} if (supplierId) { body.supplier_id = supplierId } else if (newSupplierData) { body.new_supplier = newSupplierData } const res = await fetch(`/api/extensions/ext/invoice-inbox/inbox/${itemId}/confirm`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (res.ok) { setItems((prev) => prev.map((it) => (it.id === itemId ? { ...it, status: 'confirmed' as const } : it)) ) setSelectedItem(null) fetchSuppliers() // New supplier may have been created } } async function handleReject(itemId: string) { const res = await fetch(`/api/extensions/ext/invoice-inbox/inbox/${itemId}`, { method: 'DELETE', }) if (res.ok) { setItems((prev) => prev.map((it) => (it.id === itemId ? { ...it, status: 'rejected' as const } : it)) ) setSelectedItem(null) } } async function handleReprocess(itemId: string) { setItems((prev) => prev.map((it) => (it.id === itemId ? { ...it, status: 'processing' as const } : it)) ) setSelectedItem(null) const res = await fetch(`/api/extensions/ext/invoice-inbox/inbox/${itemId}/process`, { method: 'POST', }) if (res.ok) { const { data } = await res.json() if (data) { setItems((prev) => prev.map((it) => (it.id === itemId ? data : it))) } } else { // Refetch in case of error update fetchItems() } } async function handleSaveSettings(updated: InvoiceInboxSettings) { const res = await fetch('/api/extensions/ext/invoice-inbox/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updated), }) if (res.ok) { const { data } = await res.json() if (data) setSettings(data) } } const filteredItems = activeTab === 'all' ? items : items.filter((it) => it.status === activeTab) const totalCount = items.length const readyCount = items.filter((it) => it.status === 'ready').length const errorCount = items.filter((it) => it.status === 'error').length return (
setSettingsOpen(true)} > } /> {/* KPI cards */}

{totalCount}

Totalt

{readyCount}

Att granska

{errorCount}

Fel

{/* Upload zone */} {/* Tabs + item list */} setActiveTab(v as TabValue)}> {TABS.map((tab) => ( {tab.label} ))} {TABS.map((tab) => ( {loading ? (
{Array.from({ length: 3 }).map((_, i) => ( ))}
) : filteredItems.length === 0 ? (

{activeTab === 'all' ? 'Inga fakturor ännu. Ladda upp en faktura ovan.' : 'Inga fakturor med denna status.'}

) : (
{filteredItems.map((item) => ( setSelectedItem(item)} /> ))}
)}
))}
{/* Detail dialog */} { if (!open) setSelectedItem(null) }} onConfirm={handleConfirm} onReject={handleReject} onReprocess={handleReprocess} suppliers={suppliers} /> {/* Settings dialog */}
) }