feat(backend): add responsable dashboard endpoint
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { AppError } from '../errors/app-error';
|
||||
import { getDashboardResponsable } from '../services/responsable.service';
|
||||
import { toDashboardResponsableResponse } from '../dtos/responsable.dto';
|
||||
|
||||
export async function getDashboard(req: Request, res: Response): Promise<void> {
|
||||
const user = req.user;
|
||||
if (!user) {
|
||||
throw new AppError(401, 'Authentification requise');
|
||||
}
|
||||
|
||||
const dashboard = await getDashboardResponsable(user.id, user.roleCode, user.campusId);
|
||||
res.json({ data: toDashboardResponsableResponse(dashboard) });
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { ActiviteResponsable, StatutCount } from '../repositories/responsable.repository';
|
||||
import { DashboardResponsableData } from '../services/responsable.service';
|
||||
|
||||
export interface RepartitionStatutResponse {
|
||||
total: number;
|
||||
parStatut: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface ActiviteResponsableResponse {
|
||||
id: number;
|
||||
action: string;
|
||||
description: string;
|
||||
dateAction: Date;
|
||||
utilisateur: {
|
||||
id: number;
|
||||
nom: string;
|
||||
prenom: string;
|
||||
};
|
||||
materiel: {
|
||||
id: number;
|
||||
nom: string;
|
||||
reference: string;
|
||||
} | null;
|
||||
empruntId: number | null;
|
||||
}
|
||||
|
||||
export interface DashboardResponsableResponse {
|
||||
kpis: {
|
||||
materiels: RepartitionStatutResponse;
|
||||
emprunts: RepartitionStatutResponse;
|
||||
anomalies: RepartitionStatutResponse;
|
||||
notificationsNonLues: number;
|
||||
};
|
||||
activiteRecente: ActiviteResponsableResponse[];
|
||||
}
|
||||
|
||||
function toRepartition(rows: StatutCount[]): RepartitionStatutResponse {
|
||||
const parStatut: Record<string, number> = {};
|
||||
let total = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
parStatut[row.statut] = row.count;
|
||||
total += row.count;
|
||||
}
|
||||
|
||||
return { total, parStatut };
|
||||
}
|
||||
|
||||
function toActiviteResponse(activite: ActiviteResponsable): ActiviteResponsableResponse {
|
||||
return {
|
||||
id: activite.id,
|
||||
action: activite.action,
|
||||
description: activite.description,
|
||||
dateAction: activite.dateAction,
|
||||
utilisateur: {
|
||||
id: activite.utilisateur.id,
|
||||
nom: activite.utilisateur.nom,
|
||||
prenom: activite.utilisateur.prenom,
|
||||
},
|
||||
materiel: activite.materiel
|
||||
? {
|
||||
id: activite.materiel.id,
|
||||
nom: activite.materiel.nom,
|
||||
reference: activite.materiel.reference,
|
||||
}
|
||||
: null,
|
||||
empruntId: activite.empruntId,
|
||||
};
|
||||
}
|
||||
|
||||
export function toDashboardResponsableResponse(
|
||||
data: DashboardResponsableData,
|
||||
): DashboardResponsableResponse {
|
||||
return {
|
||||
kpis: {
|
||||
materiels: toRepartition(data.materiels),
|
||||
emprunts: toRepartition(data.emprunts),
|
||||
anomalies: toRepartition(data.anomalies),
|
||||
notificationsNonLues: data.notificationsNonLues,
|
||||
},
|
||||
activiteRecente: data.activiteRecente.map(toActiviteResponse),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { prisma } from '../db/prisma';
|
||||
|
||||
export interface StatutCount {
|
||||
statut: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export type ActiviteResponsable = Prisma.HistoriqueGetPayload<{
|
||||
include: {
|
||||
utilisateur: true;
|
||||
materiel: true;
|
||||
emprunt: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
function mapStatutCounts(rows: Array<{ statut: string; _count: { _all: number } }>): StatutCount[] {
|
||||
return rows.map((row) => ({ statut: row.statut, count: row._count._all }));
|
||||
}
|
||||
|
||||
export async function countMaterielsParStatut(campusId: number): Promise<StatutCount[]> {
|
||||
const rows = await prisma.materiel.groupBy({
|
||||
by: ['statut'],
|
||||
where: { campusId, actif: true },
|
||||
_count: { _all: true },
|
||||
});
|
||||
|
||||
return mapStatutCounts(rows);
|
||||
}
|
||||
|
||||
export async function countEmpruntsParStatut(campusId: number): Promise<StatutCount[]> {
|
||||
const rows = await prisma.emprunt.groupBy({
|
||||
by: ['statut'],
|
||||
where: { campusId },
|
||||
_count: { _all: true },
|
||||
});
|
||||
|
||||
return mapStatutCounts(rows);
|
||||
}
|
||||
|
||||
export async function countAnomaliesParStatut(campusId: number): Promise<StatutCount[]> {
|
||||
const rows = await prisma.anomalie.groupBy({
|
||||
by: ['statut'],
|
||||
where: { emprunt: { campusId } },
|
||||
_count: { _all: true },
|
||||
});
|
||||
|
||||
return mapStatutCounts(rows);
|
||||
}
|
||||
|
||||
export function countNotificationsNonLues(utilisateurId: number): Promise<number> {
|
||||
return prisma.notification.count({
|
||||
where: { utilisateurId, lu: false },
|
||||
});
|
||||
}
|
||||
|
||||
export function findActiviteRecente(
|
||||
campusId: number,
|
||||
limite: number,
|
||||
): Promise<ActiviteResponsable[]> {
|
||||
return prisma.historique.findMany({
|
||||
where: { campusId },
|
||||
include: {
|
||||
utilisateur: true,
|
||||
materiel: true,
|
||||
emprunt: true,
|
||||
},
|
||||
orderBy: { dateAction: 'desc' },
|
||||
take: limite,
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { currentUser } from '../middlewares/current-user';
|
||||
import { authRoutes } from './auth.routes';
|
||||
import { materielRoutes } from './materiel.routes';
|
||||
import { empruntRoutes, mesEmpruntsRoutes } from './emprunt.routes';
|
||||
import { responsableRoutes } from './responsable.routes';
|
||||
|
||||
export const apiRouter: Router = Router();
|
||||
|
||||
@@ -13,3 +14,4 @@ apiRouter.use('/auth', authRoutes);
|
||||
apiRouter.use('/materiels', currentUser, materielRoutes);
|
||||
apiRouter.use('/emprunts', currentUser, empruntRoutes);
|
||||
apiRouter.use('/mes-emprunts', currentUser, mesEmpruntsRoutes);
|
||||
apiRouter.use('/responsable', currentUser, responsableRoutes);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import { getDashboard } from '../controllers/responsable.controller';
|
||||
|
||||
export const responsableRoutes: Router = Router();
|
||||
|
||||
responsableRoutes.get('/dashboard', getDashboard);
|
||||
@@ -0,0 +1,45 @@
|
||||
import { AppError } from '../errors/app-error';
|
||||
import {
|
||||
countAnomaliesParStatut,
|
||||
countEmpruntsParStatut,
|
||||
countMaterielsParStatut,
|
||||
countNotificationsNonLues,
|
||||
findActiviteRecente,
|
||||
StatutCount,
|
||||
ActiviteResponsable,
|
||||
} from '../repositories/responsable.repository';
|
||||
|
||||
export interface DashboardResponsableData {
|
||||
materiels: StatutCount[];
|
||||
emprunts: StatutCount[];
|
||||
anomalies: StatutCount[];
|
||||
notificationsNonLues: number;
|
||||
activiteRecente: ActiviteResponsable[];
|
||||
}
|
||||
|
||||
export async function getDashboardResponsable(
|
||||
utilisateurId: number,
|
||||
roleCode: string,
|
||||
campusId: number,
|
||||
): Promise<DashboardResponsableData> {
|
||||
if (roleCode !== 'RESPONSABLE') {
|
||||
throw new AppError(403, 'Acces reserve au responsable materiel');
|
||||
}
|
||||
|
||||
const [materiels, emprunts, anomalies, notificationsNonLues, activiteRecente] =
|
||||
await Promise.all([
|
||||
countMaterielsParStatut(campusId),
|
||||
countEmpruntsParStatut(campusId),
|
||||
countAnomaliesParStatut(campusId),
|
||||
countNotificationsNonLues(utilisateurId),
|
||||
findActiviteRecente(campusId, 10),
|
||||
]);
|
||||
|
||||
return {
|
||||
materiels,
|
||||
emprunts,
|
||||
anomalies,
|
||||
notificationsNonLues,
|
||||
activiteRecente,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user