feat: Add publicities management page with year/month filtering, list/calendar views, and CRUD functionality.

This commit is contained in:
Michael committed 2025-12-10 12:06:14 +01:00
1 parent cca262230e
commit 1df477927a
1 file changed
+256 -207
+256 -207
View File
@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Plus, Calendar, Edit, Trash2, Eye, Filter, Grid, List } from "lucide-react"; import { Plus, Calendar, Edit, Trash2, Eye, Filter, Grid, List, Download } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@@ -25,12 +25,12 @@ export default function Publicities() {
const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false);
const [selectedPublicity, setSelectedPublicity] = useState<PublicityWithRelations | null>(null); const [selectedPublicity, setSelectedPublicity] = useState<PublicityWithRelations | null>(null);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { user } = useAuthUnified(); const { user } = useAuthUnified();
const { selectedStoreId } = useStore(); const { selectedStoreId } = useStore();
const { toast } = useToast(); const { toast } = useToast();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// Seuls les admins peuvent modifier/supprimer // Seuls les admins peuvent modifier/supprimer
const canModify = user?.role === 'admin'; const canModify = user?.role === 'admin';
@@ -52,36 +52,36 @@ export default function Publicities() {
} }
const data = await response.json(); const data = await response.json();
console.log('📢 Publicities received:', data.length, 'items', data.slice(0, 2)); console.log('📢 Publicities received:', data.length, 'items', data.slice(0, 2));
// CRITICAL FIX: Force frontend sorting by pubNumber to ensure correct order // CRITICAL FIX: Force frontend sorting by pubNumber to ensure correct order
const sortedData = Array.isArray(data) const sortedData = Array.isArray(data)
? data.sort((a, b) => { ? data.sort((a, b) => {
const numA = parseInt(a.pubNumber) || 0; const numA = parseInt(a.pubNumber) || 0;
const numB = parseInt(b.pubNumber) || 0; const numB = parseInt(b.pubNumber) || 0;
return numA - numB; return numA - numB;
}) })
: []; : [];
console.log('📢 Publicities after frontend sort:', { console.log('📢 Publicities after frontend sort:', {
first5: sortedData.slice(0, 5).map(p => `N°${p.pubNumber}`), first5: sortedData.slice(0, 5).map(p => `N°${p.pubNumber}`),
total: sortedData.length total: sortedData.length
}); });
return sortedData; return sortedData;
}, },
}); });
console.log('📢 Publicities Debug:', { console.log('📢 Publicities Debug:', {
isLoading, isLoading,
publicitiesCount: publicities?.length, publicitiesCount: publicities?.length,
selectedYear, selectedYear,
selectedStoreId, selectedStoreId,
publicities: publicities?.slice(0, 2) publicities: publicities?.slice(0, 2)
}); });
console.log('📢 Publicities Debug:', { console.log('📢 Publicities Debug:', {
isLoading, isLoading,
publicitiesCount: publicities?.length, publicitiesCount: publicities?.length,
selectedYear, selectedYear,
selectedStoreId, selectedStoreId,
publicities: publicities?.slice(0, 2) publicities: publicities?.slice(0, 2)
@@ -96,14 +96,14 @@ export default function Publicities() {
mutationFn: async (id: number) => { mutationFn: async (id: number) => {
// Try POST first (for production environments that block DELETE) // Try POST first (for production environments that block DELETE)
console.log(`🗑️ [Frontend] Attempting to delete publicity ${id} using POST method`); console.log(`🗑️ [Frontend] Attempting to delete publicity ${id} using POST method`);
try { try {
const response = await fetch(`/api/publicities/${id}/delete`, { const response = await fetch(`/api/publicities/${id}/delete`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}) body: JSON.stringify({})
}); });
if (response.ok) { if (response.ok) {
console.log(`✅ [Frontend] Successfully deleted publicity ${id} using POST`); console.log(`✅ [Frontend] Successfully deleted publicity ${id} using POST`);
return response.json(); return response.json();
@@ -111,12 +111,12 @@ export default function Publicities() {
// Route not found, try DELETE method as fallback (for dev environment) // Route not found, try DELETE method as fallback (for dev environment)
console.log(`⚠️ [Frontend] POST route not found, trying DELETE method`); console.log(`⚠️ [Frontend] POST route not found, trying DELETE method`);
const deleteResponse = await fetch(`/api/publicities/${id}`, { method: 'DELETE' }); const deleteResponse = await fetch(`/api/publicities/${id}`, { method: 'DELETE' });
if (!deleteResponse.ok) { if (!deleteResponse.ok) {
const error = await deleteResponse.json(); const error = await deleteResponse.json();
throw new Error(error.message || 'Erreur lors de la suppression'); throw new Error(error.message || 'Erreur lors de la suppression');
} }
// Handle empty response (204 No Content) // Handle empty response (204 No Content)
if (deleteResponse.status === 204) { if (deleteResponse.status === 204) {
return { message: "Publicity deleted successfully" }; return { message: "Publicity deleted successfully" };
@@ -138,9 +138,9 @@ export default function Publicities() {
setSelectedPublicity(null); setSelectedPublicity(null);
}, },
onError: (error: Error) => { onError: (error: Error) => {
toast({ toast({
variant: "destructive", variant: "destructive",
description: error.message || "Erreur lors de la suppression" description: error.message || "Erreur lors de la suppression"
}); });
} }
}); });
@@ -165,7 +165,7 @@ export default function Publicities() {
const weekPublicities = publicities.filter(pub => { const weekPublicities = publicities.filter(pub => {
const pubStart = safeDate(pub.startDate); const pubStart = safeDate(pub.startDate);
const pubEnd = safeDate(pub.endDate); const pubEnd = safeDate(pub.endDate);
// More robust overlap detection: check if any part of the publicity period overlaps with the week // More robust overlap detection: check if any part of the publicity period overlaps with the week
return pubStart && pubEnd && (pubStart <= weekEnd && pubEnd >= weekStart); return pubStart && pubEnd && (pubStart <= weekEnd && pubEnd >= weekStart);
}); });
@@ -194,7 +194,7 @@ export default function Publicities() {
const getYearWeeks = () => { const getYearWeeks = () => {
const yearStart = startOfYear(new Date(selectedYear, 0, 1)); const yearStart = startOfYear(new Date(selectedYear, 0, 1));
const yearEnd = endOfYear(new Date(selectedYear, 11, 31)); const yearEnd = endOfYear(new Date(selectedYear, 11, 31));
const weeks = eachWeekOfInterval( const weeks = eachWeekOfInterval(
{ start: yearStart, end: yearEnd }, { start: yearStart, end: yearEnd },
{ weekStartsOn: 1 } { weekStartsOn: 1 }
@@ -203,18 +203,18 @@ export default function Publicities() {
return weeks.map((weekStart, index) => { return weeks.map((weekStart, index) => {
const weekEnd = endOfWeek(weekStart, { weekStartsOn: 1 }); const weekEnd = endOfWeek(weekStart, { weekStartsOn: 1 });
const weekNumber = index + 1; const weekNumber = index + 1;
const weekMidpoint = new Date(weekStart.getTime() + (3.5 * 24 * 60 * 60 * 1000)); const weekMidpoint = new Date(weekStart.getTime() + (3.5 * 24 * 60 * 60 * 1000));
let month = getMonth(weekMidpoint); let month = getMonth(weekMidpoint);
if (weekStart.getFullYear() < selectedYear && weekEnd.getFullYear() === selectedYear) { if (weekStart.getFullYear() < selectedYear && weekEnd.getFullYear() === selectedYear) {
month = 0; // Janvier month = 0; // Janvier
} else if (weekStart.getFullYear() === selectedYear && weekEnd.getFullYear() > selectedYear) { } else if (weekStart.getFullYear() === selectedYear && weekEnd.getFullYear() > selectedYear) {
month = 11; // Décembre month = 11; // Décembre
} }
const participation = getWeekParticipation(weekStart, weekEnd); const participation = getWeekParticipation(weekStart, weekEnd);
return { return {
weekStart, weekStart,
weekEnd, weekEnd,
@@ -256,6 +256,49 @@ export default function Publicities() {
setSelectedPublicity(null); setSelectedPublicity(null);
}; };
// Export CSV function for current year data
const exportToCSV = () => {
if (!publicities || publicities.length === 0) {
toast({ variant: "destructive", description: "Aucune donnée à exporter" });
return;
}
const headers = ["N° PUB", "Désignation", "Date Début", "Date Fin", "Année", "Statut", "Magasins", "Créé le"];
const rows = publicities.map(pub => {
const now = new Date();
const start = safeDate(pub.startDate);
const end = safeDate(pub.endDate);
const status = start && end && now >= start && now <= end ? "En cours"
: start && start > now ? "À venir"
: "Terminée";
const stores = pub.participations.map(p => p.group?.name || "").filter(Boolean).join(", ");
return [
pub.pubNumber,
`"${(pub.designation || "").replace(/"/g, '""')}"`,
safeFormat(pub.startDate, "dd/MM/yyyy"),
safeFormat(pub.endDate, "dd/MM/yyyy"),
pub.year,
status,
`"${stores}"`,
safeFormat(pub.createdAt, "dd/MM/yyyy HH:mm")
].join(";");
});
// UTF-8 BOM for Excel compatibility with French accents
const csvContent = "\uFEFF" + headers.join(";") + "\n" + rows.join("\n");
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `publicites_${selectedYear}.csv`;
link.click();
URL.revokeObjectURL(url);
toast({ description: `Export CSV de ${publicities.length} publicités réussi` });
};
const canCreateOrEdit = user?.role === 'admin' || user?.role === 'manager'; const canCreateOrEdit = user?.role === 'admin' || user?.role === 'manager';
const canDelete = user?.role === 'admin'; const canDelete = user?.role === 'admin';
@@ -275,82 +318,88 @@ export default function Publicities() {
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* View Mode Toggle */} {/* View Mode Toggle */}
<div className="flex items-center bg-gray-100 rounded-lg p-1"> <div className="flex items-center bg-gray-100 rounded-lg p-1">
<Button <Button
variant={viewMode === 'list' ? 'default' : 'ghost'} variant={viewMode === 'list' ? 'default' : 'ghost'}
size="sm"
onClick={() => setViewMode('list')}
className="h-8"
>
<List className="h-4 w-4" />
</Button>
<Button
variant={viewMode === 'calendar' ? 'default' : 'ghost'}
size="sm"
onClick={() => setViewMode('calendar')}
className="h-8"
>
<Grid className="h-4 w-4" />
</Button>
</div>
{/* Year Overview Toggle */}
<Button
variant={showYearOverview ? 'default' : 'outline'}
size="sm" size="sm"
onClick={() => setViewMode('list')} onClick={() => setShowYearOverview(!showYearOverview)}
className="h-8" className="h-8"
> >
<List className="h-4 w-4" /> <Calendar className="h-4 w-4 mr-2" />
Vue d'ensemble
</Button> </Button>
<Button
variant={viewMode === 'calendar' ? 'default' : 'ghost'} {/* Year Filter */}
size="sm" <div className="flex items-center gap-2">
onClick={() => setViewMode('calendar')} <Filter className="h-4 w-4 text-gray-500" />
className="h-8" <Select
> value={selectedYear.toString()}
<Grid className="h-4 w-4" /> onValueChange={(value) => setSelectedYear(parseInt(value))}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
{yearOptions.map((year) => (
<SelectItem key={year} value={year.toString()}>
{year}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Month Filter (only for calendar view) */}
{viewMode === 'calendar' && (
<Select
value={selectedMonth.toString()}
onValueChange={(value) => setSelectedMonth(parseInt(value))}
>
<SelectTrigger className="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
{monthOptions.map((month) => (
<SelectItem key={month.value} value={month.value.toString()}>
{month.label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
{/* Export CSV Button */}
<Button variant="outline" onClick={exportToCSV} className="h-9">
<Download className="h-4 w-4 mr-2" />
Exporter CSV
</Button> </Button>
</div>
{/* Year Overview Toggle */} {canModify && (
<Button <Button onClick={() => setIsCreateModalOpen(true)} className="bg-green-600 hover:bg-green-700 text-white shadow-md">
variant={showYearOverview ? 'default' : 'outline'} <Plus className="mr-2 h-4 w-4" />
size="sm" Nouvelle publicité
onClick={() => setShowYearOverview(!showYearOverview)} </Button>
className="h-8" )}
>
<Calendar className="h-4 w-4 mr-2" />
Vue d'ensemble
</Button>
{/* Year Filter */}
<div className="flex items-center gap-2">
<Filter className="h-4 w-4 text-gray-500" />
<Select
value={selectedYear.toString()}
onValueChange={(value) => setSelectedYear(parseInt(value))}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
{yearOptions.map((year) => (
<SelectItem key={year} value={year.toString()}>
{year}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Month Filter (only for calendar view) */}
{viewMode === 'calendar' && (
<Select
value={selectedMonth.toString()}
onValueChange={(value) => setSelectedMonth(parseInt(value))}
>
<SelectTrigger className="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
{monthOptions.map((month) => (
<SelectItem key={month.value} value={month.value.toString()}>
{month.label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
{canModify && (
<Button onClick={() => setIsCreateModalOpen(true)} className="bg-green-600 hover:bg-green-700 text-white shadow-md">
<Plus className="mr-2 h-4 w-4" />
Nouvelle publicité
</Button>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -422,100 +471,100 @@ export default function Publicities() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> <div className="space-y-4">
{/* Months Grid */} {/* Months Grid */}
<div className="grid grid-cols-12 gap-2"> <div className="grid grid-cols-12 gap-2">
{Array.from({ length: 12 }, (_, monthIndex) => { {Array.from({ length: 12 }, (_, monthIndex) => {
const monthName = format(new Date(selectedYear, monthIndex, 1), 'MMM', { locale: fr }); const monthName = format(new Date(selectedYear, monthIndex, 1), 'MMM', { locale: fr });
const monthWeeks = yearWeeks.filter(week => week.month === monthIndex); const monthWeeks = yearWeeks.filter(week => week.month === monthIndex);
return ( return (
<div key={monthIndex} className="space-y-2"> <div key={monthIndex} className="space-y-2">
<div className="text-xs font-medium text-gray-600 text-center"> <div className="text-xs font-medium text-gray-600 text-center">
{monthName} {monthName}
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
{monthWeeks.map((week, weekIndex) => { {monthWeeks.map((week, weekIndex) => {
const hasPublicity = week.publicities.length > 0; const hasPublicity = week.publicities.length > 0;
const storeColors = week.storeColors; const storeColors = week.storeColors;
return ( return (
<div <div
key={`${monthIndex}-${weekIndex}-${week.weekNumber}`} key={`${monthIndex}-${weekIndex}-${week.weekNumber}`}
className={` className={`
h-8 rounded border text-xs flex flex-col items-center justify-center cursor-pointer relative h-8 rounded border text-xs flex flex-col items-center justify-center cursor-pointer relative
${hasPublicity ${hasPublicity
? 'bg-blue-50 border-blue-200 text-blue-800' ? 'bg-blue-50 border-blue-200 text-blue-800'
: 'bg-gray-50 border-gray-200 text-gray-500' : 'bg-gray-50 border-gray-200 text-gray-500'
} }
hover:shadow-sm transition-shadow hover:shadow-sm transition-shadow
`} `}
title={`Semaine ${week.weekNumber} (${format(week.weekStart, 'dd/MM', { locale: fr })} - ${format(week.weekEnd, 'dd/MM', { locale: fr })}) - ${hasPublicity ? `${week.publicities.map(p => p.pubNumber).join(', ')}` : 'Aucune publicité'}`} title={`Semaine ${week.weekNumber} (${format(week.weekStart, 'dd/MM', { locale: fr })} - ${format(week.weekEnd, 'dd/MM', { locale: fr })}) - ${hasPublicity ? `${week.publicities.map(p => p.pubNumber).join(', ')}` : 'Aucune publicité'}`}
> >
<span className="text-xs font-medium">{week.weekNumber}</span> <span className="text-xs font-medium">{week.weekNumber}</span>
{/* Store participation indicators */} {/* Store participation indicators */}
{storeColors.length > 0 && ( {storeColors.length > 0 && (
<div className="absolute top-0.5 right-0.5 flex flex-wrap gap-0.5 max-w-[16px]"> <div className="absolute top-0.5 right-0.5 flex flex-wrap gap-0.5 max-w-[16px]">
{storeColors.slice(0, 4).map((color, idx) => ( {storeColors.slice(0, 4).map((color, idx) => (
<div <div
key={idx} key={idx}
className="w-1.5 h-1.5 rounded-full" className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: color }} style={{ backgroundColor: color }}
title={`Magasin ${idx + 1}`} title={`Magasin ${idx + 1}`}
/> />
))} ))}
{storeColors.length > 4 && ( {storeColors.length > 4 && (
<div <div
className="w-1.5 h-1.5 rounded-full bg-gray-400 text-white text-xs flex items-center justify-center" className="w-1.5 h-1.5 rounded-full bg-gray-400 text-white text-xs flex items-center justify-center"
title={`+${storeColors.length - 4} autres magasins`} title={`+${storeColors.length - 4} autres magasins`}
> >
+ +
</div> </div>
)} )}
</div> </div>
)} )}
</div> </div>
); );
})} })}
</div>
</div>
);
})}
</div>
{/* Legend */}
<div className="space-y-2 pt-2 border-t">
<div className="flex items-center justify-between text-sm text-gray-600">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-blue-50 border border-blue-200 rounded"></div>
<span>Semaine avec publicité</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-gray-50 border border-gray-200 rounded"></div>
<span>Semaine sans publicité</span>
</div> </div>
</div> </div>
);
})}
</div>
{/* Legend */}
<div className="space-y-2 pt-2 border-t">
<div className="flex items-center justify-between text-sm text-gray-600">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-4 h-4 bg-blue-50 border border-blue-200 rounded"></div> <div className="flex gap-1">
<span>Semaine avec publicité</span> {groups.slice(0, 3).map(group => (
</div> <div
<div className="flex items-center gap-2"> key={group.id}
<div className="w-4 h-4 bg-gray-50 border border-gray-200 rounded"></div> className="w-2 h-2 rounded-full"
<span>Semaine sans publicité</span> style={{ backgroundColor: group?.color || '#666666' }}
/>
))}
</div>
<span>Indicateurs magasins (coin supérieur droit)</span>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-4 text-xs text-gray-500">
<div className="flex gap-1"> <span>• Survolez les semaines pour voir les détails des publicités</span>
{groups.slice(0, 3).map(group => (
<div
key={group.id}
className="w-2 h-2 rounded-full"
style={{ backgroundColor: group?.color || '#666666' }}
/>
))}
</div>
<span>Indicateurs magasins (coin supérieur droit)</span>
</div> </div>
</div> </div>
<div className="flex items-center gap-4 text-xs text-gray-500">
<span>• Survolez les semaines pour voir les détails des publicités</span>
</div>
</div> </div>
</div> </CardContent>
</CardContent> </Card>
</Card>
)} )}
{/* Content */} {/* Content */}
@@ -559,16 +608,16 @@ export default function Publicities() {
{day} {day}
</div> </div>
))} ))}
{/* Calendar days */} {/* Calendar days */}
{(() => { {(() => {
const monthStart = startOfMonth(new Date(selectedYear, selectedMonth, 1)); const monthStart = startOfMonth(new Date(selectedYear, selectedMonth, 1));
const monthEnd = endOfMonth(monthStart); const monthEnd = endOfMonth(monthStart);
const startDate = startOfWeek(monthStart, { weekStartsOn: 1 }); const startDate = startOfWeek(monthStart, { weekStartsOn: 1 });
const endDate = endOfWeek(monthEnd, { weekStartsOn: 1 }); const endDate = endOfWeek(monthEnd, { weekStartsOn: 1 });
const days = eachDayOfInterval({ start: startDate, end: endDate }); const days = eachDayOfInterval({ start: startDate, end: endDate });
return days.map(day => { return days.map(day => {
const isCurrentMonth = isSameMonth(day, monthStart); const isCurrentMonth = isSameMonth(day, monthStart);
const dayPublicities = publicities.filter(pub => { const dayPublicities = publicities.filter(pub => {
@@ -576,17 +625,17 @@ export default function Publicities() {
const pubEnd = safeDate(pub.endDate); const pubEnd = safeDate(pub.endDate);
return pubStart && pubEnd && day >= pubStart && day <= pubEnd; return pubStart && pubEnd && day >= pubStart && day <= pubEnd;
}); });
const participatingStores = new Set<number>(); const participatingStores = new Set<number>();
dayPublicities.forEach(pub => { dayPublicities.forEach(pub => {
pub.participations.forEach(participation => { pub.participations.forEach(participation => {
participatingStores.add(participation.groupId); participatingStores.add(participation.groupId);
}); });
}); });
return ( return (
<div <div
key={day.toISOString()} key={day.toISOString()}
className={` className={`
p-2 min-h-[60px] border border-gray-200 rounded-lg p-2 min-h-[60px] border border-gray-200 rounded-lg
${isCurrentMonth ? 'bg-white' : 'bg-gray-50'} ${isCurrentMonth ? 'bg-white' : 'bg-gray-50'}
@@ -599,8 +648,8 @@ export default function Publicities() {
{dayPublicities.length > 0 && ( {dayPublicities.length > 0 && (
<div className="space-y-1"> <div className="space-y-1">
{dayPublicities.slice(0, 2).map(pub => ( {dayPublicities.slice(0, 2).map(pub => (
<div <div
key={pub.id} key={pub.id}
className="text-xs bg-blue-100 text-blue-800 rounded px-1 py-0.5 truncate" className="text-xs bg-blue-100 text-blue-800 rounded px-1 py-0.5 truncate"
title={pub.designation} title={pub.designation}
> >
@@ -620,7 +669,7 @@ export default function Publicities() {
{Array.from(participatingStores).slice(0, 3).map(storeId => { {Array.from(participatingStores).slice(0, 3).map(storeId => {
const group = groups.find(g => g.id === storeId); const group = groups.find(g => g.id === storeId);
return ( return (
<div <div
key={storeId} key={storeId}
className="w-2 h-2 rounded-full" className="w-2 h-2 rounded-full"
style={{ backgroundColor: group?.color || '#666666' }} style={{ backgroundColor: group?.color || '#666666' }}
@@ -638,7 +687,7 @@ export default function Publicities() {
}); });
})()} })()}
</div> </div>
{/* Legend */} {/* Legend */}
<div className="mt-4 p-3 bg-gray-50 rounded-lg"> <div className="mt-4 p-3 bg-gray-50 rounded-lg">
<h4 className="text-sm font-medium text-gray-900 mb-2">Légende</h4> <h4 className="text-sm font-medium text-gray-900 mb-2">Légende</h4>
@@ -650,7 +699,7 @@ export default function Publicities() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex gap-1"> <div className="flex gap-1">
{groups.slice(0, 3).map(group => ( {groups.slice(0, 3).map(group => (
<div <div
key={group.id} key={group.id}
className="w-2 h-2 rounded-full" className="w-2 h-2 rounded-full"
style={{ backgroundColor: group?.color || '#666666' }} style={{ backgroundColor: group?.color || '#666666' }}
@@ -736,8 +785,8 @@ export default function Publicities() {
<> <>
{publicity.participations.slice(0, 2).map((participation) => ( {publicity.participations.slice(0, 2).map((participation) => (
<Badge key={participation.groupId} variant="outline" className="text-xs"> <Badge key={participation.groupId} variant="outline" className="text-xs">
<div <div
className="w-2 h-2 rounded-full mr-1" className="w-2 h-2 rounded-full mr-1"
style={{ backgroundColor: participation.group?.color || '#666666' }} style={{ backgroundColor: participation.group?.color || '#666666' }}
/> />
{participation.group?.name || 'Magasin'} {participation.group?.name || 'Magasin'}
@@ -767,7 +816,7 @@ export default function Publicities() {
> >
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</Button> </Button>
{canModify && ( {canModify && (
<> <>
<Button <Button
@@ -778,7 +827,7 @@ export default function Publicities() {
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -846,7 +895,7 @@ export default function Publicities() {
Informations complètes de la campagne publicitaire. Informations complètes de la campagne publicitaire.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{selectedPublicity && ( {selectedPublicity && (
<div className="space-y-6"> <div className="space-y-6">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
@@ -884,8 +933,8 @@ export default function Publicities() {
) : ( ) : (
selectedPublicity.participations.map((participation) => ( selectedPublicity.participations.map((participation) => (
<Badge key={participation.groupId} variant="outline"> <Badge key={participation.groupId} variant="outline">
<div <div
className="w-3 h-3 rounded-full mr-2" className="w-3 h-3 rounded-full mr-2"
style={{ backgroundColor: participation.group?.color || '#666666' }} style={{ backgroundColor: participation.group?.color || '#666666' }}
/> />
{participation.group?.name || 'Magasin'} {participation.group?.name || 'Magasin'}
@@ -919,20 +968,20 @@ export default function Publicities() {
Êtes-vous sûr de vouloir supprimer cette publicité ? Cette action est irréversible. Êtes-vous sûr de vouloir supprimer cette publicité ? Cette action est irréversible.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{selectedPublicity && ( {selectedPublicity && (
<div className="space-y-4"> <div className="space-y-4">
<div className="p-4 bg-gray-50 rounded-lg"> <div className="p-4 bg-gray-50 rounded-lg">
<p className="font-medium">{selectedPublicity.pubNumber}</p> <p className="font-medium">{selectedPublicity.pubNumber}</p>
<p className="text-sm text-gray-600">{selectedPublicity.designation}</p> <p className="text-sm text-gray-600">{selectedPublicity.designation}</p>
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setIsDeleteModalOpen(false)}> <Button variant="outline" onClick={() => setIsDeleteModalOpen(false)}>
Annuler Annuler
</Button> </Button>
<Button <Button
variant="destructive" variant="destructive"
onClick={confirmDelete} onClick={confirmDelete}
disabled={deleteMutation.isPending} disabled={deleteMutation.isPending}
> >