import type { StoryGroup } from '../types/jira' import type { DashboardConfig } from '../lib/dashboardConfig' import type { LandingEstimate } from '../lib/executiveLanding' import { computeMacroPipelineHealth } from '../lib/macroTrafficLight' import { assigneeOpenLoadRadar } from '../lib/assigneeRadar' import { countOpenGapsByBadge } from '../lib/functionalGaps' import { calendarDelayVsLastMilestone } from '../lib/scheduleDelay' import { resolveWorkBucketFromIssue } from '../lib/statusBuckets' type Props = { groups: StoryGroup[] dashboardCfg: DashboardConfig landing: LandingEstimate finalMilestoneIso: string | null } function trafficLightClasses(light: 'green' | 'amber' | 'red'): { ring: string; bg: string; dot: string } { switch (light) { case 'green': return { ring: 'ring-emerald-400/50', bg: 'bg-emerald-500/20', dot: 'bg-emerald-400 shadow-[0_0_14px_rgba(52,211,153,0.7)]', } case 'amber': return { ring: 'ring-amber-400/55', bg: 'bg-amber-500/20', dot: 'bg-amber-400 shadow-[0_0_14px_rgba(251,191,36,0.65)]', } case 'red': return { ring: 'ring-rose-500/60', bg: 'bg-rose-600/25', dot: 'bg-rose-500 shadow-[0_0_16px_rgba(244,63,94,0.75)]', } } } export function MacroCockpitStrip({ groups, dashboardCfg, landing, finalMilestoneIso }: Props) { const macro = computeMacroPipelineHealth( groups, dashboardCfg.statusBuckets, dashboardCfg.laneLabels, dashboardCfg.functionalGaps, ) const delay = calendarDelayVsLastMilestone(landing, finalMilestoneIso) const radar = assigneeOpenLoadRadar( groups, dashboardCfg.statusBuckets, dashboardCfg.wipSlotsPerDev, ).slice(0, 6) const gaps = countOpenGapsByBadge(groups, dashboardCfg.functionalGaps, dashboardCfg.statusBuckets) const openSamples = groups .flatMap((g) => g.subtasks .filter((st) => { const b = resolveWorkBucketFromIssue(st, dashboardCfg.statusBuckets) return b === 'in_progress' || b === 'blocked' }) .map((st) => ({ key: st.key, who: st.fields.assignee?.displayName ?? '—', summary: st.fields.summary, })), ) .slice(0, 5) const cls = trafficLightClasses(macro.light) return (

Cockpit macro — DSI · Projet · PO · Exécution

Données = instantané Jira au dernier chargement (Actualiser).

Feux phases (DSI)

{macro.title}

{macro.detail}

{macro.violatingStoryKeys.length > 0 && (

{macro.violatingStoryKeys.join(', ')}

)}

Jalons & vélocité (Directeur de projet)

{delay ? (

{delay.message}

) : (

Aucun retard calendaire détecté par rapport au dernier jalon (ou date / vélocité indisponible).

)}

{landing.businessDaysToFinish != null ? `~${landing.businessDaysToFinish} j. ouvrés restants (sous-tâches), vélocité ajustée effectif / baseline.` : 'Vélocité nulle ou données insuffisantes pour estimer la fin.'}

Écarts fonctionnels (PO)

    {gaps.map((g) => (
  • {g.label} ({g.openCount})
  • ))}

Radar de charge (Chef de projet)

Plafond WIP : {dashboardCfg.wipSlotsPerDev} sous-tâches ouvertes / personne.

    {radar.length === 0 ? (
  • Aucune sous-tâche ouverte.
  • ) : ( radar.map((r) => (
  • {r.name} {r.openCount} {r.overload ? ' ⚠' : ''}
  • )) )}

En cours (aperçu)

    {openSamples.length === 0 ? (
  • Aucun ticket « en cours » ou « bloqué ».
  • ) : ( openSamples.map((x) => (
  • {x.key}{' '} · {x.who}
  • )) )}
) }