mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Merge pull request #551 from R0m1k3/1.1
feat: Implement new dashboard, publicity management, and calendar pag…
This commit is contained in:
10 files changed
+696
-630
No files matched your search
@@ -51,8 +51,8 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe
|
||||
defaultValues: {
|
||||
pubNumber: publicity?.pubNumber || "",
|
||||
designation: publicity?.designation || "",
|
||||
startDate: publicity?.startDate ? safeDate(publicity.startDate) : undefined,
|
||||
endDate: publicity?.endDate ? safeDate(publicity.endDate) : undefined,
|
||||
startDate: (publicity?.startDate ? safeDate(publicity.startDate) : undefined) as Date,
|
||||
endDate: (publicity?.endDate ? safeDate(publicity.endDate) : undefined) as Date,
|
||||
year: publicity?.year || selectedYear || 2025, // Utilise l'année sélectionnée ou 2025 par défaut
|
||||
participatingGroups: publicity?.participations?.map(p => p.groupId) || [],
|
||||
},
|
||||
@@ -62,7 +62,7 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: async (data: PublicityFormData) => {
|
||||
const response = await fetch('/api/publicities', {
|
||||
const response = await fetch('/api/ad-campaigns', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -81,21 +81,21 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe
|
||||
return response.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/publicities'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/ad-campaigns'] });
|
||||
toast({ description: "Publicité créée avec succès" });
|
||||
onSuccess();
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast({
|
||||
toast({
|
||||
variant: "destructive",
|
||||
description: error.message || "Erreur lors de la création"
|
||||
description: error.message || "Erreur lors de la création"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: async (data: PublicityFormData) => {
|
||||
const response = await fetch(`/api/publicities/${publicity!.id}`, {
|
||||
const response = await fetch(`/api/ad-campaigns/${publicity!.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -114,23 +114,31 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe
|
||||
return response.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/publicities'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/ad-campaigns'] });
|
||||
toast({ description: "Publicité modifiée avec succès" });
|
||||
onSuccess();
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast({
|
||||
toast({
|
||||
variant: "destructive",
|
||||
description: error.message || "Erreur lors de la modification"
|
||||
description: error.message || "Erreur lors de la modification"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmit = (data: PublicityFormData) => {
|
||||
const onSubmit = (data: any) => {
|
||||
// Transformer l'année en nombre si c'est une string
|
||||
const formattedData = {
|
||||
...data,
|
||||
year: parseInt(data.year.toString()),
|
||||
// s'assurer que les dates sont bien des objets Date ou null
|
||||
startDate: data.startDate ? new Date(data.startDate) : undefined,
|
||||
endDate: data.endDate ? new Date(data.endDate) : undefined,
|
||||
};
|
||||
if (publicity) {
|
||||
updateMutation.mutate(data);
|
||||
updateMutation.mutate(formattedData);
|
||||
} else {
|
||||
createMutation.mutate(data);
|
||||
createMutation.mutate(formattedData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -284,9 +292,9 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe
|
||||
htmlFor={`group-${group.id}`}
|
||||
className="flex items-center space-x-2 cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: group.color }}
|
||||
<div
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: group.color || '#cccccc' }}
|
||||
/>
|
||||
<span>{group.name}</span>
|
||||
</Label>
|
||||
|
||||
@@ -35,9 +35,9 @@ export default function Calendar() {
|
||||
|
||||
// Fetch orders and deliveries for the current month with store filtering
|
||||
const { data: orders = [], isLoading: loadingOrders } = useQuery({
|
||||
queryKey: ['/api/orders', selectedStoreId, {
|
||||
startDate: format(monthStart, 'yyyy-MM-dd'),
|
||||
endDate: format(monthEnd, 'yyyy-MM-dd')
|
||||
queryKey: ['/api/orders', selectedStoreId, {
|
||||
startDate: format(monthStart, 'yyyy-MM-dd'),
|
||||
endDate: format(monthEnd, 'yyyy-MM-dd')
|
||||
}],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams({
|
||||
@@ -48,7 +48,7 @@ export default function Calendar() {
|
||||
if (selectedStoreId) {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
|
||||
|
||||
const url = `/api/orders?${params.toString()}`;
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('📅 Calendar fetching orders:', {
|
||||
@@ -58,20 +58,20 @@ export default function Calendar() {
|
||||
params: params.toString()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(url, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch orders');
|
||||
}
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('📅 Calendar orders received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', 'items');
|
||||
}
|
||||
|
||||
|
||||
// Protection contre les données invalides en production
|
||||
if (!Array.isArray(data)) {
|
||||
console.warn('⚠️ Orders data is not an array, returning empty array');
|
||||
@@ -84,9 +84,9 @@ export default function Calendar() {
|
||||
});
|
||||
|
||||
const { data: deliveries = [], isLoading: loadingDeliveries } = useQuery({
|
||||
queryKey: ['/api/deliveries', selectedStoreId, {
|
||||
startDate: format(monthStart, 'yyyy-MM-dd'),
|
||||
endDate: format(monthEnd, 'yyyy-MM-dd')
|
||||
queryKey: ['/api/deliveries', selectedStoreId, {
|
||||
startDate: format(monthStart, 'yyyy-MM-dd'),
|
||||
endDate: format(monthEnd, 'yyyy-MM-dd')
|
||||
}],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams({
|
||||
@@ -97,7 +97,7 @@ export default function Calendar() {
|
||||
if (selectedStoreId) {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
|
||||
|
||||
const url = `/api/deliveries?${params.toString()}`;
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('📅 Calendar fetching deliveries:', {
|
||||
@@ -107,20 +107,20 @@ export default function Calendar() {
|
||||
params: params.toString()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(url, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch deliveries');
|
||||
}
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('📅 Calendar deliveries received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', 'items');
|
||||
}
|
||||
|
||||
|
||||
// Protection contre les données invalides en production
|
||||
if (!Array.isArray(data)) {
|
||||
console.warn('⚠️ Deliveries data is not an array, returning empty array');
|
||||
@@ -134,15 +134,15 @@ export default function Calendar() {
|
||||
|
||||
// Fetch publicities for the current year
|
||||
const { data: publicities = [], isLoading: loadingPublicities } = useQuery({
|
||||
queryKey: ['/api/publicities', currentDate.getFullYear(), selectedStoreId],
|
||||
queryKey: ['/api/ad-campaigns', currentDate.getFullYear(), selectedStoreId],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('year', currentDate.getFullYear().toString());
|
||||
if (selectedStoreId && user?.role === 'admin') {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
|
||||
const url = `/api/publicities?${params.toString()}`;
|
||||
|
||||
const url = `/api/ad-campaigns?${params.toString()}`;
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('📅 Calendar fetching publicities:', {
|
||||
url,
|
||||
@@ -151,15 +151,15 @@ export default function Calendar() {
|
||||
year: currentDate.getFullYear()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(url, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch publicities');
|
||||
}
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('📅 Calendar publicities received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', 'items');
|
||||
|
||||
@@ -33,19 +33,19 @@ export default function Dashboard() {
|
||||
year: currentDate.getFullYear().toString(),
|
||||
month: (currentDate.getMonth() + 1).toString(),
|
||||
});
|
||||
|
||||
|
||||
if (selectedStoreId && user?.role === 'admin') {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(`/api/stats/monthly?${params.toString()}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch stats');
|
||||
}
|
||||
|
||||
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
@@ -58,19 +58,19 @@ export default function Dashboard() {
|
||||
const params = new URLSearchParams({
|
||||
year: currentDate.getFullYear().toString(),
|
||||
});
|
||||
|
||||
|
||||
if (selectedStoreId && user?.role === 'admin') {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(`/api/stats/yearly?${params.toString()}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch yearly stats');
|
||||
}
|
||||
|
||||
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
@@ -102,34 +102,34 @@ export default function Dashboard() {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
params.append('recent', 'true');
|
||||
|
||||
|
||||
const response = await fetch(`/api/announcements?${params.toString()}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const announcements: DashboardMessage[] = await response.json();
|
||||
|
||||
|
||||
// Filtrer les annonces créées il y a moins de 2 jours côté client
|
||||
const twoDaysAgo = new Date();
|
||||
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
|
||||
|
||||
|
||||
const recentAnnouncements = announcements.filter(announcement => {
|
||||
if (!announcement.createdAt) return false;
|
||||
const createdAt = safeDate(announcement.createdAt);
|
||||
return createdAt && createdAt >= twoDaysAgo;
|
||||
});
|
||||
|
||||
|
||||
// Retourner la plus récente
|
||||
return recentAnnouncements.length > 0
|
||||
return recentAnnouncements.length > 0
|
||||
? recentAnnouncements.sort((a, b) => {
|
||||
const dateA = safeDate(a.createdAt);
|
||||
const dateB = safeDate(b.createdAt);
|
||||
return (dateB?.getTime() || 0) - (dateA?.getTime() || 0);
|
||||
})[0]
|
||||
const dateA = safeDate(a.createdAt);
|
||||
const dateB = safeDate(b.createdAt);
|
||||
return (dateB?.getTime() || 0) - (dateA?.getTime() || 0);
|
||||
})[0]
|
||||
: null;
|
||||
},
|
||||
enabled: !!user,
|
||||
@@ -139,30 +139,30 @@ export default function Dashboard() {
|
||||
const getAnnouncementPriorityConfig = (type: string) => {
|
||||
switch (type) {
|
||||
case 'error':
|
||||
return {
|
||||
label: 'Nouveauté',
|
||||
return {
|
||||
label: 'Nouveauté',
|
||||
color: 'bg-purple-100 text-purple-800',
|
||||
icon: Sparkles,
|
||||
iconColor: 'text-purple-600'
|
||||
};
|
||||
case 'warning':
|
||||
return {
|
||||
label: 'Attention',
|
||||
return {
|
||||
label: 'Attention',
|
||||
color: 'bg-orange-100 text-orange-800',
|
||||
icon: AlertTriangle,
|
||||
iconColor: 'text-orange-600'
|
||||
};
|
||||
case 'success':
|
||||
return {
|
||||
label: 'Important',
|
||||
return {
|
||||
label: 'Important',
|
||||
color: 'bg-red-100 text-red-800',
|
||||
icon: Star,
|
||||
iconColor: 'text-red-600'
|
||||
};
|
||||
case 'info':
|
||||
default:
|
||||
return {
|
||||
label: 'Information',
|
||||
return {
|
||||
label: 'Information',
|
||||
color: 'bg-blue-100 text-blue-800',
|
||||
icon: Info,
|
||||
iconColor: 'text-blue-600'
|
||||
@@ -195,19 +195,19 @@ export default function Dashboard() {
|
||||
|
||||
// Récupérer les publicités à venir (chercher dans 2024 ET 2025) - TOUTES les publicités
|
||||
const { data: upcomingPublicities = [] } = useQuery<PublicityWithRelations[]>({
|
||||
queryKey: ['/api/publicities', 'upcoming'],
|
||||
queryKey: ['/api/ad-campaigns', 'upcoming'],
|
||||
queryFn: async () => {
|
||||
// Essayer d'abord 2024, puis 2025 pour avoir toutes les publicités
|
||||
const years = [2024, 2025];
|
||||
let allPublicities: PublicityWithRelations[] = [];
|
||||
|
||||
|
||||
for (const year of years) {
|
||||
const params = new URLSearchParams();
|
||||
params.append('year', year.toString());
|
||||
// NE PAS filtrer par magasin - on veut toutes les publicités
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/publicities?${params}`, { credentials: 'include' });
|
||||
const response = await fetch(`/api/ad-campaigns?${params}`, { credentials: 'include' });
|
||||
if (response.ok) {
|
||||
const yearPublicities = await response.json();
|
||||
allPublicities = [...allPublicities, ...yearPublicities];
|
||||
@@ -216,12 +216,12 @@ export default function Dashboard() {
|
||||
console.log(`Erreur lors de la récupération des publicités ${year}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Filtrer les publicités à venir et les trier par date
|
||||
const futurePublicities = allPublicities
|
||||
.filter((publicity: any) => new Date(publicity.startDate) > new Date())
|
||||
.sort((a: any, b: any) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime());
|
||||
|
||||
|
||||
return futurePublicities;
|
||||
},
|
||||
});
|
||||
@@ -249,10 +249,10 @@ export default function Dashboard() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Vérifier s'il y a des problèmes DLC
|
||||
const shouldShowDlcModal = dlcStats.expired > 0 || dlcStats.expiringSoon > 0;
|
||||
|
||||
|
||||
if (shouldShowDlcModal && !showDlcAlertModal) {
|
||||
// Vérifier s'il n'y a pas de snooze actif
|
||||
const snoozedUntil = localStorage.getItem('dlcAlertSnooze');
|
||||
@@ -305,7 +305,7 @@ export default function Dashboard() {
|
||||
const dateB = safeDate(b.createdAt);
|
||||
return (dateB ? dateB.getTime() : 0) - (dateA ? dateA.getTime() : 0);
|
||||
}) : []; // Afficher toutes les commandes en attente
|
||||
|
||||
|
||||
const upcomingDeliveries = Array.isArray(allDeliveries) ? allDeliveries
|
||||
.filter((d: any) => {
|
||||
const isPlanned = d.status === 'planned';
|
||||
@@ -314,7 +314,7 @@ export default function Dashboard() {
|
||||
})
|
||||
.sort((a: any, b: any) => new Date(a.scheduledDate).getTime() - new Date(b.scheduledDate).getTime())
|
||||
.slice(0, 4) : []; // Afficher les 4 prochaines livraisons
|
||||
|
||||
|
||||
console.log('🚚 Dashboard - Upcoming deliveries result:', upcomingDeliveries.length, upcomingDeliveries);
|
||||
|
||||
// Calculs pour les statistiques
|
||||
@@ -324,9 +324,9 @@ export default function Dashboard() {
|
||||
const deliveryDate = safeDate(delivery.deliveredDate || delivery.createdAt);
|
||||
if (!deliveryDate) return false;
|
||||
const now = new Date();
|
||||
return deliveryDate.getMonth() === now.getMonth() &&
|
||||
deliveryDate.getFullYear() === now.getFullYear() &&
|
||||
delivery.status === 'delivered';
|
||||
return deliveryDate.getMonth() === now.getMonth() &&
|
||||
deliveryDate.getFullYear() === now.getFullYear() &&
|
||||
delivery.status === 'delivered';
|
||||
}).length : 0;
|
||||
|
||||
// Calculer le total des palettes pour les livraisons 'delivered' du mois en cours
|
||||
@@ -346,35 +346,35 @@ export default function Dashboard() {
|
||||
const getPriorityConfig = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'high':
|
||||
return {
|
||||
color: 'destructive' as const,
|
||||
icon: AlertTriangle,
|
||||
label: 'Élevée'
|
||||
return {
|
||||
color: 'destructive' as const,
|
||||
icon: AlertTriangle,
|
||||
label: 'Élevée'
|
||||
};
|
||||
case 'medium':
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: Clock,
|
||||
label: 'Moyenne'
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: Clock,
|
||||
label: 'Moyenne'
|
||||
};
|
||||
case 'low':
|
||||
return {
|
||||
color: 'secondary' as const,
|
||||
icon: Circle,
|
||||
label: 'Faible'
|
||||
return {
|
||||
color: 'secondary' as const,
|
||||
icon: Circle,
|
||||
label: 'Faible'
|
||||
};
|
||||
default:
|
||||
return {
|
||||
color: 'secondary' as const,
|
||||
icon: Circle,
|
||||
label: 'Moyenne'
|
||||
return {
|
||||
color: 'secondary' as const,
|
||||
icon: Circle,
|
||||
label: 'Moyenne'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
console.log('📊 Dashboard Debug - Raw Data:', {
|
||||
allOrders: Array.isArray(allOrders) ? allOrders.length : 'NOT_ARRAY',
|
||||
allDeliveries: Array.isArray(allDeliveries) ? allDeliveries.length : 'NOT_ARRAY',
|
||||
allDeliveries: Array.isArray(allDeliveries) ? allDeliveries.length : 'NOT_ARRAY',
|
||||
customerOrders: Array.isArray(customerOrders) ? customerOrders.length : 'NOT_ARRAY',
|
||||
upcomingPublicities: Array.isArray(upcomingPublicities) ? upcomingPublicities.length : 'NOT_ARRAY',
|
||||
stats: stats,
|
||||
@@ -423,7 +423,7 @@ export default function Dashboard() {
|
||||
|
||||
{/* Alerts */}
|
||||
<div className="space-y-3">
|
||||
|
||||
|
||||
{dlcStats.expiringSoon > 0 && (
|
||||
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 flex items-center space-x-3 shadow-sm">
|
||||
<Clock className="h-5 w-5 text-yellow-600" />
|
||||
@@ -432,7 +432,7 @@ export default function Dashboard() {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{dlcStats.expired > 0 && (
|
||||
<div className="bg-red-50 border-l-4 border-red-400 p-4 flex items-center space-x-3 shadow-sm">
|
||||
<XCircle className="h-5 w-5 text-red-600" />
|
||||
@@ -523,11 +523,10 @@ export default function Dashboard() {
|
||||
const orderDate = safeDate(order.plannedDate);
|
||||
const daysPending = orderDate ? Math.floor((new Date().getTime() - orderDate.getTime()) / (1000 * 60 * 60 * 24)) : 0;
|
||||
const isOverdue = daysPending > 10;
|
||||
|
||||
|
||||
return (
|
||||
<div key={order.id} className={`flex items-center justify-between p-4 transition-colors border-l-3 ${
|
||||
isOverdue ? 'bg-red-50 hover:bg-red-100 border-red-500' : 'bg-orange-50 hover:bg-orange-100 border-orange-500'
|
||||
}`}>
|
||||
<div key={order.id} className={`flex items-center justify-between p-4 transition-colors border-l-3 ${isOverdue ? 'bg-red-50 hover:bg-red-100 border-red-500' : 'bg-orange-50 hover:bg-orange-100 border-orange-500'
|
||||
}`}>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`h-2 w-2 ${isOverdue ? 'bg-red-500' : 'bg-orange-500'}`}></div>
|
||||
<div>
|
||||
@@ -536,7 +535,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Badge
|
||||
<Badge
|
||||
variant={isOverdue ? 'destructive' : 'secondary'}
|
||||
className="text-xs"
|
||||
>
|
||||
@@ -604,7 +603,7 @@ export default function Dashboard() {
|
||||
.map((publicity: any) => {
|
||||
const participatingStores = publicity.participations || [];
|
||||
const isCurrentStoreParticipating = selectedStoreId && participatingStores.some((p: any) => p.groupId === parseInt(selectedStoreId.toString()));
|
||||
|
||||
|
||||
return (
|
||||
<div key={publicity.id} className="p-4 bg-gray-50 hover:bg-gray-100 transition-colors border-l-3 border-purple-500 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -624,7 +623,7 @@ export default function Dashboard() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Magasins participants */}
|
||||
<div className="flex items-center space-x-2 text-xs">
|
||||
<span className="text-gray-500">Magasins:</span>
|
||||
@@ -635,12 +634,12 @@ export default function Dashboard() {
|
||||
{participatingStores.map((participation: any) => {
|
||||
const isCurrentStore = selectedStoreId && participation.groupId === parseInt(selectedStoreId.toString());
|
||||
const groupColor = participation.group?.color || '#1976D2';
|
||||
|
||||
|
||||
return (
|
||||
<Badge
|
||||
key={participation.groupId}
|
||||
<Badge
|
||||
key={participation.groupId}
|
||||
className={`text-xs text-white border ${isCurrentStore ? 'ring-2 ring-white ring-offset-1' : ''}`}
|
||||
style={{
|
||||
style={{
|
||||
backgroundColor: groupColor,
|
||||
borderColor: groupColor
|
||||
}}
|
||||
@@ -690,7 +689,7 @@ export default function Dashboard() {
|
||||
.map((task: any) => {
|
||||
const priorityConfig = getPriorityConfig(task.priority);
|
||||
const PriorityIcon = priorityConfig.icon;
|
||||
|
||||
|
||||
return (
|
||||
<div key={task.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">
|
||||
@@ -702,7 +701,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="text-right space-y-1">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Badge
|
||||
<Badge
|
||||
className={`text-xs flex items-center gap-1 ${priorityConfig.color}`}
|
||||
>
|
||||
<PriorityIcon className="h-3 w-3" />
|
||||
@@ -716,11 +715,11 @@ export default function Dashboard() {
|
||||
</div>
|
||||
);
|
||||
}) : (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-600">Aucune tâche en cours</p>
|
||||
<p className="text-xs text-gray-400 mt-1">Toutes les tâches sont terminées</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-600">Aucune tâche en cours</p>
|
||||
<p className="text-xs text-gray-400 mt-1">Toutes les tâches sont terminées</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -734,7 +733,7 @@ export default function Dashboard() {
|
||||
Nouvelle Annonce
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
{recentAnnouncement && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -756,7 +755,7 @@ export default function Dashboard() {
|
||||
{safeFormat(recentAnnouncement.createdAt, "d MMMM yyyy 'à' HH:mm")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
{recentAnnouncement.title}
|
||||
@@ -767,7 +766,7 @@ export default function Dashboard() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{recentAnnouncement.createdBy && (
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-gray-100">
|
||||
<User className="h-4 w-4 text-gray-400" />
|
||||
@@ -778,7 +777,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button onClick={handleCloseAnnouncementModal} className="flex items-center gap-2">
|
||||
<X className="h-4 w-4" />
|
||||
@@ -793,7 +792,7 @@ export default function Dashboard() {
|
||||
isOpen={showDlcAlertModal}
|
||||
onClose={handleCloseDlcAlertModal}
|
||||
dlcStats={dlcStats}
|
||||
selectedStoreId={selectedStoreId}
|
||||
selectedStoreId={selectedStoreId ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -39,15 +39,15 @@ export default function Publicities() {
|
||||
const startYear = currentYear - 2;
|
||||
const yearOptions = Array.from({ length: 13 }, (_, i) => startYear + i);
|
||||
|
||||
const { data: publicities = [], isLoading } = useQuery({
|
||||
queryKey: ['/api/publicities', selectedYear, selectedStoreId],
|
||||
const { data: publicities = [], isLoading } = useQuery<PublicityWithRelations[]>({
|
||||
queryKey: ['/api/ad-campaigns', selectedYear, selectedStoreId],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('year', selectedYear.toString());
|
||||
if (selectedStoreId) {
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
}
|
||||
const response = await fetch(`/api/publicities?${params}`);
|
||||
const response = await fetch(`/api/ad-campaigns?${params}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch publicities');
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export default function Publicities() {
|
||||
console.log(`🗑️ [Frontend] Attempting to delete publicity ${id} using POST method`);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/publicities/${id}/delete`, {
|
||||
const response = await fetch(`/api/ad-campaigns/${id}/delete`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({})
|
||||
@@ -111,7 +111,7 @@ export default function Publicities() {
|
||||
} else if (response.status === 404) {
|
||||
// Route not found, try DELETE method as fallback (for dev environment)
|
||||
console.log(`⚠️ [Frontend] POST route not found, trying DELETE method`);
|
||||
const deleteResponse = await fetch(`/api/publicities/${id}`, { method: 'DELETE' });
|
||||
const deleteResponse = await fetch(`/api/ad-campaigns/${id}`, { method: 'DELETE' });
|
||||
|
||||
if (!deleteResponse.ok) {
|
||||
const error = await deleteResponse.json();
|
||||
@@ -133,7 +133,7 @@ export default function Publicities() {
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/publicities'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/ad-campaigns'] });
|
||||
toast({ description: "Publicité supprimée avec succès" });
|
||||
setIsDeleteModalOpen(false);
|
||||
setSelectedPublicity(null);
|
||||
|
||||
@@ -34,16 +34,17 @@ export default function MobilePublicitiesPage() {
|
||||
const [selectedYear, setSelectedYear] = useState<number>(new Date().getFullYear());
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
// Générer les années pour le select (année courante +/- 2 ans)
|
||||
// Générer les années pour le select (année courante - 2 ans à + 10 ans)
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = Array.from({ length: 5 }, (_, i) => currentYear - 2 + i);
|
||||
const startYear = currentYear - 2;
|
||||
const years = Array.from({ length: 13 }, (_, i) => startYear + i);
|
||||
|
||||
const { data: publicities = [], isLoading } = useQuery({
|
||||
queryKey: ["/api/publicities", selectedStoreId, selectedYear],
|
||||
queryKey: ["/api/ad-campaigns", selectedStoreId, selectedYear],
|
||||
queryFn: async () => {
|
||||
if (!selectedStoreId) return [];
|
||||
const response = await fetch(
|
||||
`/api/publicities?storeId=${selectedStoreId}&year=${selectedYear}`,
|
||||
`/api/ad-campaigns?storeId=${selectedStoreId}&year=${selectedYear}`,
|
||||
{ credentials: 'include' }
|
||||
);
|
||||
if (!response.ok) throw new Error("Failed to fetch publicities");
|
||||
|
||||
Generated
+21
-12
@@ -45,7 +45,6 @@
|
||||
"@types/form-data": "^2.2.1",
|
||||
"@types/memoizee": "^0.4.12",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/pg": "^8.15.4",
|
||||
"bcrypt": "^6.0.0",
|
||||
"busboy": "^1.6.0",
|
||||
@@ -102,13 +101,15 @@
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"@types/connect-pg-simple": "^7.0.3",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/date-fns": "^2.5.3",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/node": "20.16.11",
|
||||
"@types/node": "^20.16.11",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/passport": "^1.0.16",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react": "^18.3.27",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
@@ -3720,6 +3721,13 @@
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/date-fns": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/date-fns/-/date-fns-2.5.3.tgz",
|
||||
"integrity": "sha512-4KVPD3g5RjSgZtdOjvI/TDFkLNUHhdoWxmierdQbDeEg17Rov0hbBYtIzNaQA67ORpteOhvR9YEMTb6xeDCang==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -3810,6 +3818,7 @@
|
||||
"version": "2.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
|
||||
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
@@ -3880,14 +3889,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||
"version": "18.3.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
@@ -4600,9 +4609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d": {
|
||||
|
||||
+6
-5
@@ -47,7 +47,6 @@
|
||||
"@types/form-data": "^2.2.1",
|
||||
"@types/memoizee": "^0.4.12",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/pg": "^8.15.4",
|
||||
"bcrypt": "^6.0.0",
|
||||
"busboy": "^1.6.0",
|
||||
@@ -104,13 +103,15 @@
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"@types/connect-pg-simple": "^7.0.3",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/date-fns": "^2.5.3",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/node": "20.16.11",
|
||||
"@types/node": "^20.16.11",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/passport": "^1.0.16",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react": "^18.3.27",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
|
||||
+473
-463
File diff suppressed because it is too large.
Load diff
@@ -1,25 +1,38 @@
|
||||
# Task: Redirection authentification si déconnecté
|
||||
# Task: Correction blocage API Publicités
|
||||
|
||||
## Context
|
||||
|
||||
L'utilisateur signale que lorsqu'il est déconnecté et navigue ou se trouve sur une page, il n'est pas systématiquement renvoyé vers la page d'authentification. Il souhaite une redirection immédiate.
|
||||
L'utilisateur rencontre une erreur `net::ERR_BLOCKED_BY_CLIENT` lors de l'appel à l'API `/api/publicities`. Cette erreur est typique des bloqueurs de publicité (AdBlock, uBlock) qui filtrent les requêtes contenant des mots-clés comme "publicity" ou "ads".
|
||||
|
||||
## Current Focus
|
||||
|
||||
Vérification terminée.
|
||||
Renommer les endpoints API pour éviter le blocage par les bloqueurs de publicité.
|
||||
|
||||
## Master Plan
|
||||
|
||||
- [x] Analyser le fichier principal de routing (ex: `App.tsx` ou `main.tsx`).
|
||||
- [x] Examiner le hook d'authentification `useAuthUnified` et les composants de protection de route (ex: `ProtectedRoute`).
|
||||
- [x] Identifier pourquoi la redirection ne se fait pas ou pas correctement.
|
||||
- [x] Implémenter/Corriger la logique de redirection vers `/auth` (ou la page de login).
|
||||
- [x] Vérifier le comportement.
|
||||
- [x] **Remplacement des API calls (Client-side)**
|
||||
- [x] `client/src/pages/Calendar.tsx`
|
||||
- [x] `client/src/pages/Dashboard.tsx`
|
||||
- [x] `client/src/components/PublicityForm.tsx`
|
||||
- [x] `client/src/pages/Publicities.tsx`
|
||||
- [x] `client/src/pages/mobile/PublicitiesPage.tsx`
|
||||
|
||||
- [x] **Revue du sélecteur d'année Mobile**
|
||||
- [x] Vérifier la logique de génération des années dans `client/src/pages/mobile/PublicitiesPage.tsx` pour inclure les années passées (ex: 2024 en 2026).
|
||||
- [x] Aligner avec la correction desktop si nécessaire.
|
||||
|
||||
- [x] **Correction des erreurs Lint TypeScript**
|
||||
- [x] Installer les paquets `@types` manquants (`node`, `express`, `react`, etc.).
|
||||
- [x] Fixer les paramètres `req` et `res` (implicitement `any`) dans `server/routes.ts`.
|
||||
- [x] Corriger l'erreur de propriété `invoiceAmountTTC` dans `server/routes.ts`.
|
||||
- [x] Résoudre `Property 'env' does not exist on type 'ImportMeta'`.
|
||||
- [x] Résoudre `JSX element implicitly has type 'any'`.
|
||||
|
||||
- [x] **Tests et Validation**
|
||||
- [x] Vérifier que les appels API utilisent bien `/api/ad-campaigns`.
|
||||
- [x] Tester le sélecteur d'année sur mobile.
|
||||
- [x] Compiler le projet (`npm run check`) pour s'assurer que les erreurs critiques sont résolues.
|
||||
|
||||
## Progress Log
|
||||
|
||||
- Création de la tâche.
|
||||
- Analyse de `App.tsx` et `useAuthUnified.ts`.
|
||||
- Identification que `RouterProduction.tsx` renvoyait `NotFound` au lieu de rediriger.
|
||||
- Modification de `RouterProduction.tsx` pour rediriger vers `/auth` toutes les routes inconnues si non authentifié.
|
||||
- Correction d'une erreur de remplacement de code en réécrivant le fichier complet.
|
||||
- Création de la tâche suite aux logs fournis.
|
||||
+32
-7
@@ -1,6 +1,15 @@
|
||||
{
|
||||
"include": ["client/src/**/*", "shared/**/*", "server/**/*"],
|
||||
"exclude": ["node_modules", "build", "dist", "**/*.test.ts"],
|
||||
"include": [
|
||||
"client/src/**/*",
|
||||
"shared/**/*",
|
||||
"server/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build",
|
||||
"dist",
|
||||
"**/*.test.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./node_modules/typescript/tsbuildinfo",
|
||||
@@ -8,7 +17,11 @@
|
||||
"target": "ES2020",
|
||||
"module": "ES2022",
|
||||
"strict": true,
|
||||
"lib": ["ES2020", "dom", "dom.iterable"],
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"jsx": "preserve",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
@@ -16,10 +29,22 @@
|
||||
"moduleResolution": "bundler",
|
||||
"downlevelIteration": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["node", "vite/client"],
|
||||
"types": [
|
||||
"node",
|
||||
"vite/client",
|
||||
"express",
|
||||
"react",
|
||||
"react-dom",
|
||||
"node-fetch",
|
||||
"date-fns"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./client/src/*"],
|
||||
"@shared/*": ["./shared/*"]
|
||||
"@/*": [
|
||||
"./client/src/*"
|
||||
],
|
||||
"@shared/*": [
|
||||
"./shared/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user