quest-vorteq/src/app/(portal)/invoices/page.tsx
Lorentz Hinrichsen b32553668d
Some checks failed
Build and Deploy / build (push) Successful in 5m4s
Build and Deploy / deploy (push) Failing after 3s
feat: add invoice viewing (C-015) and notifications display (C-016)
- Invoice page with table, search, sort, CSV export, PDF download
- Company-level access control (can_access_invoices + view_invoices permission)
- Notifications page with unread alerts section and mark-as-read
- Fix unread notification count in layout (was counting read markers)
- Fix dashboard notification count to use per-user unread alerts
- Configurable INVOICE_STORAGE_PATH for invoice PDF file serving

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:04:01 -05:00

88 lines
2.6 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { InvoiceTable } from '@/components/invoices/invoice-table';
import { FileText } from 'lucide-react';
import type { InvoiceEntry } from '@/types/invoices';
function InvoiceSkeleton() {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-4">
<div className="h-8 w-48 animate-pulse rounded bg-muted" />
<div className="h-10 w-full animate-pulse rounded bg-muted" />
<div className="space-y-2">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="h-12 w-full animate-pulse rounded bg-muted"
/>
))}
</div>
</div>
</CardContent>
</Card>
);
}
export default function InvoicesPage() {
const [invoices, setInvoices] = useState<InvoiceEntry[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [accessDenied, setAccessDenied] = useState(false);
useEffect(() => {
fetch('/api/invoices')
.then((res) => {
if (res.status === 403) {
setAccessDenied(true);
return [];
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((json) => {
if (!accessDenied) setInvoices(json);
})
.catch((err) => setError(err.message));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (accessDenied) {
return (
<div>
<h1 className="mb-6 text-3xl font-bold">Invoices</h1>
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<FileText className="mb-4 h-12 w-12 text-muted-foreground/40" />
<p className="text-lg font-medium text-muted-foreground">
Invoice access is not available
</p>
<p className="text-sm text-muted-foreground">
Your company does not have invoice viewing enabled. Please contact
Vorteq support if you need access to invoices.
</p>
</CardContent>
</Card>
</div>
);
}
return (
<div>
<h1 className="mb-6 text-3xl font-bold">Invoices</h1>
{error ? (
<Card>
<CardContent className="p-6 text-center text-destructive">
Failed to load invoices: {error}
</CardContent>
</Card>
) : invoices === null ? (
<InvoiceSkeleton />
) : (
<InvoiceTable data={invoices} />
)}
</div>
);
}