Files
accounted/app/(dashboard)/supplier-invoices/page.tsx
T
Jakob WennbergandClaude Opus 4.6 838dc6b8b5 refactor: clean up codebase, remove dead code and obsolete docs
Remove influencer-era documentation, unused components, boilerplate
assets, and ghost tiktok cron job. Add supplier invoice management,
document API routes, and PWA icons. Replace boilerplate README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:23:17 +01:00

218 lines
8.4 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
import { Plus, FileInput, AlertCircle, Clock } from 'lucide-react'
import Link from 'next/link'
import type { SupplierInvoice } from '@/types'
function formatAmount(amount: number): string {
return amount.toLocaleString('sv-SE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
const statusColors: Record<string, string> = {
registered: 'bg-blue-100 text-blue-800',
approved: 'bg-yellow-100 text-yellow-800',
paid: 'bg-green-100 text-green-800',
partially_paid: 'bg-orange-100 text-orange-800',
overdue: 'bg-red-100 text-red-800',
disputed: 'bg-purple-100 text-purple-800',
credited: 'bg-gray-100 text-gray-800',
}
const statusLabels: Record<string, string> = {
registered: 'Registrerad',
approved: 'Godkänd',
paid: 'Betald',
partially_paid: 'Delbetald',
overdue: 'Förfallen',
disputed: 'Tvist',
credited: 'Krediterad',
}
export default function SupplierInvoicesPage() {
const [invoices, setInvoices] = useState<(SupplierInvoice & { supplier?: { id: string; name: string } })[]>([])
const [isLoading, setIsLoading] = useState(true)
const [activeTab, setActiveTab] = useState('all')
useEffect(() => {
fetchInvoices()
}, [])
async function fetchInvoices() {
setIsLoading(true)
const res = await fetch('/api/supplier-invoices?status=all')
const { data } = await res.json()
setInvoices(data || [])
setIsLoading(false)
}
const filteredInvoices = invoices.filter((inv) => {
switch (activeTab) {
case 'registered': return inv.status === 'registered'
case 'approved': return inv.status === 'approved'
case 'to_pay': return inv.status === 'approved' || inv.status === 'overdue'
case 'paid': return inv.status === 'paid'
default: return true
}
})
// Summary stats
const totalUnpaid = invoices
.filter((i) => !['paid', 'credited'].includes(i.status))
.reduce((sum, i) => sum + i.remaining_amount, 0)
const overdueAmount = invoices
.filter((i) => i.status === 'overdue')
.reduce((sum, i) => sum + i.remaining_amount, 0)
const overdueCount = invoices.filter((i) => i.status === 'overdue').length
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">Leverantörsfakturor</h1>
<p className="text-muted-foreground">
Registrera och hantera inkommande fakturor
</p>
</div>
<Link href="/supplier-invoices/new">
<Button>
<Plus className="mr-2 h-4 w-4" />
Registrera faktura
</Button>
</Link>
</div>
{/* Summary cards */}
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm text-muted-foreground flex items-center gap-2">
<FileInput className="h-4 w-4" />
Totalt obetalt
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{formatAmount(totalUnpaid)} kr</p>
<p className="text-xs text-muted-foreground">
{invoices.filter((i) => !['paid', 'credited'].includes(i.status)).length} fakturor
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm text-muted-foreground flex items-center gap-2">
<AlertCircle className="h-4 w-4 text-red-500" />
Förfallet
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold text-red-600">{formatAmount(overdueAmount)} kr</p>
<p className="text-xs text-muted-foreground">{overdueCount} fakturor</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm text-muted-foreground flex items-center gap-2">
<Clock className="h-4 w-4" />
Antal fakturor
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{invoices.length}</p>
</CardContent>
</Card>
</div>
{/* Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="all">Alla</TabsTrigger>
<TabsTrigger value="registered">Registrerade</TabsTrigger>
<TabsTrigger value="approved">Godkända</TabsTrigger>
<TabsTrigger value="to_pay">Att betala</TabsTrigger>
<TabsTrigger value="paid">Betalda</TabsTrigger>
</TabsList>
<TabsContent value={activeTab}>
{isLoading ? (
<Card>
<CardContent className="p-8 text-center text-muted-foreground">
Laddar fakturor...
</CardContent>
</Card>
) : filteredInvoices.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<FileInput className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-medium">Inga fakturor</h3>
<p className="text-muted-foreground text-center mt-1">
{activeTab === 'all'
? 'Registrera din första leverantörsfaktura'
: 'Inga fakturor i denna kategori'}
</p>
{activeTab === 'all' && (
<Link href="/supplier-invoices/new">
<Button className="mt-4">
<Plus className="mr-2 h-4 w-4" />
Registrera faktura
</Button>
</Link>
)}
</CardContent>
</Card>
) : (
<Card>
<CardContent className="p-0">
<table className="w-full text-sm">
<thead>
<tr className="border-b text-left text-muted-foreground">
<th className="p-3">Ankomst</th>
<th className="p-3">Leverantör</th>
<th className="p-3">Fakturanr</th>
<th className="p-3">Fakturadatum</th>
<th className="p-3">Förfaller</th>
<th className="p-3 text-right">Belopp</th>
<th className="p-3 text-right">Kvar att betala</th>
<th className="p-3">Status</th>
</tr>
</thead>
<tbody>
{filteredInvoices.map((inv) => (
<tr key={inv.id} className="border-b last:border-0 hover:bg-muted/50">
<td className="p-3 font-mono">{inv.arrival_number}</td>
<td className="p-3">
<Link href={`/suppliers/${inv.supplier_id}`} className="hover:underline">
{inv.supplier?.name || '-'}
</Link>
</td>
<td className="p-3">
<Link href={`/supplier-invoices/${inv.id}`} className="text-primary hover:underline">
{inv.supplier_invoice_number}
</Link>
</td>
<td className="p-3">{inv.invoice_date}</td>
<td className="p-3">{inv.due_date}</td>
<td className="p-3 text-right font-mono">{formatAmount(inv.total)}</td>
<td className="p-3 text-right font-mono">{formatAmount(inv.remaining_amount)}</td>
<td className="p-3">
<Badge className={statusColors[inv.status] || ''}>
{statusLabels[inv.status] || inv.status}
</Badge>
</td>
</tr>
))}
</tbody>
</table>
</CardContent>
</Card>
)}
</TabsContent>
</Tabs>
</div>
)
}