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
+288 -297

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+278 -285
View File
@@ -1,275 +1,351 @@
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 { useState } from "react";
import React from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
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 { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { insertAnnouncementSchema } from "@shared/schema"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import type { AnnouncementWithRelations } from "@shared/schema"; import { Button } from "@/components/ui/button";
import { z } from "zod"; 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 { safeFormat, safeDate } from "@/lib/dateUtils";
import {
insertAnnouncementSchema,
type InsertAnnouncement,
type DashboardMessage
} from "@shared/schema";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
// Schéma adapté pour DASHBOARD_MESSAGES // Type pour les annonces avec relations
const formSchema = z.object({ type AnnouncementWithRelations = DashboardMessage & {
title: z.string().min(1, "Le titre est obligatoire"), author: {
content: z.string().min(1, "Le contenu est obligatoire"), id: string | number;
type: z.enum(["info", "warning", "error", "success"]).default("info"), 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() { export default function AnnouncementCard() {
const { user } = useAuthUnified(); const { user } = useAuthUnified();
const { selectedStoreId } = useStore(); const { selectedStoreId } = useStore();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { toast } = useToast();
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [editingAnnouncement, setEditingAnnouncement] = useState<AnnouncementWithRelations | null>(null); const [editingAnnouncement, setEditingAnnouncement] = useState<AnnouncementWithRelations | null>(null);
const { toast } = useToast();
const form = useForm<z.infer<typeof formSchema>>({ // Configuration du formulaire
resolver: zodResolver(formSchema), const form = useForm<InsertAnnouncement>({
resolver: zodResolver(insertAnnouncementSchema),
defaultValues: { defaultValues: {
title: "", title: '',
content: "", content: '',
type: "info", type: 'info',
storeId: selectedStoreId ? parseInt(selectedStoreId.toString()) : null,
createdBy: user?.username || '',
}, },
}); });
// Reset form when editing announcement changes // Construire l'URL avec les paramètres appropriés
React.useEffect(() => { const buildApiUrl = () => {
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",
});
}
}, [editingAnnouncement, form]);
// Fetch announcements
const { data: announcements = [], isLoading } = useQuery<AnnouncementWithRelations[]>({
queryKey: ['/api/announcements', selectedStoreId],
queryFn: async () => {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (selectedStoreId && user?.role === 'admin') {
// Pour les admins, permettre le filtrage par magasin sélectionné
if (user?.role === 'admin' && selectedStoreId) {
params.append('storeId', selectedStoreId.toString()); params.append('storeId', selectedStoreId.toString());
} }
const response = await fetch(`/api/announcements?${params}`, { return `/api/announcements${params.toString() ? `?${params.toString()}` : ''}`;
};
// Récupérer les annonces
const { data: announcements = [], isLoading } = useQuery<AnnouncementWithRelations[]>({
queryKey: ['/api/announcements', selectedStoreId, user?.role],
queryFn: async () => {
const url = buildApiUrl();
console.log('🔍 [ANNOUNCEMENTS] Fetching from:', url);
const response = await fetch(url, {
credentials: 'include' credentials: 'include'
}); });
if (!response.ok) { 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, enabled: !!user,
}); });
// Mutation pour créer/modifier une annonce
// Create/Update announcement mutation
const createMutation = useMutation({ const createMutation = useMutation({
mutationFn: async (data: z.infer<typeof formSchema>) => { mutationFn: async (announcementData: InsertAnnouncement) => {
const isEditing = !!editingAnnouncement; const url = editingAnnouncement
console.log('📢 Submitting announcement:', { data, isEditing });
const url = isEditing
? `/api/announcements/${editingAnnouncement.id}` ? `/api/announcements/${editingAnnouncement.id}`
: '/api/announcements'; : '/api/announcements';
const method = isEditing ? 'PUT' : 'POST'; const method = editingAnnouncement ? 'PUT' : 'POST';
console.log(`🔍 [ANNOUNCEMENTS] ${method} ${url}`, announcementData);
const response = await fetch(url, { const response = await fetch(url, {
method, method,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
credentials: 'include', 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) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
console.error('📢 Announcement failed:', errorText); console.error(`❌ [ANNOUNCEMENTS] ${method} failed:`, response.status, errorText);
throw new Error(`Failed to ${isEditing ? 'update' : 'create'} announcement: ${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;
},
onSuccess: (data) => {
const isEditing = !!editingAnnouncement;
console.log('📢 Announcement success callback:', data);
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",
});
},
onError: (error) => {
const isEditing = !!editingAnnouncement;
console.error('📢 Announcement error:', error);
toast({
title: "Erreur",
description: error.message || `Impossible de ${isEditing ? 'modifier' : 'créer'} l'information`,
variant: "destructive",
});
},
});
// 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(); return response.json();
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/announcements'] }); queryClient.invalidateQueries({ queryKey: ['/api/announcements'] });
setIsCreateDialogOpen(false);
setEditingAnnouncement(null);
form.reset();
toast({
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) => {
console.error('❌ [ANNOUNCEMENTS] Mutation error:', error);
toast({
title: "Erreur",
description: `Impossible de ${editingAnnouncement ? 'modifier' : 'créer'} l'information.`,
variant: "destructive",
});
}, },
}); });
const onSubmit = (data: z.infer<typeof formSchema>) => { // Mutation pour supprimer une annonce
console.log('🔥 [FORM] onSubmit called with data:', data); const deleteMutation = useMutation({
console.log('🔥 [FORM] Form errors:', form.formState.errors); mutationFn: async (announcementId: number) => {
console.log('🔥 [FORM] Form is valid:', form.formState.isValid); const response = await fetch(`/api/announcements/${announcementId}`, {
console.log('🔥 [FORM] Form is submitting:', form.formState.isSubmitting); method: 'DELETE',
credentials: 'include',
});
if (!data.title || !data.content) { if (!response.ok) {
console.error('🔥 [FORM] Missing required fields:', { title: data.title, content: data.content }); 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({ toast({
title: "Erreur", title: "Erreur",
description: "Le titre et le contenu sont obligatoires", description: "Impossible de supprimer l'information.",
variant: "destructive", variant: "destructive",
}); });
return; },
} });
console.log('🔥 [FORM] Calling createMutation.mutate'); // Soumission du formulaire
createMutation.mutate(data); const onSubmit = (data: InsertAnnouncement) => {
console.log('🔍 [ANNOUNCEMENTS] Form submitted:', data);
const announcementData = {
...data,
createdBy: user?.username || '',
storeId: data.storeId || null
}; };
const getPriorityIcon = (priority: string) => { createMutation.mutate(announcementData);
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) => { // Ouvrir le dialogue pour éditer
switch (priority) { const handleEdit = (announcement: AnnouncementWithRelations) => {
case 'urgent': setEditingAnnouncement(announcement);
return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; form.reset({
case 'important': title: announcement.title,
return 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200'; content: announcement.content,
default: type: announcement.type as any,
return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'; storeId: announcement.storeId,
} createdBy: announcement.createdBy,
});
setIsCreateDialogOpen(true);
}; };
if (isLoading) {
return ( return (
<Card> <>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
<CardTitle className="text-sm font-medium">Informations</CardTitle> <CardHeader className="pb-4 border-b border-gray-100">
<Megaphone className="h-4 w-4 text-muted-foreground" /> <div className="flex items-center justify-between">
</CardHeader> <CardTitle className="text-lg font-semibold text-gray-800 flex items-center">
<CardContent> <Megaphone className="h-5 w-5 mr-3 text-blue-600" />
<div className="flex items-center justify-center h-32"> Informations
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div> </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>
</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);
return (
<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}`}
</p>
</div>
</div>
<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>
);
})
)}
</CardContent> </CardContent>
</Card> </Card>
);
}
const getPriorityConfig = (type: string) => { {/* Dialog pour créer/modifier une annonce */}
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;
}
};
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) => { <Dialog open={isCreateDialogOpen} onOpenChange={(open) => {
setIsCreateDialogOpen(open); setIsCreateDialogOpen(open);
if (!open) { if (!open) {
@@ -277,11 +353,6 @@ export default function AnnouncementCard() {
form.reset(); 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]"> <DialogContent className="sm:max-w-[500px]">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
@@ -355,7 +426,6 @@ export default function AnnouncementCard() {
<Button <Button
type="submit" type="submit"
disabled={createMutation.isPending} disabled={createMutation.isPending}
onClick={() => console.log('🔥 [BUTTON] Submit button clicked')}
> >
{createMutation.isPending {createMutation.isPending
? (editingAnnouncement ? 'Modification...' : 'Création...') ? (editingAnnouncement ? 'Modification...' : 'Création...')
@@ -367,83 +437,6 @@ export default function AnnouncementCard() {
</Form> </Form>
</DialogContent> </DialogContent>
</Dialog> </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>
</div>
) : (
<div className="space-y-3">
{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}
{announcement.group && ` • ${announcement.group.name}`}
</span>
</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>
)}
</div>
);
})}
</div>
)}
</CardContent>
</Card>
); );
} }
@@ -33,7 +33,7 @@ export default function RecentTasksCard() {
queryFn: () => { queryFn: () => {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (selectedStoreId) { if (selectedStoreId) {
params.append('storeId', selectedStoreId); params.append('storeId', selectedStoreId.toString());
} }
return fetch(`/api/tasks?${params.toString()}`).then(res => res.json()); 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 # https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk. # 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; const results = await query;
return results.map(result => ({ return results.map((result: any) => ({
id: result.id, id: result.id,
title: result.title, title: result.title,
content: result.content, content: result.content,
@@ -249,15 +249,12 @@ class AnnouncementMemoryStorage implements IAnnouncementStorage {
return announcementWithRelations; return announcementWithRelations;
} }
async deleteAnnouncement(id: number): Promise<boolean> { async deleteAnnouncement(id: number): Promise<void> {
const existed = this.announcements.has(id);
const deleted = this.announcements.delete(id); const deleted = this.announcements.delete(id);
if (deleted) { if (deleted) {
console.log(`🗑️ Annonce supprimée: ID ${id} (${this.announcements.size}/${this.MAX_ANNOUNCEMENTS} restantes)`); 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( const announcements = await Promise.all(
messages.map(async (message) => { messages.map(async (message) => {
// Récupérer l'auteur // 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 { 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) { if (userResult) {
author = { author = {
id: parseInt(message.createdBy), id: userResult.id,
firstName: userResult.firstName || 'Utilisateur', firstName: userResult.firstName || 'Utilisateur',
lastName: userResult.lastName || 'Inconnu' lastName: userResult.lastName || 'Inconnu'
}; };
} }
} catch (e) { } 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 // Récupérer le groupe si storeId est défini
@@ -3152,7 +3153,7 @@ RÉSUMÉ DU SCAN
content: announcementData.content, content: announcementData.content,
type: announcementData.type, type: announcementData.type,
storeId: announcementData.storeId, storeId: announcementData.storeId,
createdBy: user.id.toString(), createdBy: user.username, // Utiliser username plutôt que ID pour PostgreSQL
}).returning(); }).returning();
const announcement = { const announcement = {