Merge pull request #348 from R0m1k3/1.1

Update dashboard to display yearly delivery statistics
This commit is contained in:
LogiFlow authored and GitHub committed 2025-09-03 17:01:28 +02:00
commit 71a7c717f3
4 files changed
+209 -7

No files matched your search

+1 -1
View File
@@ -37,4 +37,4 @@ args = "npm run dev"
waitForPort = 5000
[agent]
integrations = ["javascript_log_in_with_replit==1.0.0", "javascript_object_storage==1.0.0", "javascript_database==1.0.0"]
integrations = ["javascript_database==1.0.0", "javascript_log_in_with_replit==1.0.0", "javascript_object_storage==1.0.0"]
+5 -5
View File
@@ -14,8 +14,8 @@ export default function StatsPanel({ currentDate = new Date() }: StatsPanelProps
const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1;
// Construire l'URL avec les paramètres
const statsUrl = `/api/stats/monthly?year=${year}&month=${month}${selectedStoreId ? `&storeId=${selectedStoreId}` : ''}`;
// Construire l'URL pour les statistiques annuelles
const statsUrl = `/api/stats/yearly?year=${year}${selectedStoreId ? `&storeId=${selectedStoreId}` : ''}`;
const { data: stats, isLoading } = useQuery({
queryKey: [statsUrl, selectedStoreId], // Include selectedStoreId in key to force refetch
@@ -25,7 +25,7 @@ export default function StatsPanel({ currentDate = new Date() }: StatsPanelProps
});
if (!response.ok) {
throw new Error('Failed to fetch stats');
throw new Error('Failed to fetch yearly stats');
}
return response.json();
@@ -55,7 +55,7 @@ export default function StatsPanel({ currentDate = new Date() }: StatsPanelProps
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center">
<TrendingUp className="w-5 h-5 text-accent mr-2" />
Statistiques du mois
Statistiques de l'année
</CardTitle>
</CardHeader>
<CardContent>
@@ -91,7 +91,7 @@ export default function StatsPanel({ currentDate = new Date() }: StatsPanelProps
<div className="mt-3 pt-3 border-t border-gray-200 text-center">
<div className="text-sm text-gray-600 flex items-center justify-center">
<Clock className="w-4 h-4 mr-1" />
Délai moyen:
Délai commande → livraison:
<span className="font-medium text-gray-900 ml-1">
{stats?.averageDeliveryTime?.toFixed(1) || '0'} jours
</span>
+36
View File
@@ -2337,6 +2337,42 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
// Route pour les statistiques annuelles
app.get('/api/stats/yearly', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
const { year, storeId } = req.query;
const currentYear = year ? parseInt(year as string) : new Date().getFullYear();
let groupIds: number[] | undefined;
if (user.role === 'admin') {
// Admin can view all stores or filter by selected store
groupIds = storeId ? [parseInt(storeId as string)] : undefined;
} else {
// Non-admin users: filter by their assigned groups
const userGroupIds = user.userGroups.map(ug => ug.groupId);
// If a specific store is selected and user has access, filter by it
if (storeId && userGroupIds.includes(parseInt(storeId as string))) {
groupIds = [parseInt(storeId as string)];
} else {
groupIds = userGroupIds;
}
}
const stats = await storage.getYearlyStats(currentYear, groupIds);
res.json(stats);
} catch (error) {
console.error("Error fetching yearly stats:", error);
res.status(500).json({ message: "Failed to fetch yearly statistics" });
}
});
// User-Group management routes (admin only)
app.post('/api/users/:userId/groups', isAuthenticated, async (req: any, res) => {
try {
+167 -1
View File
@@ -131,6 +131,14 @@ export interface IStorage {
totalPalettes: number;
totalPackages: number;
}>;
getYearlyStats(year: number, groupIds?: number[]): Promise<{
ordersCount: number;
deliveriesCount: number;
pendingOrdersCount: number;
averageDeliveryTime: number;
totalPalettes: number;
totalPackages: number;
}>;
// Publicity operations
getPublicities(year?: number, groupIds?: number[]): Promise<PublicityWithRelations[]>;
@@ -1206,6 +1214,144 @@ export class DatabaseStorage implements IStorage {
}
}
// Statistics annuelles
async getYearlyStats(year: number, groupIds?: number[]): Promise<{
ordersCount: number;
deliveriesCount: number;
pendingOrdersCount: number;
averageDeliveryTime: number;
totalPalettes: number;
totalPackages: number;
}> {
// En mode développement avec MemStorage, retourner des statistiques simulées
if (process.env.NODE_ENV === 'development') {
console.log('📊 Mode développement - statistiques annuelles simulées');
return {
ordersCount: 144, // 12 mois * 12
deliveriesCount: 96, // 12 mois * 8
pendingOrdersCount: 18, // 12 mois * 1.5
averageDeliveryTime: 2.8, // Délai moyen annuel
totalPalettes: 540, // 12 mois * 45
totalPackages: 1872, // 12 mois * 156
};
}
// En production, calculer les vraies statistiques sur l'année
const startDate = `${year}-01-01`;
const endDate = `${year + 1}-01-01`;
try {
console.log('📊 Calcul statistiques annuelles:', { year, startDate, endDate, groupIds });
// Construire les conditions pour le filtrage par groupe
let ordersWhereCondition = and(
gte(orders.plannedDate, startDate),
lt(orders.plannedDate, endDate)
);
let deliveriesWhereCondition = and(
gte(deliveries.deliveredDate, sql`${startDate}::timestamp`),
lt(deliveries.deliveredDate, sql`${endDate}::timestamp`)
);
// Ajouter le filtre par groupe si spécifié
if (groupIds && groupIds.length > 0) {
ordersWhereCondition = and(ordersWhereCondition, inArray(orders.groupId, groupIds));
deliveriesWhereCondition = and(deliveriesWhereCondition, inArray(deliveries.groupId, groupIds));
}
// Compter les commandes de l'année
const ordersResult = await db
.select({ count: sql<number>`count(*)` })
.from(orders)
.where(ordersWhereCondition);
// Compter les livraisons de l'année
const deliveriesResult = await db
.select({ count: sql<number>`count(*)` })
.from(deliveries)
.where(deliveriesWhereCondition);
// Commandes en attente (sans date de livraison ou pas encore livrées)
let pendingWhereCondition = eq(orders.status, 'pending');
if (groupIds && groupIds.length > 0) {
pendingWhereCondition = and(pendingWhereCondition, inArray(orders.groupId, groupIds)) as any;
}
const pendingResult = await db
.select({ count: sql<number>`count(*)` })
.from(orders)
.where(pendingWhereCondition);
// Calculer les totaux de palettes et colis de l'année (SEULEMENT livraisons delivered)
let deliveredWhereCondition = and(
gte(deliveries.deliveredDate, sql`${startDate}::timestamp`),
lt(deliveries.deliveredDate, sql`${endDate}::timestamp`),
eq(deliveries.status, 'delivered'),
isNotNull(deliveries.deliveredDate)
);
// Ajouter le filtre par groupe pour les livraisons delivered
if (groupIds && groupIds.length > 0) {
deliveredWhereCondition = and(deliveredWhereCondition, inArray(deliveries.groupId, groupIds));
}
// NOUVEAU: Calculer le délai moyen entre la date de commande et la date de livraison
// Uniquement pour les livraisons qui ont une commande liée
const deliveriesStatsResult = await db
.select({
totalPalettes: sql<number>`COALESCE(SUM(CAST(${deliveries.quantity} as INTEGER)), 0)`,
totalPackages: sql<number>`COALESCE(COUNT(*), 0)`,
avgDelay: sql<number>`COALESCE(AVG(EXTRACT(DAY FROM (${deliveries.deliveredDate} - ${orders.plannedDate}))), 0)`
})
.from(deliveries)
.innerJoin(orders, eq(deliveries.orderId, orders.id))
.where(
and(
deliveredWhereCondition,
isNotNull(deliveries.orderId), // S'assurer qu'il y a une commande liée
isNotNull(orders.plannedDate) // S'assurer que la commande a une date
)
);
const ordersCount = Number(ordersResult[0]?.count || 0);
const deliveriesCount = Number(deliveriesResult[0]?.count || 0);
const pendingOrdersCount = Number(pendingResult[0]?.count || 0);
const totalPalettes = Number(deliveriesStatsResult[0]?.totalPalettes || 0);
const totalPackages = Number(deliveriesStatsResult[0]?.totalPackages || 0);
const averageDeliveryTime = Number(deliveriesStatsResult[0]?.avgDelay || 0);
console.log('📊 Statistiques annuelles calculées:', {
ordersCount,
deliveriesCount,
pendingOrdersCount,
averageDeliveryTime: `${averageDeliveryTime} jours (commande → livraison, livraisons avec commande liée uniquement)`,
totalPalettes,
totalPackages,
});
return {
ordersCount,
deliveriesCount,
pendingOrdersCount,
averageDeliveryTime,
totalPalettes,
totalPackages,
};
} catch (error) {
console.error('❌ Erreur calcul statistiques annuelles:', error);
// Retourner des zéros en cas d'erreur pour éviter un crash
return {
ordersCount: 0,
deliveriesCount: 0,
pendingOrdersCount: 0,
averageDeliveryTime: 0,
totalPalettes: 0,
totalPackages: 0,
};
}
}
// Publicity operations
async getPublicities(year?: number, groupIds?: number[]): Promise<PublicityWithRelations[]> {
let query = db
@@ -2907,7 +3053,27 @@ export class MemStorage implements IStorage {
async assignUserToGroup(): Promise<UserGroup> { return {} as UserGroup; }
async removeUserFromGroup(): Promise<void> {}
async getMonthlyStats(): Promise<any> { return {}; }
async getMonthlyStats(): Promise<any> {
return {
ordersCount: 12,
deliveriesCount: 8,
pendingOrdersCount: 3,
averageDeliveryTime: 2.5,
totalPalettes: 45,
totalPackages: 156,
};
}
async getYearlyStats(): Promise<any> {
return {
ordersCount: 144, // 12 mois * 12
deliveriesCount: 96, // 12 mois * 8
pendingOrdersCount: 18, // 12 mois * 1.5
averageDeliveryTime: 2.8, // Délai moyen annuel
totalPalettes: 540, // 12 mois * 45
totalPackages: 1872, // 12 mois * 156
};
}
async getPublicities(year?: number, groupIds?: number[]): Promise<PublicityWithRelations[]> {
let publicities = Array.from(this.publicities.values());