Improve announcement card display and fix creation bug

Update the announcement card component to match the dashboard's visual style and resolve an issue where new announcements were not displaying correctly due to incorrect data handling and display logic.

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/dnQ3tT8
This commit is contained in:
michaelschal committed 2025-08-17 09:17:43 +00:00
1 parent 9f694d0251
commit 243240701c
6 files changed
+358 -367

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+348 -355
View File
@@ -1,449 +1,442 @@
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, Clock, Circle, Edit2, Bell, Info, CheckCircle } from "lucide-react";
import { useState } from "react";
import React 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 { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
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 { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Megaphone,
Plus,
Edit2,
X,
Info,
AlertTriangle,
CheckCircle,
XCircle
} from "lucide-react";
import { useAuthUnified } from "@/hooks/useAuthUnified";
import { useStore } from "@/components/Layout";
import { safeFormat, safeDate } from "@/lib/dateUtils";
import {
insertAnnouncementSchema,
type InsertAnnouncement,
type DashboardMessage
} from "@shared/schema";
import { useToast } from "@/hooks/use-toast";
// Schéma adapté pour DASHBOARD_MESSAGES
const formSchema = z.object({
title: z.string().min(1, "Le titre est obligatoire"),
content: z.string().min(1, "Le contenu est obligatoire"),
type: z.enum(["info", "warning", "error", "success"]).default("info"),
});
// Type pour les annonces avec relations
type AnnouncementWithRelations = DashboardMessage & {
author: {
id: string | number;
firstName?: string;
lastName?: string;
username: string;
};
group?: {
id: number;
name: string;
};
};
// Configuration des priorités
const getPriorityConfig = (type: string) => {
switch (type) {
case 'error':
return { label: 'Erreur', color: 'bg-red-100 text-red-800' };
case 'warning':
return { label: 'Attention', color: 'bg-orange-100 text-orange-800' };
case 'success':
return { label: 'Succès', color: 'bg-green-100 text-green-800' };
case 'info':
default:
return { label: 'Information', color: 'bg-blue-100 text-blue-800' };
}
};
export default function AnnouncementCard() {
const { user } = useAuthUnified();
const { selectedStoreId } = useStore();
const queryClient = useQueryClient();
const { toast } = useToast();
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [editingAnnouncement, setEditingAnnouncement] = useState<AnnouncementWithRelations | null>(null);
const { toast } = useToast();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
// Configuration du formulaire
const form = useForm<InsertAnnouncement>({
resolver: zodResolver(insertAnnouncementSchema),
defaultValues: {
title: "",
content: "",
type: "info",
title: '',
content: '',
type: 'info',
storeId: selectedStoreId ? parseInt(selectedStoreId.toString()) : null,
createdBy: user?.username || '',
},
});
// Reset form when editing announcement changes
React.useEffect(() => {
if (editingAnnouncement) {
form.reset({
title: editingAnnouncement.title,
content: editingAnnouncement.content,
type: editingAnnouncement.type as "info" | "warning" | "error" | "success",
});
} else {
form.reset({
title: "",
content: "",
type: "info",
});
// Construire l'URL avec les paramètres appropriés
const buildApiUrl = () => {
const params = new URLSearchParams();
// Pour les admins, permettre le filtrage par magasin sélectionné
if (user?.role === 'admin' && selectedStoreId) {
params.append('storeId', selectedStoreId.toString());
}
}, [editingAnnouncement, form]);
return `/api/announcements${params.toString() ? `?${params.toString()}` : ''}`;
};
// Fetch announcements
// Récupérer les annonces
const { data: announcements = [], isLoading } = useQuery<AnnouncementWithRelations[]>({
queryKey: ['/api/announcements', selectedStoreId],
queryKey: ['/api/announcements', selectedStoreId, user?.role],
queryFn: async () => {
const params = new URLSearchParams();
if (selectedStoreId && user?.role === 'admin') {
params.append('storeId', selectedStoreId.toString());
}
const url = buildApiUrl();
console.log('🔍 [ANNOUNCEMENTS] Fetching from:', url);
const response = await fetch(`/api/announcements?${params}`, {
const response = await fetch(url, {
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to fetch announcements');
console.error('❌ [ANNOUNCEMENTS] Fetch failed:', response.status, response.statusText);
if (response.status === 401) {
console.log('🔑 [ANNOUNCEMENTS] Authentication required');
return [];
}
throw new Error(`Failed to fetch announcements: ${response.status}`);
}
return response.json();
const data = await response.json();
console.log('✅ [ANNOUNCEMENTS] Data received:', data);
if (!Array.isArray(data)) {
console.warn('⚠️ [ANNOUNCEMENTS] Data is not an array:', data);
return [];
}
return data;
},
enabled: !!user,
});
// Create/Update announcement mutation
// Mutation pour créer/modifier une annonce
const createMutation = useMutation({
mutationFn: async (data: z.infer<typeof formSchema>) => {
const isEditing = !!editingAnnouncement;
console.log('📢 Submitting announcement:', { data, isEditing });
const url = isEditing
mutationFn: async (announcementData: InsertAnnouncement) => {
const url = editingAnnouncement
? `/api/announcements/${editingAnnouncement.id}`
: '/api/announcements';
const method = isEditing ? 'PUT' : 'POST';
const method = editingAnnouncement ? 'PUT' : 'POST';
console.log(`🔍 [ANNOUNCEMENTS] ${method} ${url}`, announcementData);
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(data),
body: JSON.stringify(announcementData),
});
console.log('📢 Announcement response:', {
status: response.status,
statusText: response.statusText,
ok: response.ok
});
if (!response.ok) {
const errorText = await response.text();
console.error('📢 Announcement failed:', errorText);
throw new Error(`Failed to ${isEditing ? 'update' : 'create'} announcement: ${response.status} - ${errorText}`);
console.error(`❌ [ANNOUNCEMENTS] ${method} failed:`, response.status, errorText);
throw new Error(`Failed to ${editingAnnouncement ? 'update' : 'create'} announcement: ${response.status}`);
}
const result = await response.json();
console.log('📢 Announcement success:', result);
return result;
return response.json();
},
onSuccess: (data) => {
const isEditing = !!editingAnnouncement;
console.log('📢 Announcement success callback:', data);
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/announcements'] });
setIsCreateDialogOpen(false);
setEditingAnnouncement(null);
form.reset();
toast({
title: "Succès",
description: isEditing ? "Information modifiée avec succès" : "Information créée avec succès",
title: editingAnnouncement ? "Information modifiée" : "Information créée",
description: editingAnnouncement ? "L'information a été modifiée avec succès." : "L'information a été créée avec succès.",
});
},
onError: (error) => {
const isEditing = !!editingAnnouncement;
console.error('📢 Announcement error:', error);
console.error('❌ [ANNOUNCEMENTS] Mutation error:', error);
toast({
title: "Erreur",
description: error.message || `Impossible de ${isEditing ? 'modifier' : 'créer'} l'information`,
description: `Impossible de ${editingAnnouncement ? 'modifier' : 'créer'} l'information.`,
variant: "destructive",
});
},
});
// Delete announcement mutation
// Mutation pour supprimer une annonce
const deleteMutation = useMutation({
mutationFn: async (id: number) => {
const response = await fetch(`/api/announcements/${id}`, {
mutationFn: async (announcementId: number) => {
const response = await fetch(`/api/announcements/${announcementId}`, {
method: 'DELETE',
credentials: 'include',
});
if (!response.ok) {
throw new Error('Failed to delete announcement');
throw new Error(`Failed to delete announcement: ${response.status}`);
}
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/announcements'] });
toast({
title: "Information supprimée",
description: "L'information a été supprimée avec succès.",
});
},
onError: (error) => {
console.error('❌ [ANNOUNCEMENTS] Delete error:', error);
toast({
title: "Erreur",
description: "Impossible de supprimer l'information.",
variant: "destructive",
});
},
});
const onSubmit = (data: z.infer<typeof formSchema>) => {
console.log('🔥 [FORM] onSubmit called with data:', data);
console.log('🔥 [FORM] Form errors:', form.formState.errors);
console.log('🔥 [FORM] Form is valid:', form.formState.isValid);
console.log('🔥 [FORM] Form is submitting:', form.formState.isSubmitting);
// Soumission du formulaire
const onSubmit = (data: InsertAnnouncement) => {
console.log('🔍 [ANNOUNCEMENTS] Form submitted:', data);
if (!data.title || !data.content) {
console.error('🔥 [FORM] Missing required fields:', { title: data.title, content: data.content });
toast({
title: "Erreur",
description: "Le titre et le contenu sont obligatoires",
variant: "destructive",
});
return;
}
const announcementData = {
...data,
createdBy: user?.username || '',
storeId: data.storeId || null
};
console.log('🔥 [FORM] Calling createMutation.mutate');
createMutation.mutate(data);
createMutation.mutate(announcementData);
};
const getPriorityIcon = (priority: string) => {
switch (priority) {
case 'urgent':
return <AlertTriangle className="h-4 w-4" />;
case 'important':
return <Bell className="h-4 w-4" />;
default:
return <Info className="h-4 w-4" />;
}
};
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';
}
};
if (isLoading) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Informations</CardTitle>
<Megaphone className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="flex items-center justify-center h-32">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
</div>
</CardContent>
</Card>
);
}
const getPriorityConfig = (type: string) => {
switch (type) {
case 'error':
return {
color: 'destructive' as const,
icon: AlertTriangle,
label: 'Erreur'
};
case 'warning':
return {
color: 'default' as const,
icon: Bell,
label: 'Attention'
};
case 'success':
return {
color: 'default' as const,
icon: CheckCircle,
label: 'Succès'
};
case 'info':
default:
return {
color: 'secondary' as const,
icon: Info,
label: 'Information'
};
}
};
const getTypeIcon = (type: string) => {
switch (type) {
case 'error':
return AlertTriangle;
case 'warning':
return Bell;
case 'success':
return CheckCircle;
case 'info':
default:
return Info;
}
// Ouvrir le dialogue pour éditer
const handleEdit = (announcement: AnnouncementWithRelations) => {
setEditingAnnouncement(announcement);
form.reset({
title: announcement.title,
content: announcement.content,
type: announcement.type as any,
storeId: announcement.storeId,
createdBy: announcement.createdBy,
});
setIsCreateDialogOpen(true);
};
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Informations</CardTitle>
<div className="flex items-center gap-2">
{user?.role === 'admin' && (
<Dialog open={isCreateDialogOpen} onOpenChange={(open) => {
setIsCreateDialogOpen(open);
if (!open) {
setEditingAnnouncement(null);
form.reset();
}
}}>
<DialogTrigger asChild>
<Button size="sm" variant="ghost" className="h-8 w-8 p-0">
<Plus className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>
{editingAnnouncement ? 'Modifier l\'information' : 'Nouvelle information'}
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Titre</FormLabel>
<FormControl>
<Input placeholder="Titre de l'information" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormLabel>Contenu</FormLabel>
<FormControl>
<Textarea
placeholder="Contenu de l'information"
rows={4}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Type</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Sélectionner un type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="info">Information</SelectItem>
<SelectItem value="warning">Attention</SelectItem>
<SelectItem value="error">Erreur</SelectItem>
<SelectItem value="success">Succès</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end space-x-2 pt-4">
<Button
type="button"
variant="outline"
onClick={() => setIsCreateDialogOpen(false)}
>
Annuler
</Button>
<Button
type="submit"
disabled={createMutation.isPending}
onClick={() => console.log('🔥 [BUTTON] Submit button clicked')}
>
{createMutation.isPending
? (editingAnnouncement ? 'Modification...' : 'Création...')
: (editingAnnouncement ? 'Modifier' : 'Créer')
}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
)}
<Megaphone className="h-4 w-4 text-muted-foreground" />
</div>
</CardHeader>
<CardContent>
{announcements.length === 0 ? (
<div className="text-center py-6">
<Megaphone className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
<p className="text-sm text-muted-foreground">
Aucune information disponible
</p>
<>
<Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
<CardHeader className="pb-4 border-b border-gray-100">
<div className="flex items-center justify-between">
<CardTitle className="text-lg font-semibold text-gray-800 flex items-center">
<Megaphone className="h-5 w-5 mr-3 text-blue-600" />
Informations
</CardTitle>
{user?.role === 'admin' && (
<Button
onClick={() => {
setEditingAnnouncement(null);
form.reset({
title: '',
content: '',
type: 'info',
storeId: selectedStoreId ? parseInt(selectedStoreId.toString()) : null,
createdBy: user?.username || '',
});
setIsCreateDialogOpen(true);
}}
size="sm"
className="bg-blue-600 hover:bg-blue-700 text-white"
>
<Plus className="h-4 w-4 mr-2" />
Nouvelle
</Button>
)}
</div>
) : (
<div className="space-y-3">
{announcements.map((announcement) => {
</CardHeader>
<CardContent className="space-y-3 p-6">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
) : !Array.isArray(announcements) || announcements.length === 0 ? (
<div className="text-center py-8">
<p className="text-gray-600">Aucune information disponible</p>
<p className="text-xs text-gray-400 mt-1">
{user?.role === 'admin' ? 'Créez la première annonce' : 'Aucune annonce pour le moment'}
</p>
</div>
) : (
announcements.map((announcement) => {
const priorityConfig = getPriorityConfig(announcement.type);
const TypeIcon = getTypeIcon(announcement.type);
return (
<div key={announcement.id} className="flex items-start justify-between p-3 rounded-lg border bg-card hover:bg-accent/50 transition-colors">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-medium text-sm truncate">
{announcement.title}
</h4>
<Badge variant={priorityConfig.color} className="flex items-center gap-1 text-xs">
<TypeIcon className="w-2.5 h-2.5" />
{priorityConfig.label}
</Badge>
</div>
{announcement.content && (
<p className="text-xs text-muted-foreground mb-1 line-clamp-1">
{announcement.content}
</p>
)}
<div className="flex items-center gap-3 text-xs text-muted-foreground">
<span>
{safeFormat(safeDate(announcement.createdAt), 'dd/MM à HH:mm')}
</span>
<span>
{announcement.author.firstName} {announcement.author.lastName}
<div key={announcement.id} className="flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors border-l-3 border-blue-500">
<div className="flex items-center space-x-3">
<div className="h-2 w-2 bg-blue-500"></div>
<div>
<p className="font-medium text-gray-900">{announcement.title}</p>
{announcement.content && (
<p className="text-sm text-gray-600 truncate max-w-60">{announcement.content}</p>
)}
<p className="text-xs text-gray-500 mt-1">
{announcement.author?.firstName} {announcement.author?.lastName}
{announcement.group && ` • ${announcement.group.name}`}
</span>
</p>
</div>
</div>
{user?.role === 'admin' && (
<div className="flex items-center gap-1 ml-2 flex-shrink-0">
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-muted-foreground hover:text-blue-600"
onClick={() => {
setEditingAnnouncement(announcement);
setIsCreateDialogOpen(true);
}}
>
<Edit2 className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive"
onClick={() => deleteMutation.mutate(announcement.id)}
disabled={deleteMutation.isPending}
>
<X className="h-3 w-3" />
</Button>
<div className="text-right flex items-center space-x-2">
<div>
<Badge className={`text-xs ${priorityConfig.color}`}>
{priorityConfig.label}
</Badge>
<p className="text-xs text-gray-500 mt-1">
{safeFormat(safeDate(announcement.createdAt), 'dd/MM à HH:mm')}
</p>
</div>
)}
{user?.role === 'admin' && (
<div className="flex items-center gap-1 ml-2 flex-shrink-0">
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-muted-foreground hover:text-blue-600"
onClick={() => handleEdit(announcement)}
>
<Edit2 className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive"
onClick={() => deleteMutation.mutate(announcement.id)}
disabled={deleteMutation.isPending}
>
<X className="h-3 w-3" />
</Button>
</div>
)}
</div>
</div>
);
})}
</div>
)}
</CardContent>
</Card>
})
)}
</CardContent>
</Card>
{/* Dialog pour créer/modifier une annonce */}
<Dialog open={isCreateDialogOpen} onOpenChange={(open) => {
setIsCreateDialogOpen(open);
if (!open) {
setEditingAnnouncement(null);
form.reset();
}
}}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>
{editingAnnouncement ? 'Modifier l\'information' : 'Nouvelle information'}
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Titre</FormLabel>
<FormControl>
<Input placeholder="Titre de l'information" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormLabel>Contenu</FormLabel>
<FormControl>
<Textarea
placeholder="Contenu de l'information"
rows={4}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Type</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Sélectionner un type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="info">Information</SelectItem>
<SelectItem value="warning">Attention</SelectItem>
<SelectItem value="error">Erreur</SelectItem>
<SelectItem value="success">Succès</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end space-x-2 pt-4">
<Button
type="button"
variant="outline"
onClick={() => setIsCreateDialogOpen(false)}
>
Annuler
</Button>
<Button
type="submit"
disabled={createMutation.isPending}
>
{createMutation.isPending
? (editingAnnouncement ? 'Modification...' : 'Création...')
: (editingAnnouncement ? 'Modifier' : 'Créer')
}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
</>
);
}
@@ -33,7 +33,7 @@ export default function RecentTasksCard() {
queryFn: () => {
const params = new URLSearchParams();
if (selectedStoreId) {
params.append('storeId', selectedStoreId);
params.append('storeId', selectedStoreId.toString());
}
return fetch(`/api/tasks?${params.toString()}`).then(res => res.json());
},
+1 -1
View File
@@ -2,4 +2,4 @@
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 1755334812 connect.sid s%3AIUsQcAbYjJ2Yu9GRwhXwQt22rGvvMGRo.zTBgX64O5zq1nh16Yc0umuKKQnBAy0xAn4BkA5iErOQ
#HttpOnly_localhost FALSE / FALSE 1755508518 connect.sid s%3A2H06I9RjaU2RHqAtgys18vROWYhhcdKw.StlcmCQB97ez7pxB4FWYVSv83LR9nxAxXRWu7do9v7E
+2 -5
View File
@@ -46,7 +46,7 @@ class AnnouncementDatabaseStorage implements IAnnouncementStorage {
const results = await query;
return results.map(result => ({
return results.map((result: any) => ({
id: result.id,
title: result.title,
content: result.content,
@@ -249,15 +249,12 @@ class AnnouncementMemoryStorage implements IAnnouncementStorage {
return announcementWithRelations;
}
async deleteAnnouncement(id: number): Promise<boolean> {
const existed = this.announcements.has(id);
async deleteAnnouncement(id: number): Promise<void> {
const deleted = this.announcements.delete(id);
if (deleted) {
console.log(`🗑️ Annonce supprimée: ID ${id} (${this.announcements.size}/${this.MAX_ANNOUNCEMENTS} restantes)`);
}
return deleted;
}
}
+6 -5
View File
@@ -3043,18 +3043,19 @@ RÉSUMÉ DU SCAN
const announcements = await Promise.all(
messages.map(async (message) => {
// Récupérer l'auteur
let author = { id: parseInt(message.createdBy) || 0, firstName: 'Utilisateur', lastName: 'Inconnu' };
let author = { id: message.createdBy, firstName: 'Utilisateur', lastName: 'Inconnu' };
try {
const [userResult] = await db.select().from(users).where(eq(users.id, message.createdBy));
// En production, createdBy est varchar, donc chercher par username
const [userResult] = await db.select().from(users).where(eq(users.username, message.createdBy));
if (userResult) {
author = {
id: parseInt(message.createdBy),
id: userResult.id,
firstName: userResult.firstName || 'Utilisateur',
lastName: userResult.lastName || 'Inconnu'
};
}
} catch (e) {
console.warn('Could not fetch author for message:', message.id);
console.warn('❌ [PRODUCTION] Could not fetch author for message:', message.id, e);
}
// Récupérer le groupe si storeId est défini
@@ -3152,7 +3153,7 @@ RÉSUMÉ DU SCAN
content: announcementData.content,
type: announcementData.type,
storeId: announcementData.storeId,
createdBy: user.id.toString(),
createdBy: user.username, // Utiliser username plutôt que ID pour PostgreSQL
}).returning();
const announcement = {