- 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>
70 lines
2 KiB
TypeScript
70 lines
2 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { NotificationList } from '@/components/notifications/notification-list';
|
|
import type { NotificationItem } from '@/types/notifications';
|
|
|
|
function NotificationsSkeleton() {
|
|
return (
|
|
<div className="space-y-3">
|
|
{[...Array(3)].map((_, i) => (
|
|
<Card key={i}>
|
|
<CardContent className="p-6">
|
|
<div className="space-y-2">
|
|
<div className="h-5 w-48 animate-pulse rounded bg-muted" />
|
|
<div className="h-4 w-32 animate-pulse rounded bg-muted" />
|
|
<div className="h-4 w-full animate-pulse rounded bg-muted" />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function NotificationsPage() {
|
|
const [notifications, setNotifications] = useState<NotificationItem[] | null>(
|
|
null
|
|
);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetch('/api/notifications')
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return res.json();
|
|
})
|
|
.then((json) => setNotifications(json))
|
|
.catch((err) => setError(err.message));
|
|
}, []);
|
|
|
|
const handleMarkRead = async (id: string) => {
|
|
const res = await fetch(`/api/notifications/${id}/read`, {
|
|
method: 'POST',
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error('Failed to mark notification as read');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<h1 className="mb-6 text-3xl font-bold">Notifications</h1>
|
|
{error ? (
|
|
<Card>
|
|
<CardContent className="p-6 text-center text-destructive">
|
|
Failed to load notifications: {error}
|
|
</CardContent>
|
|
</Card>
|
|
) : notifications === null ? (
|
|
<NotificationsSkeleton />
|
|
) : (
|
|
<NotificationList
|
|
notifications={notifications}
|
|
onMarkRead={handleMarkRead}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|