Files
reservas/resources/js/pages/dashboard.tsx

234 lines
10 KiB
TypeScript

import { Head, Link } from '@inertiajs/react';
import {
ArrowRight,
CalendarClock,
CheckCircle2,
Clock3,
PackageCheck,
Plus,
} from 'lucide-react';
import { PageHeading } from '@/components/page-heading';
import { ReservationStatus } from '@/components/reservation-status';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import type { Reservation } from '@/types';
type Props = {
summary: { pending: number; approved: number; inUse: number };
nextReservation: Reservation | null;
recentReservations: Reservation[];
};
const dateTime = new Intl.DateTimeFormat('pt-PT', {
dateStyle: 'medium',
timeStyle: 'short',
});
export default function Dashboard({
summary,
nextReservation,
recentReservations,
}: Props) {
return (
<>
<Head title="Visão geral" />
<main className="mx-auto flex w-full max-w-7xl flex-1 flex-col gap-8 p-5 sm:p-8">
<PageHeading
eyebrow="Área pessoal"
title="Bom dia. O que precisa de reservar?"
description="Consulte a disponibilidade dos equipamentos e acompanhe as suas requisições num só lugar."
action={
<Button asChild className="shadow-sm">
<Link href="/requisicoes/nova">
<Plus /> Nova requisição
</Link>
</Button>
}
/>
<section className="grid gap-4 sm:grid-cols-3">
<SummaryCard
icon={Clock3}
label="A aguardar aprovação"
value={summary.pending}
tone="amber"
/>
<SummaryCard
icon={CheckCircle2}
label="Aprovadas"
value={summary.approved}
tone="emerald"
/>
<SummaryCard
icon={PackageCheck}
label="Em utilização"
value={summary.inUse}
tone="blue"
/>
</section>
<section className="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
<Card className="border-slate-200 shadow-none dark:border-slate-800">
<CardHeader className="flex-row items-center justify-between">
<div>
<p className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
Próxima
</p>
<CardTitle className="mt-2">
Próxima requisição
</CardTitle>
</div>
<CalendarClock className="size-5 text-primary" />
</CardHeader>
<CardContent>
{nextReservation ? (
<div className="space-y-5">
<div className="flex flex-wrap items-center gap-3">
<span className="font-mono text-sm font-medium">
{nextReservation.reference}
</span>
<ReservationStatus
status={nextReservation.status}
/>
</div>
<div className="grid gap-4 rounded-xl bg-slate-50 p-4 sm:grid-cols-2 dark:bg-slate-900">
<div>
<p className="text-xs text-muted-foreground">
Início
</p>
<p className="mt-1 text-sm font-medium">
{dateTime.format(
new Date(
nextReservation.starts_at,
),
)}
</p>
</div>
<div>
<p className="text-xs text-muted-foreground">
Fim
</p>
<p className="mt-1 text-sm font-medium">
{dateTime.format(
new Date(
nextReservation.ends_at,
),
)}
</p>
</div>
</div>
<div>
<p className="text-xs text-muted-foreground">
Equipamento
</p>
<p className="mt-1 text-sm font-medium">
{nextReservation.items
.map((item) => item.asset.name)
.join(', ')}
</p>
</div>
</div>
) : (
<div className="rounded-xl border border-dashed py-10 text-center">
<PackageCheck className="mx-auto size-8 text-slate-300" />
<p className="mt-3 text-sm font-medium">
Ainda não tem requisições futuras.
</p>
<Button
asChild
variant="link"
className="mt-1"
>
<Link href="/equipamentos">
Explorar catálogo
</Link>
</Button>
</div>
)}
</CardContent>
</Card>
<Card className="border-slate-200 shadow-none dark:border-slate-800">
<CardHeader className="flex-row items-center justify-between">
<CardTitle>Atividade recente</CardTitle>
<Button asChild variant="ghost" size="sm">
<Link href="/requisicoes">
Ver todas <ArrowRight />
</Link>
</Button>
</CardHeader>
<CardContent className="space-y-1">
{recentReservations.length > 0 ? (
recentReservations.map((reservation) => (
<div
key={reservation.id}
className="flex items-center justify-between gap-3 border-b py-3 last:border-0"
>
<div className="min-w-0">
<p className="truncate text-sm font-medium">
{reservation.reference}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{dateTime.format(
new Date(
reservation.starts_at,
),
)}
</p>
</div>
<ReservationStatus
status={reservation.status}
/>
</div>
))
) : (
<p className="py-8 text-center text-sm text-muted-foreground">
Sem atividade recente.
</p>
)}
</CardContent>
</Card>
</section>
</main>
</>
);
}
function SummaryCard({
icon: Icon,
label,
value,
tone,
}: {
icon: typeof Clock3;
label: string;
value: number;
tone: 'amber' | 'emerald' | 'blue';
}) {
const colors = {
amber: 'bg-amber-50 text-amber-700',
emerald: 'bg-emerald-50 text-emerald-700',
blue: 'bg-blue-50 text-blue-700',
};
return (
<Card className="gap-3 border-slate-200 py-5 shadow-none dark:border-slate-800">
<CardContent className="flex items-center gap-4">
<span
className={`flex size-11 items-center justify-center rounded-xl ${colors[tone]}`}
>
<Icon className="size-5" />
</span>
<div>
<p className="text-2xl font-semibold">{value}</p>
<p className="text-sm text-muted-foreground">{label}</p>
</div>
</CardContent>
</Card>
);
}
Dashboard.layout = {
breadcrumbs: [{ title: 'Visão geral', href: '/dashboard' }],
};