VigilanzaTurni/client/src/pages/notifications.tsx
marco370 abe4041cd1 Add basic UI components and structure for the application
Initial commit adds core UI components, including layout elements, form controls, and navigation elements, along with the main application structure and routing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 99f0fce6-9386-489a-9632-1d81223cab44
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/99f0fce6-9386-489a-9632-1d81223cab44/nGJAldO
2025-10-11 09:36:55 +00:00

129 lines
4.7 KiB
TypeScript

import { useQuery, useMutation } from "@tanstack/react-query";
import { Notification } from "@shared/schema";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Bell, Check, Calendar, AlertTriangle, Clock } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { formatDistanceToNow } from "date-fns";
import { it } from "date-fns/locale";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
import { cn } from "@/lib/utils";
export default function Notifications() {
const { toast } = useToast();
const { data: notifications, isLoading } = useQuery<Notification[]>({
queryKey: ["/api/notifications"],
});
const markAsReadMutation = useMutation({
mutationFn: async (notificationId: string) => {
return await apiRequest("PATCH", `/api/notifications/${notificationId}/read`, {});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/notifications"] });
},
onError: (error) => {
toast({
title: "Errore",
description: error.message,
variant: "destructive",
});
},
});
const getNotificationIcon = (type: string) => {
const icons: Record<string, React.ReactNode> = {
shift_assigned: <Calendar className="h-5 w-5" />,
certification_expiring: <AlertTriangle className="h-5 w-5" />,
shift_reminder: <Clock className="h-5 w-5" />,
};
return icons[type] || <Bell className="h-5 w-5" />;
};
const unreadCount = notifications?.filter(n => !n.isRead).length || 0;
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-semibold mb-2">Notifiche</h1>
<p className="text-muted-foreground">
{unreadCount > 0 ? `${unreadCount} notifiche non lette` : "Tutte le notifiche lette"}
</p>
</div>
{isLoading ? (
<div className="space-y-3">
<Skeleton className="h-24" />
<Skeleton className="h-24" />
<Skeleton className="h-24" />
</div>
) : notifications && notifications.length > 0 ? (
<div className="space-y-3">
{notifications.map((notification) => (
<Card
key={notification.id}
className={cn(
"hover-elevate",
!notification.isRead && "border-primary/50 bg-primary/5"
)}
data-testid={`notification-${notification.id}`}
>
<CardHeader className="pb-3">
<div className="flex items-start gap-4">
<div className={cn(
"p-2 rounded-md",
!notification.isRead ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"
)}>
{getNotificationIcon(notification.type)}
</div>
<div className="flex-1 min-w-0">
<CardTitle className="text-base flex items-center gap-2">
{notification.title}
{!notification.isRead && (
<span className="h-2 w-2 rounded-full bg-primary" />
)}
</CardTitle>
<CardDescription className="mt-1">
{notification.message}
</CardDescription>
</div>
{!notification.isRead && (
<Button
variant="ghost"
size="icon"
onClick={() => markAsReadMutation.mutate(notification.id)}
data-testid={`button-mark-read-${notification.id}`}
>
<Check className="h-4 w-4" />
</Button>
)}
</div>
</CardHeader>
<CardContent>
<p className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(notification.createdAt), {
addSuffix: true,
locale: it,
})}
</p>
</CardContent>
</Card>
))}
</div>
) : (
<Card>
<CardContent className="flex flex-col items-center justify-center py-16">
<Bell className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-lg font-medium mb-2">Nessuna notifica</p>
<p className="text-sm text-muted-foreground">
Le notifiche appariranno qui quando disponibili
</p>
</CardContent>
</Card>
)}
</div>
);
}