2026-01-22 18:53:23 +01:00
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
|
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
|
import { getCurrentUser } from '@/lib/auth';
|
|
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
// Types de période: day | 7days | 30days | month | quarter | year
|
|
|
|
|
// Ou plage personnalisée via from & to (format ISO)
|
|
|
|
|
function getDateRange(period: string | null, from: string | null, to: string | null): {
|
|
|
|
|
start: Date;
|
|
|
|
|
end: Date;
|
|
|
|
|
prevStart: Date;
|
|
|
|
|
prevEnd: Date;
|
|
|
|
|
periodLabel: string;
|
|
|
|
|
} {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const endOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
|
|
|
|
|
const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
|
|
|
|
|
|
|
|
if (from && to) {
|
|
|
|
|
const start = new Date(from);
|
|
|
|
|
const end = endOfDay(new Date(to));
|
|
|
|
|
const diff = end.getTime() - start.getTime();
|
|
|
|
|
const prevEnd = new Date(start.getTime() - 1);
|
|
|
|
|
const prevStart = new Date(prevEnd.getTime() - diff);
|
|
|
|
|
return {
|
|
|
|
|
start: startOfDay(start),
|
|
|
|
|
end,
|
|
|
|
|
prevStart: startOfDay(prevStart),
|
|
|
|
|
prevEnd: endOfDay(prevEnd),
|
|
|
|
|
periodLabel: `${start.toLocaleDateString('fr-FR')} - ${new Date(to).toLocaleDateString('fr-FR')}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (period) {
|
|
|
|
|
case 'day': {
|
|
|
|
|
const start = startOfDay(now);
|
|
|
|
|
const end = endOfDay(now);
|
|
|
|
|
const yesterday = new Date(now);
|
|
|
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
|
return {
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
prevStart: startOfDay(yesterday),
|
|
|
|
|
prevEnd: endOfDay(yesterday),
|
|
|
|
|
periodLabel: "Aujourd'hui",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case 'yesterday': {
|
|
|
|
|
const yesterday = new Date(now);
|
|
|
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
|
const start = startOfDay(yesterday);
|
|
|
|
|
const end = endOfDay(yesterday);
|
|
|
|
|
const dayBefore = new Date(yesterday);
|
|
|
|
|
dayBefore.setDate(dayBefore.getDate() - 1);
|
|
|
|
|
return {
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
prevStart: startOfDay(dayBefore),
|
|
|
|
|
prevEnd: endOfDay(dayBefore),
|
|
|
|
|
periodLabel: 'Hier',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case 'week': {
|
|
|
|
|
// Cette semaine : lundi à aujourd'hui (ISO week, lundi = 1)
|
|
|
|
|
const dayOfWeek = now.getDay();
|
|
|
|
|
const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
|
|
|
|
const startOfWeek = new Date(now);
|
|
|
|
|
startOfWeek.setDate(startOfWeek.getDate() + mondayOffset);
|
|
|
|
|
const start = startOfDay(startOfWeek);
|
|
|
|
|
const end = endOfDay(now);
|
|
|
|
|
const prevWeekStart = new Date(start);
|
|
|
|
|
prevWeekStart.setDate(prevWeekStart.getDate() - 7);
|
|
|
|
|
const prevWeekEnd = new Date(start);
|
|
|
|
|
prevWeekEnd.setDate(prevWeekEnd.getDate() - 1);
|
|
|
|
|
return {
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
prevStart: startOfDay(prevWeekStart),
|
|
|
|
|
prevEnd: endOfDay(prevWeekEnd),
|
|
|
|
|
periodLabel: 'Cette semaine',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case '7days': {
|
|
|
|
|
const start = new Date(now);
|
|
|
|
|
start.setDate(start.getDate() - 6);
|
|
|
|
|
const end = endOfDay(now);
|
|
|
|
|
const prevEnd = new Date(start.getTime() - 1);
|
|
|
|
|
const prevStart = new Date(prevEnd);
|
|
|
|
|
prevStart.setDate(prevStart.getDate() - 6);
|
|
|
|
|
return {
|
|
|
|
|
start: startOfDay(start),
|
|
|
|
|
end,
|
|
|
|
|
prevStart: startOfDay(prevStart),
|
|
|
|
|
prevEnd: endOfDay(prevEnd),
|
|
|
|
|
periodLabel: '7 derniers jours',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case '30days': {
|
|
|
|
|
const start = new Date(now);
|
|
|
|
|
start.setDate(start.getDate() - 29);
|
|
|
|
|
const end = endOfDay(now);
|
|
|
|
|
const prevEnd = new Date(start.getTime() - 1);
|
|
|
|
|
const prevStart = new Date(prevEnd);
|
|
|
|
|
prevStart.setDate(prevStart.getDate() - 29);
|
|
|
|
|
return {
|
|
|
|
|
start: startOfDay(start),
|
|
|
|
|
end,
|
|
|
|
|
prevStart: startOfDay(prevStart),
|
|
|
|
|
prevEnd: endOfDay(prevEnd),
|
|
|
|
|
periodLabel: '30 derniers jours',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case 'quarter': {
|
|
|
|
|
const q = Math.floor(now.getMonth() / 3) + 1;
|
|
|
|
|
const startOfQuarter = new Date(now.getFullYear(), (q - 1) * 3, 1);
|
|
|
|
|
const endOfQuarter = new Date(now.getFullYear(), q * 3, 0, 23, 59, 59, 999);
|
|
|
|
|
const prevQuarter = q === 1 ? 4 : q - 1;
|
|
|
|
|
const prevYear = q === 1 ? now.getFullYear() - 1 : now.getFullYear();
|
|
|
|
|
const prevStart = new Date(prevYear, (prevQuarter - 1) * 3, 1);
|
|
|
|
|
const prevEnd = new Date(prevYear, prevQuarter * 3, 0, 23, 59, 59, 999);
|
|
|
|
|
return {
|
|
|
|
|
start: startOfQuarter,
|
|
|
|
|
end: endOfQuarter,
|
|
|
|
|
prevStart: prevStart,
|
|
|
|
|
prevEnd: prevEnd,
|
|
|
|
|
periodLabel: `T${q} ${now.getFullYear()}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case 'year': {
|
|
|
|
|
const startOfYear = new Date(now.getFullYear(), 0, 1);
|
|
|
|
|
const endOfYear = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999);
|
|
|
|
|
const prevStart = new Date(now.getFullYear() - 1, 0, 1);
|
|
|
|
|
const prevEnd = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 999);
|
|
|
|
|
return {
|
|
|
|
|
start: startOfYear,
|
|
|
|
|
end: endOfYear,
|
|
|
|
|
prevStart: prevStart,
|
|
|
|
|
prevEnd: prevEnd,
|
|
|
|
|
periodLabel: `Année ${now.getFullYear()}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case 'month':
|
|
|
|
|
default: {
|
|
|
|
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
|
|
|
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
|
|
|
const startOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
|
|
|
const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
|
|
|
|
|
return {
|
|
|
|
|
start: startOfMonth,
|
|
|
|
|
end: endOfMonth,
|
|
|
|
|
prevStart: startOfLastMonth,
|
|
|
|
|
prevEnd: endOfLastMonth,
|
|
|
|
|
periodLabel: 'Mois en cours',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 18:53:23 +01:00
|
|
|
// GET - Récupérer les statistiques du dashboard
|
|
|
|
|
export async function GET(request: NextRequest) {
|
|
|
|
|
try {
|
|
|
|
|
const user = await getCurrentUser();
|
|
|
|
|
if (!user) {
|
|
|
|
|
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
const { searchParams } = new URL(request.url);
|
|
|
|
|
const period = searchParams.get('period') || 'month';
|
|
|
|
|
const from = searchParams.get('from');
|
|
|
|
|
const to = searchParams.get('to');
|
|
|
|
|
|
|
|
|
|
const { start, end, prevStart, prevEnd, periodLabel } = getDateRange(period, from, to);
|
|
|
|
|
|
|
|
|
|
// 1. Participations sur la période (trajets validés/terminés)
|
2026-02-15 14:36:28 +01:00
|
|
|
const participationsMoisData = await prisma.participationFinanciere.findMany({
|
2026-01-22 18:53:23 +01:00
|
|
|
where: {
|
2026-02-15 14:36:28 +01:00
|
|
|
trajet: {
|
|
|
|
|
date: {
|
2026-02-16 19:03:56 +01:00
|
|
|
gte: start,
|
|
|
|
|
lte: end,
|
2026-02-15 14:36:28 +01:00
|
|
|
},
|
2026-01-22 18:53:23 +01:00
|
|
|
},
|
|
|
|
|
},
|
2026-02-15 14:36:28 +01:00
|
|
|
include: { trajet: { select: { date: true } } },
|
2026-01-22 18:53:23 +01:00
|
|
|
});
|
|
|
|
|
|
2026-02-15 14:36:28 +01:00
|
|
|
const participationsCeMois = participationsMoisData;
|
|
|
|
|
const montantMoyenParTrajet = 6.80;
|
|
|
|
|
const participationsMois = participationsCeMois.reduce(
|
|
|
|
|
(sum, p) => sum + (p.montant ?? montantMoyenParTrajet),
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
const nombreFactures = participationsCeMois.length;
|
2026-01-22 18:53:23 +01:00
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
// 2. Trajets sur la période
|
|
|
|
|
const trajetsPeriode = await prisma.trajet.count({
|
2026-01-22 18:53:23 +01:00
|
|
|
where: {
|
|
|
|
|
archived: false,
|
|
|
|
|
date: {
|
2026-02-16 19:03:56 +01:00
|
|
|
gte: start,
|
|
|
|
|
lte: end,
|
2026-01-22 18:53:23 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
// Trajets période précédente pour comparaison
|
|
|
|
|
const trajetsPeriodePrecedente = await prisma.trajet.count({
|
2026-01-22 18:53:23 +01:00
|
|
|
where: {
|
|
|
|
|
archived: false,
|
|
|
|
|
date: {
|
2026-02-16 19:03:56 +01:00
|
|
|
gte: prevStart,
|
|
|
|
|
lte: prevEnd,
|
2026-01-22 18:53:23 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
const differenceTrajets = trajetsPeriode - trajetsPeriodePrecedente;
|
2026-01-22 18:53:23 +01:00
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
// 3. Trajets réalisés sur la période (terminés)
|
|
|
|
|
const trajetsRealisesPeriode = await prisma.trajet.count({
|
2026-01-22 18:53:23 +01:00
|
|
|
where: {
|
|
|
|
|
archived: false,
|
|
|
|
|
statut: 'Terminé',
|
|
|
|
|
date: {
|
2026-02-16 19:03:56 +01:00
|
|
|
gte: start,
|
|
|
|
|
lte: end,
|
2026-01-22 18:53:23 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
// Trajets réalisés période précédente pour comparaison
|
|
|
|
|
const trajetsRealisesPeriodePrecedente = await prisma.trajet.count({
|
2026-01-22 18:53:23 +01:00
|
|
|
where: {
|
|
|
|
|
archived: false,
|
|
|
|
|
statut: 'Terminé',
|
|
|
|
|
date: {
|
2026-02-16 19:03:56 +01:00
|
|
|
gte: prevStart,
|
|
|
|
|
lte: prevEnd,
|
2026-01-22 18:53:23 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 19:03:56 +01:00
|
|
|
const pourcentageEvolution = trajetsRealisesPeriodePrecedente > 0
|
|
|
|
|
? Math.round(((trajetsRealisesPeriode - trajetsRealisesPeriodePrecedente) / trajetsRealisesPeriodePrecedente) * 100)
|
|
|
|
|
: trajetsRealisesPeriode > 0 ? 100 : 0;
|
2026-01-22 18:53:23 +01:00
|
|
|
|
|
|
|
|
// 4. Chauffeurs actifs (disponibles)
|
|
|
|
|
const totalChauffeurs = await prisma.chauffeur.count();
|
|
|
|
|
const chauffeursActifs = await prisma.chauffeur.count({
|
|
|
|
|
where: {
|
|
|
|
|
status: 'Disponible',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
2026-02-16 19:03:56 +01:00
|
|
|
periodLabel,
|
2026-01-22 18:53:23 +01:00
|
|
|
participationsMois: {
|
|
|
|
|
montant: participationsMois,
|
|
|
|
|
nombreFactures: nombreFactures,
|
|
|
|
|
},
|
|
|
|
|
trajetsAujourdhui: {
|
2026-02-16 19:03:56 +01:00
|
|
|
nombre: trajetsPeriode,
|
|
|
|
|
difference: differenceTrajets,
|
2026-01-22 18:53:23 +01:00
|
|
|
},
|
|
|
|
|
trajetsRealisesMois: {
|
2026-02-16 19:03:56 +01:00
|
|
|
nombre: trajetsRealisesPeriode,
|
2026-01-22 18:53:23 +01:00
|
|
|
pourcentageEvolution: pourcentageEvolution,
|
|
|
|
|
},
|
|
|
|
|
chauffeursActifs: {
|
|
|
|
|
nombre: chauffeursActifs,
|
|
|
|
|
total: totalChauffeurs,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Erreur lors de la récupération des statistiques:', error);
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{ error: 'Erreur serveur' },
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|