89 lines
2.6 KiB
TypeScript
89 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>
|
||
|
|
);
|
||
|
|
}
|