feat: complete approval workflow
This commit is contained in:
123
resources/js/pages/notifications/index.tsx
Normal file
123
resources/js/pages/notifications/index.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Head, Link, router } from '@inertiajs/react';
|
||||
import { Bell, CheckCheck } from 'lucide-react';
|
||||
import { PageHeading } from '@/components/page-heading';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { Paginated } from '@/types';
|
||||
|
||||
type PortalNotification = {
|
||||
id: string;
|
||||
data: {
|
||||
message: string;
|
||||
url?: string;
|
||||
reference?: string;
|
||||
};
|
||||
read_at: string | null;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export default function NotificationsIndex({
|
||||
notifications,
|
||||
}: {
|
||||
notifications: Paginated<PortalNotification>;
|
||||
}) {
|
||||
function openNotification(notification: PortalNotification) {
|
||||
router.post(`/notificacoes/${notification.id}/ler`);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head title="Notificações" />
|
||||
<main className="mx-auto flex w-full max-w-4xl flex-1 flex-col gap-7 p-5 sm:p-8">
|
||||
<PageHeading
|
||||
eyebrow="Caixa interna"
|
||||
title="Notificações"
|
||||
description="Atualizações sobre requisições, decisões e pedidos de esclarecimento."
|
||||
action={
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
router.post('/notificacoes/ler-todas')
|
||||
}
|
||||
>
|
||||
<CheckCheck /> Marcar todas como lidas
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<section className="overflow-hidden rounded-xl border bg-card">
|
||||
{notifications.data.map((notification) => (
|
||||
<button
|
||||
type="button"
|
||||
key={notification.id}
|
||||
onClick={() => openNotification(notification)}
|
||||
className={cn(
|
||||
'flex w-full gap-4 border-b p-5 text-left last:border-0 hover:bg-slate-50 dark:hover:bg-slate-900',
|
||||
notification.read_at === null && 'bg-primary/5',
|
||||
)}
|
||||
>
|
||||
<span className="mt-0.5 rounded-full bg-primary/10 p-2 text-primary">
|
||||
<Bell className="size-4" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium">
|
||||
{notification.data.message}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{new Intl.DateTimeFormat('pt-PT', {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
}).format(
|
||||
new Date(notification.created_at),
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{notification.read_at === null && (
|
||||
<span className="mt-2 size-2 rounded-full bg-primary" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
{notifications.data.length === 0 && (
|
||||
<div className="py-16 text-center text-sm text-muted-foreground">
|
||||
Não existem notificações.
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{notifications.last_page > 1 && (
|
||||
<nav className="flex justify-center gap-2">
|
||||
{notifications.links.map((link, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
asChild={Boolean(link.url)}
|
||||
disabled={!link.url}
|
||||
variant={link.active ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
>
|
||||
{link.url ? (
|
||||
<Link
|
||||
href={link.url}
|
||||
preserveScroll
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: link.label,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: link.label,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
NotificationsIndex.layout = {
|
||||
breadcrumbs: [{ title: 'Notificações', href: '/notificacoes' }],
|
||||
};
|
||||
Reference in New Issue
Block a user