From 30cfc4b3de9f51b5f03335cdd85666511598b5ba Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Sat, 16 Aug 2025 09:54:34 +0000 Subject: [PATCH] Introduce an information system for important announcements Implement a new announcement system with priority levels, role-based access, and store-specific filtering, replacing the customer order statistics card on the dashboard. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e437ffed-c31b-44eb-9fd5-b6f9faa8e7ce Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/e437ffed-c31b-44eb-9fd5-b6f9faa8e7ce/biGr9ER --- client/src/components/AnnouncementCard.tsx | 309 ++++++++++++++++++ client/src/pages/Dashboard.tsx | 49 +-- .../20250816_create_announcements_table.sql | 27 ++ migrations/migrate.js | 98 ++++++ package.scripts.js | 55 ++++ replit.md | 1 + server/routes.ts | 88 +++++ server/storage.ts | 109 ++++++ shared/schema.ts | 40 ++- 9 files changed, 729 insertions(+), 47 deletions(-) create mode 100644 client/src/components/AnnouncementCard.tsx create mode 100644 migrations/20250816_create_announcements_table.sql create mode 100644 migrations/migrate.js create mode 100644 package.scripts.js diff --git a/client/src/components/AnnouncementCard.tsx b/client/src/components/AnnouncementCard.tsx new file mode 100644 index 0000000..4a05de5 --- /dev/null +++ b/client/src/components/AnnouncementCard.tsx @@ -0,0 +1,309 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { useStore } from "@/components/Layout"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Megaphone, Plus, X, AlertTriangle, Info, Bell } from "lucide-react"; +import { useState } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { insertAnnouncementSchema } from "@shared/schema"; +import type { AnnouncementWithRelations } from "@shared/schema"; +import { z } from "zod"; +import { safeFormat, safeDate } from "@/lib/dateUtils"; + +const formSchema = insertAnnouncementSchema.extend({ + groupId: z.coerce.number().optional(), +}); + +export default function AnnouncementCard() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + const queryClient = useQueryClient(); + const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + title: "", + content: "", + priority: "normal", + groupId: undefined, + }, + }); + + // Fetch announcements + const { data: announcements = [] } = useQuery({ + queryKey: ['/api/announcements', selectedStoreId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId && user?.role === 'admin') { + params.append('storeId', selectedStoreId.toString()); + } + + const response = await fetch(`/api/announcements?${params}`, { + credentials: 'include' + }); + + if (!response.ok) { + throw new Error('Failed to fetch announcements'); + } + + return response.json(); + }, + }); + + // Fetch groups for admin + const { data: groups = [] } = useQuery({ + queryKey: ['/api/groups'], + enabled: user?.role === 'admin', + }); + + // Create announcement mutation + const createMutation = useMutation({ + mutationFn: async (data: z.infer) => { + const response = await fetch('/api/announcements', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error('Failed to create announcement'); + } + + return response.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/api/announcements'] }); + setIsCreateDialogOpen(false); + form.reset(); + }, + }); + + // Delete announcement mutation + const deleteMutation = useMutation({ + mutationFn: async (id: number) => { + const response = await fetch(`/api/announcements/${id}`, { + method: 'DELETE', + credentials: 'include', + }); + + if (!response.ok) { + throw new Error('Failed to delete announcement'); + } + + return response.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/api/announcements'] }); + }, + }); + + const onSubmit = (data: z.infer) => { + createMutation.mutate(data); + }; + + const getPriorityIcon = (priority: string) => { + switch (priority) { + case 'urgent': + return ; + case 'important': + return ; + default: + return ; + } + }; + + const getPriorityColor = (priority: string) => { + switch (priority) { + case 'urgent': + return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; + case 'important': + return 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200'; + default: + return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'; + } + }; + + return ( + + +
+ + Informations +
+ {user?.role === 'admin' && ( + + + + + + + Nouvelle information + +
+ + ( + + Titre + + + + + + )} + /> + ( + + Contenu + +