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
129 lines
4.7 KiB
TypeScript
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>
|
|
);
|
|
}
|