mirror of
https://github.com/R0m1k3/Presencia.git
synced 2026-10-22 06:38:04 +02:00
Implémenter le design Presencia (projet Claude Design)
Reprend la maquette Presencia.dc.html sur l'application réelle : thème clair chaud à accent vert forêt, navigation latérale groupée par rôle et tableau de bord, en conservant l'intégralité du fonctionnel existant. Frontend - Écran de connexion en deux panneaux (argumentaire + formulaire). - Tableau de bord par rôle : compteurs du mois et liste « à faire maintenant » (sociétés prêtes à valider, cadres en retard, saisie à compléter), plus l'état du mois par société côté admin. - Planning : saisie rapide au pinceau, trois affichages (grille, compact, liste), sélecteur avec raccourcis clavier (1–4, ⌫, ↵ pour appliquer à la journée), barre d'état de verrouillage, « Tout effacer ». - Nouvelle vue Historique pour les cadres (totaux par mois et statut). - Validation & export : barre de progression et compteurs de demi-journées par cadre. - Mobile : liste verticale, navigation en barre basse, cibles 44 px. Backend - GET /api/validations/overview — état de toutes les sociétés d'un mois. - GET /api/attendance/history — totaux par mois et par statut. - DELETE /api/attendance/month — effacement d'un mois (mêmes verrous). - Compteur de demi-journées saisies par cadre dans /validations/company. Polices et icônes servies localement (Inter, sous-ensemble de 18 icônes Phosphor) au lieu de Google Fonts et unpkg : l'application ne fait plus aucune requête externe au chargement. Vérifié de bout en bout via la vraie configuration nginx : 62 contrôles Playwright (desktop et mobile), tous passants, sur base réinitialisée et rejoués deux fois pour garantir le déterminisme.
This commit is contained in:
14 files changed
+2033
-1234
No files matched your search
@@ -65,14 +65,19 @@ router.get('/company/:companyId', requireAdmin, async (req, res) => {
|
||||
const companyId = req.params.companyId;
|
||||
if (!year || !month) return res.status(400).json({ error: 'year et month requis' });
|
||||
|
||||
const start = `${year}-${String(month).padStart(2, '0')}-01`;
|
||||
const { rows: cadres } = await db.query(
|
||||
`SELECT u.id, u.full_name, u.email,
|
||||
ml.cadre_validated, ml.cadre_validated_at
|
||||
ml.cadre_validated, ml.cadre_validated_at,
|
||||
COUNT(ae.id) AS filled
|
||||
FROM users u
|
||||
LEFT JOIN month_locks ml ON ml.user_id = u.id AND ml.year = $2 AND ml.month = $3
|
||||
LEFT JOIN attendance_entries ae ON ae.user_id = u.id
|
||||
AND ae.entry_date >= $4::date AND ae.entry_date < ($4::date + INTERVAL '1 month')
|
||||
WHERE u.company_id = $1 AND u.role = 'cadre' AND u.active = true
|
||||
GROUP BY u.id, ml.cadre_validated, ml.cadre_validated_at
|
||||
ORDER BY u.full_name`,
|
||||
[companyId, year, month]
|
||||
[companyId, year, month, start]
|
||||
);
|
||||
|
||||
const { rows: cv } = await db.query(
|
||||
@@ -86,6 +91,7 @@ router.get('/company/:companyId', requireAdmin, async (req, res) => {
|
||||
id: c.id,
|
||||
fullName: c.full_name,
|
||||
email: c.email,
|
||||
filled: parseInt(c.filled, 10),
|
||||
cadreValidated: !!c.cadre_validated,
|
||||
cadreValidatedAt: c.cadre_validated_at,
|
||||
})),
|
||||
@@ -95,6 +101,42 @@ router.get('/company/:companyId', requireAdmin, async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Admin: one-shot overview of every company for a given month (dashboard).
|
||||
router.get('/overview', requireAdmin, async (req, res) => {
|
||||
const year = parseInt(req.query.year, 10);
|
||||
const month = parseInt(req.query.month, 10);
|
||||
if (!year || !month) return res.status(400).json({ error: 'year et month requis' });
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT c.id, c.name,
|
||||
COUNT(u.id) FILTER (WHERE u.id IS NOT NULL) AS active_cadres,
|
||||
COUNT(u.id) FILTER (WHERE ml.cadre_validated) AS validated_cadres,
|
||||
BOOL_OR(cmv.admin_validated) AS company_validated
|
||||
FROM companies c
|
||||
LEFT JOIN users u ON u.company_id = c.id AND u.role = 'cadre' AND u.active = true
|
||||
LEFT JOIN month_locks ml ON ml.user_id = u.id AND ml.year = $1 AND ml.month = $2
|
||||
LEFT JOIN company_month_validations cmv ON cmv.company_id = c.id AND cmv.year = $1 AND cmv.month = $2
|
||||
GROUP BY c.id
|
||||
ORDER BY c.name`,
|
||||
[year, month]
|
||||
);
|
||||
|
||||
const companies = rows.map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
activeCadres: parseInt(r.active_cadres, 10),
|
||||
validatedCadres: parseInt(r.validated_cadres, 10),
|
||||
companyValidated: !!r.company_validated,
|
||||
}));
|
||||
|
||||
res.json({
|
||||
companies,
|
||||
totalActiveCadres: companies.reduce((n, c) => n + c.activeCadres, 0),
|
||||
lateCadres: companies.reduce((n, c) => n + (c.companyValidated ? 0 : c.activeCadres - c.validatedCadres), 0),
|
||||
pendingCompanies: companies.filter((c) => !c.companyValidated).length,
|
||||
});
|
||||
});
|
||||
|
||||
// Admin validates the whole company for the month (requires every cadre validated).
|
||||
router.post('/company/:companyId', requireAdmin, async (req, res) => {
|
||||
const ym = parseYearMonth(req);
|
||||
|
||||
Reference in new issue
Block a user