<?php
$h=fn($v)=>htmlspecialchars((string)$v,ENT_QUOTES,'UTF-8');
$rows=$result['rows'];$old=$_SESSION['old']??[];unset($_SESSION['old']);$form=$old?:($edit?:[]);
$stepOld=$_SESSION['step_old']??[];unset($_SESSION['step_old']);$stepForm=$stepOld?:($stepEdit?:[]);
$milestoneOld=$_SESSION['milestone_old']??[];unset($_SESSION['milestone_old']);$milestoneForm=$milestoneOld?:($milestoneEdit?:[]);
$statuses=['draft'=>'Brouillon','planned'=>'Planifié','active'=>'Actif','completed'=>'Terminé','cancelled'=>'Annulé'];
$stepStatuses=['todo'=>'À faire','in_progress'=>'En cours','blocked'=>'Bloqué','completed'=>'Terminé'];
$milestoneStatuses=['planned'=>'Prévu','in_progress'=>'En cours','completed'=>'Terminé','cancelled'=>'Annulé'];
$roadmap=$roadmap??[];$dashboard=$dashboard??[];
$trainStatuses=['draft'=>'Brouillon','planned'=>'Planifié','active'=>'Actif','paused'=>'En pause','completed'=>'Terminé','cancelled'=>'Annulé'];
$dt=fn($v)=>$v?date('Y-m-d\TH:i',strtotime((string)$v)):'';
?>
<style>
.rtm{display:grid;gap:16px}.rtm-hero,.rtm-card{background:#fff;border:1px solid #dbe5ef;border-radius:16px;padding:18px}.rtm-hero{display:flex;justify-content:space-between;gap:16px;align-items:center}.rtm h1,.rtm h2,.rtm h3{margin:0;color:#173b5a}.rtm-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.rtm-btn{display:inline-flex;align-items:center;justify-content:center;padding:10px 13px;border:0;border-radius:10px;background:#e9f1f8;color:#17466b;font-weight:800;text-decoration:none;cursor:pointer}.rtm-btn.primary{background:#1769aa;color:#fff}.rtm-btn.danger{background:#fee2e2;color:#991b1b}.rtm-grid{display:grid;grid-template-columns:minmax(300px,.8fr) minmax(0,1.4fr);gap:16px}.rtm-form{display:grid;gap:11px}.rtm-form label{font-size:12px;font-weight:800;color:#516579}.rtm-form input,.rtm-form select,.rtm-form textarea{width:100%;box-sizing:border-box;padding:10px;border:1px solid #cbd8e5;border-radius:9px}.rtm-two{display:grid;grid-template-columns:1fr 1fr;gap:10px}.rtm-table-wrap{overflow:auto}.rtm-table{width:100%;border-collapse:collapse;min-width:820px}.rtm-table th,.rtm-table td{padding:10px;border-bottom:1px solid #e5edf4;text-align:left;vertical-align:top}.rtm-filter{display:grid;grid-template-columns:1fr 180px auto;gap:8px}.rtm-pages{display:flex;gap:6px;margin-top:12px;flex-wrap:wrap}.rtm-progress{height:12px;background:#edf2f7;border-radius:999px;overflow:hidden}.rtm-progress>span{display:block;height:100%;background:#1769aa}.rtm-muted{color:#66788a;font-size:13px}.rtm-badge{display:inline-flex;padding:5px 8px;border-radius:999px;background:#eef4f8;font-size:12px;font-weight:800}.rtm-risk{display:inline-flex;padding:7px 10px;border-radius:999px;font-weight:800}.rtm-risk.low{background:#dcfce7;color:#166534}.rtm-risk.medium{background:#fef3c7;color:#92400e}.rtm-risk.high{background:#fee2e2;color:#991b1b}.rtm-deps{display:grid;gap:6px;margin-top:8px}.rtm-dep{display:flex;gap:6px;align-items:center;flex-wrap:wrap}.rtm-warning{padding:8px;border-radius:9px;background:#fff7ed;color:#9a3412;font-size:12px}.rtm-step-layout{display:grid;grid-template-columns:minmax(280px,.75fr) minmax(0,1.25fr);gap:16px}@media(max-width:900px){.rtm-grid,.rtm-step-layout{grid-template-columns:1fr}.rtm-hero{align-items:flex-start;flex-direction:column}.rtm-filter,.rtm-two{grid-template-columns:1fr}}
.rtm-board{display:grid;grid-template-columns:repeat(4,minmax(230px,1fr));gap:12px;overflow-x:auto;padding-bottom:4px}.rtm-column{background:#f6f9fc;border:1px solid #dbe5ef;border-radius:14px;min-height:210px;padding:10px}.rtm-column.is-over{outline:3px solid #8bbce0;background:#eef7ff}.rtm-column-head{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:10px}.rtm-column-head h4{margin:0;color:#173b5a}.rtm-count{display:inline-flex;min-width:26px;height:26px;align-items:center;justify-content:center;border-radius:999px;background:#dceaf5;font-weight:900}.rtm-cards{display:grid;gap:9px;min-height:150px}.rtm-step-card{background:#fff;border:1px solid #d7e3ed;border-radius:12px;padding:11px;cursor:grab;box-shadow:0 2px 8px rgba(23,59,90,.06)}.rtm-step-card:active{cursor:grabbing}.rtm-step-card.is-dragging{opacity:.45}.rtm-step-card h5{margin:0 0 6px;color:#173b5a}.rtm-card-meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}.rtm-toast{position:fixed;right:20px;bottom:20px;max-width:380px;padding:12px 15px;border-radius:11px;background:#173b5a;color:#fff;font-weight:800;z-index:9999;box-shadow:0 8px 24px rgba(0,0,0,.2)}.rtm-toast.error{background:#991b1b}@media(max-width:1100px){.rtm-board{grid-template-columns:repeat(4,260px)}}

.rtm-timeline-shell{overflow-x:auto;padding:8px 0 4px}.rtm-timeline{position:relative;min-width:760px;padding:48px 28px 26px}.rtm-timeline-axis{position:absolute;left:28px;right:28px;top:78px;height:6px;border-radius:999px;background:#dce7f0}.rtm-timeline-items{position:relative;height:150px}.rtm-milestone{position:absolute;top:31px;transform:translateX(-50%);width:170px;text-align:center}.rtm-milestone-dot{width:22px;height:22px;margin:0 auto 8px;border:5px solid #fff;border-radius:50%;background:#64748b;box-shadow:0 0 0 2px #94a3b8}.rtm-milestone.planned .rtm-milestone-dot{background:#64748b}.rtm-milestone.in_progress .rtm-milestone-dot{background:#1769aa;box-shadow:0 0 0 2px #1769aa}.rtm-milestone.completed .rtm-milestone-dot{background:#16a34a;box-shadow:0 0 0 2px #16a34a}.rtm-milestone.cancelled .rtm-milestone-dot{background:#94a3b8}.rtm-milestone.overdue .rtm-milestone-dot{background:#dc2626;box-shadow:0 0 0 2px #dc2626}.rtm-milestone-card{background:#fff;border:1px solid #dbe5ef;border-radius:12px;padding:9px;box-shadow:0 4px 14px rgba(23,59,90,.08)}.rtm-milestone-card strong{display:block;color:#173b5a}.rtm-milestone-date{font-size:12px;color:#66788a}.rtm-timeline-legend{display:flex;gap:12px;flex-wrap:wrap;margin-top:8px}.rtm-legend-item{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:#516579}.rtm-legend-dot{width:10px;height:10px;border-radius:50%;background:#64748b}.rtm-legend-dot.progress{background:#1769aa}.rtm-legend-dot.done{background:#16a34a}.rtm-legend-dot.late{background:#dc2626}.rtm-timeline-empty{padding:24px;text-align:center;background:#f8fafc;border:1px dashed #cbd8e5;border-radius:12px;color:#66788a}
.rtm-roadmap{display:grid;gap:12px;margin-top:14px}.rtm-roadmap-row{display:grid;grid-template-columns:minmax(210px,1.25fr) minmax(180px,2fr) 130px;gap:14px;align-items:center;padding:14px;border:1px solid #dbe5ef;border-radius:14px;background:#fff}.rtm-roadmap-meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px}.rtm-roadmap-track{position:relative;height:16px;border-radius:999px;background:#e8eef5;overflow:hidden}.rtm-roadmap-track span{display:block;height:100%;background:#1769aa}.rtm-roadmap-row.late{border-color:#fecaca;background:#fffafa}.rtm-roadmap-row.late .rtm-roadmap-track span{background:#dc2626}.rtm-roadmap-date{text-align:right;color:#516579;font-size:13px}@media(max-width:850px){.rtm-roadmap-row{grid-template-columns:1fr}.rtm-roadmap-date{text-align:left}}
.rtm-kpis{display:grid;grid-template-columns:repeat(5,minmax(145px,1fr));gap:12px}.rtm-kpi{border:1px solid #dbe5ef;border-radius:14px;padding:14px;background:#f8fbfd}.rtm-kpi strong{display:block;font-size:26px;color:#173b5a;margin-top:4px}.rtm-dashboard-grid{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:14px;margin-top:14px}.rtm-health{display:grid;gap:9px}.rtm-health-row{display:grid;grid-template-columns:minmax(170px,1fr) minmax(180px,1.5fr) 95px;gap:10px;align-items:center;border-bottom:1px solid #e5edf4;padding:9px 0}.rtm-alerts{display:grid;gap:8px}.rtm-alert{padding:10px;border-radius:10px;background:#fff7ed;color:#9a3412}.rtm-alert.high{background:#fef2f2;color:#991b1b}.rtm-deadline{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px solid #e5edf4}.rtm-risk-dot{display:inline-flex;padding:5px 8px;border-radius:999px;font-size:12px;font-weight:900}.rtm-risk-dot.low{background:#dcfce7;color:#166534}.rtm-risk-dot.medium{background:#fef3c7;color:#92400e}.rtm-risk-dot.high{background:#fee2e2;color:#991b1b}@media(max-width:1050px){.rtm-kpis{grid-template-columns:repeat(2,1fr)}.rtm-dashboard-grid{grid-template-columns:1fr}}@media(max-width:560px){.rtm-kpis{grid-template-columns:1fr}.rtm-health-row{grid-template-columns:1fr}}
</style>
<div class="rtm">
<section class="rtm-hero"><div><small>PLATFORM-023.5.6 · Release Automation 2.1.1</small><h1>Release Train Manager</h1><p>Dashboard Direction, Pipeline visuel, Timeline et Roadmap consolidée des releases.</p></div><div class="rtm-actions"><a class="rtm-btn primary" href="/public/administration/plateforme/release-automation">Automatisation</a><a class="rtm-btn" href="/public/administration/plateforme/orchestrateur">Orchestrateur</a><a class="rtm-btn" href="/public/administration/plateforme/packages">Packages</a><a class="rtm-btn" href="/public/administration">Retour</a></div></section>
<section class="rtm-card" style="margin-top:16px">
<div class="rtm-actions" style="justify-content:space-between"><div><h2>Dashboard Direction</h2><p class="rtm-muted">Santé globale, risques et échéances de l’organisation.</p></div><span class="rtm-badge"><?= (int)($dashboard['train_count']??0) ?> release(s)</span></div>
<div class="rtm-kpis" style="margin-top:14px">
<div class="rtm-kpi"><small>Progression globale</small><strong><?= (int)($dashboard['global_progress']??0) ?>%</strong><div class="rtm-progress"><span style="width:<?= (int)($dashboard['global_progress']??0) ?>%"></span></div></div>
<div class="rtm-kpi"><small>Actives</small><strong><?= (int)($dashboard['status_counts']['active']??0) ?></strong><span class="rtm-muted">Release trains en cours</span></div>
<div class="rtm-kpi"><small>Planifiées</small><strong><?= (int)($dashboard['status_counts']['planned']??0) ?></strong><span class="rtm-muted">À venir</span></div>
<div class="rtm-kpi"><small>Étapes bloquées</small><strong><?= (int)($dashboard['steps']['blocked']??0) ?></strong><span class="rtm-muted"><?= (int)($dashboard['steps']['completed']??0) ?>/<?= (int)($dashboard['steps']['total']??0) ?> terminées</span></div>
<div class="rtm-kpi"><small>Jalons en retard</small><strong><?= (int)($dashboard['milestones']['overdue']??0) ?></strong><span class="rtm-muted"><?= (int)($dashboard['milestones']['completed']??0) ?>/<?= (int)($dashboard['milestones']['total']??0) ?> atteints</span></div>
</div>
<div class="rtm-dashboard-grid">
<div><h3>Santé des Release Trains</h3><div class="rtm-health">
<?php foreach(($dashboard['trains']??[]) as $health):?><div class="rtm-health-row"><div><a href="?train=<?= (int)$health['id'] ?>"><strong><?= $h($health['code']) ?></strong></a><div class="rtm-muted"><?= $h($health['name']) ?> · <?= $h($health['target_version']) ?></div></div><div><div class="rtm-progress"><span style="width:<?= (int)$health['roadmap_progress'] ?>%"></span></div><small><?= (int)$health['roadmap_progress'] ?>% · <?= (int)$health['completed_steps'] ?>/<?= (int)$health['step_count'] ?> étapes</small></div><span class="rtm-risk-dot <?= $h($health['executive_risk']) ?>"><?= $h($health['executive_risk_label']) ?></span></div><?php endforeach;?>
<?php if(empty($dashboard['trains'])):?><div class="rtm-timeline-empty">Aucune donnée à consolider.</div><?php endif;?>
</div></div>
<div><h3>Alertes prioritaires</h3><div class="rtm-alerts"><?php foreach(($dashboard['alerts']??[]) as $alert):?><a class="rtm-alert <?= $h($alert['level']) ?>" href="?train=<?= (int)$alert['train_id'] ?>"><strong><?= $h($alert['title']) ?></strong><br><?= $h($alert['message']) ?></a><?php endforeach;?><?php if(empty($dashboard['alerts'])):?><div class="rtm-alert" style="background:#f0fdf4;color:#166534">Aucune alerte prioritaire.</div><?php endif;?></div>
<h3 style="margin-top:18px">Prochaines échéances</h3><?php foreach(($dashboard['deadlines']??[]) as $deadline):?><div class="rtm-deadline"><div><a href="?train=<?= (int)$deadline['train_id'] ?>"><strong><?= $h($deadline['code']) ?></strong></a><div class="rtm-muted"><?= $h($deadline['name']) ?></div></div><div style="text-align:right"><strong><?= $h(date('d/m/Y',strtotime((string)$deadline['date']))) ?></strong><div class="rtm-muted"><?= (int)$deadline['progress'] ?>%</div></div></div><?php endforeach;?><?php if(empty($dashboard['deadlines'])):?><p class="rtm-muted">Aucune échéance planifiée.</p><?php endif;?></div>
</div>
</section>
<section class="rtm-card" style="margin-top:16px">
<div class="rtm-actions" style="justify-content:space-between"><div><h2>Roadmap des Release Trains</h2><p class="rtm-muted">Vue consolidée par version, jalons, progression et échéances.</p></div><span class="rtm-badge"><?= count($roadmap) ?> release(s)</span></div>
<div class="rtm-roadmap">
<?php foreach($roadmap as $roadmapTrain):?><?php
$roadmapProgress=(int)($roadmapTrain['roadmap_progress']??0);
$roadmapLate=!empty($roadmapTrain['is_late']);
$roadmapStart=$roadmapTrain['roadmap_start_at']?:$roadmapTrain['planned_at'];
$roadmapEnd=$roadmapTrain['roadmap_end_at']?:$roadmapTrain['planned_at'];
?>
<div class="rtm-roadmap-row<?= $roadmapLate?' late':'' ?>">
<div><a href="?train=<?= (int)$roadmapTrain['id'] ?>"><strong><?= $h($roadmapTrain['code']) ?> · <?= $h($roadmapTrain['name']) ?></strong></a><div class="rtm-roadmap-meta"><span class="rtm-badge"><?= $h($roadmapTrain['target_version']) ?></span><span class="rtm-badge"><?= $h($trainStatuses[$roadmapTrain['status']]??$roadmapTrain['status']) ?></span><?php if($roadmapLate):?><span class="rtm-warning"><?= (int)$roadmapTrain['overdue_milestones'] ?> jalon(s) en retard</span><?php endif;?></div></div>
<div><div class="rtm-progress"><span style="width:<?= $roadmapProgress ?>%"></span></div><small><?= $roadmapProgress ?>% · <?= (int)$roadmapTrain['completed_steps'] ?>/<?= (int)$roadmapTrain['step_count'] ?> étape(s) · <?= (int)$roadmapTrain['completed_milestones'] ?>/<?= (int)$roadmapTrain['milestone_count'] ?> jalon(s)</small></div>
<div class="rtm-roadmap-date"><?= $roadmapStart?$h(date('d/m/Y',strtotime((string)$roadmapStart))):'—' ?> → <?= $roadmapEnd?$h(date('d/m/Y',strtotime((string)$roadmapEnd))):'—' ?></div>
</div>
<?php endforeach;?>
<?php if(!$roadmap):?><div class="rtm-timeline-empty">Aucun Release Train disponible dans cette organisation.</div><?php endif;?>
</div>
</section>

<section class="rtm-grid">
<article class="rtm-card"><h2><?= $edit?'Modifier':'Créer' ?></h2><form class="rtm-form" method="post" action="/public/administration/plateforme/release-trains/enregistrer"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="id" value="<?= (int)($form['id']??0) ?>"><div><label>Code</label><input required maxlength="80" name="code" value="<?= $h($form['code']??'PLATFORM-') ?>"></div><div><label>Nom</label><input required maxlength="160" name="name" value="<?= $h($form['name']??'') ?>"></div><div><label>Version cible</label><input required maxlength="50" name="target_version" value="<?= $h($form['target_version']??'') ?>"></div><div><label>Statut</label><select name="status"><?php foreach($statuses as $k=>$v):?><option value="<?= $k ?>" <?= ($form['status']??'draft')===$k?'selected':'' ?>><?= $v ?></option><?php endforeach;?></select></div><div><label>Date planifiée</label><input type="datetime-local" name="planned_at" value="<?= $h($dt($form['planned_at']??null)) ?>"></div><div><label>Description</label><textarea rows="4" name="description"><?= $h($form['description']??'') ?></textarea></div><div class="rtm-actions"><button class="rtm-btn primary">Enregistrer</button><?php if($edit):?><a class="rtm-btn" href="/public/administration/plateforme/release-trains">Nouveau</a><?php endif;?></div></form></article>
<article class="rtm-card"><form class="rtm-filter"><input name="q" value="<?= $h($filters['q']??'') ?>" placeholder="Rechercher"><select name="status"><option value="">Tous les statuts</option><?php foreach($statuses as $k=>$v):?><option value="<?= $k ?>" <?= ($filters['status']??'')===$k?'selected':'' ?>><?= $v ?></option><?php endforeach;?></select><button class="rtm-btn primary">Filtrer</button></form><div class="rtm-table-wrap"><table class="rtm-table"><thead><tr><th>Code</th><th>Nom</th><th>Version</th><th>Statut</th><th>Progression</th><th>Actions</th></tr></thead><tbody><?php foreach($rows as $r):?><tr><td><strong><?= $h($r['code']) ?></strong></td><td><?= $h($r['name']) ?></td><td><?= $h($r['target_version']) ?></td><td><?= $h($statuses[$r['status']]??$r['status']) ?></td><td><div class="rtm-progress"><span style="width:<?= (int)$r['progress'] ?>%"></span></div><small><?= (int)$r['completed_steps'] ?>/<?= (int)$r['step_count'] ?> · <?= (int)$r['progress'] ?>%</small></td><td><div class="rtm-actions"><a class="rtm-btn primary" href="?train=<?= (int)$r['id'] ?>">Étapes</a><a class="rtm-btn" href="?edit=<?= (int)$r['id'] ?>">Modifier</a><form method="post" action="/public/administration/plateforme/release-trains/archiver" onsubmit="return confirm('Archiver ce release train ?')"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="id" value="<?= (int)$r['id'] ?>"><button class="rtm-btn danger">Archiver</button></form></div></td></tr><?php endforeach;?><?php if(!$rows):?><tr><td colspan="6">Aucun release train.</td></tr><?php endif;?></tbody></table></div><div class="rtm-pages"><?php for($p=1;$p<=$result['pages'];$p++):?><a class="rtm-btn <?= $p===$result['page']?'primary':'' ?>" href="?page=<?= $p ?>&q=<?= urlencode((string)($filters['q']??'')) ?>&status=<?= urlencode((string)($filters['status']??'')) ?>"><?= $p ?></a><?php endfor;?></div></article>
</section>
<?php if($selected):?>
<section class="rtm-card"><div class="rtm-hero" style="padding:0;border:0"><div><small><?= $h($selected['code']) ?></small><h2><?= $h($selected['name']) ?></h2><p class="rtm-muted"><?= $progress['completed'] ?>/<?= $progress['total'] ?> étape(s) terminée(s)</p><span class="rtm-risk <?= $h($risk['level']) ?>"><?= $h($risk['label']) ?> · <?= (int)$risk['blocked'] ?> bloquée(s) · <?= (int)$risk['overdue'] ?> en retard</span></div><div style="min-width:220px"><div class="rtm-progress"><span style="width:<?= (int)$progress['percent'] ?>%"></span></div><strong><?= (int)$progress['percent'] ?>%</strong></div></div></section>

<section class="rtm-card" id="rtm-pipeline" data-train-id="<?= (int)$selected['id'] ?>" data-csrf="<?= $h(\App\Core\Csrf::token()) ?>">
<div class="rtm-actions" style="justify-content:space-between;margin-bottom:12px"><div><h3>Pipeline visuel</h3><p class="rtm-muted">Glissez une étape vers un autre statut. Les dépendances restent obligatoires.</p></div><span class="rtm-badge" id="rtm-live-progress"><?= (int)$progress['percent'] ?>% terminé</span></div>
<div class="rtm-board">
<?php foreach($stepStatuses as $statusKey=>$statusLabel):?>
<section class="rtm-column" data-status="<?= $h($statusKey) ?>">
<div class="rtm-column-head"><h4><?= $h($statusLabel) ?></h4><span class="rtm-count"><?= count(array_filter($steps,static fn($item)=>(string)$item['status']===$statusKey)) ?></span></div>
<div class="rtm-cards">
<?php foreach($steps as $s):?><?php if((string)$s['status']===$statusKey):?>
<article class="rtm-step-card" draggable="true" data-step-id="<?= (int)$s['id'] ?>">
<h5><?= $h($s['title']) ?></h5>
<?php if(!empty($s['unmet_dependencies'])):?><div class="rtm-warning">Prérequis non terminés</div><?php endif;?>
<?php if(!empty($s['blocked_reason'])):?><div class="rtm-warning"><?= $h($s['blocked_reason']) ?></div><?php endif;?>
<div class="rtm-card-meta"><span class="rtm-badge">Ordre <?= (int)$s['position'] ?></span><?php if($s['responsible_user_id']):?><span class="rtm-badge">Responsable #<?= (int)$s['responsible_user_id'] ?></span><?php endif;?></div>
<div class="rtm-actions" style="margin-top:8px"><a class="rtm-btn" href="?train=<?= (int)$selected['id'] ?>&step_edit=<?= (int)$s['id'] ?>">Modifier</a></div>
</article>
<?php endif;?><?php endforeach;?>
</div></section>
<?php endforeach;?>
</div>
</section>

<section class="rtm-step-layout"><article class="rtm-card"><h3><?= $stepEdit?'Modifier l’étape':'Ajouter une étape' ?></h3><form class="rtm-form" method="post" action="/public/administration/plateforme/release-trains/etapes/enregistrer"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="release_train_id" value="<?= (int)$selected['id'] ?>"><input type="hidden" name="step_id" value="<?= (int)($stepForm['id']??0) ?>"><div><label>Titre</label><input required maxlength="180" name="title" value="<?= $h($stepForm['title']??'') ?>"></div><div class="rtm-two"><div><label>Statut</label><select name="step_status"><?php foreach($stepStatuses as $k=>$v):?><option value="<?= $k ?>" <?= ($stepForm['status']??'todo')===$k?'selected':'' ?>><?= $v ?></option><?php endforeach;?></select></div><div><label>Ordre</label><input type="number" min="1" max="9999" name="position" value="<?= (int)($stepForm['position']??10) ?>"></div></div><div><label>Responsable — ID utilisateur facultatif</label><input type="number" min="1" name="responsible_user_id" value="<?= $h($stepForm['responsible_user_id']??'') ?>"></div><div class="rtm-two"><div><label>Début prévisionnel</label><input type="datetime-local" name="planned_start_at" value="<?= $h($dt($stepForm['planned_start_at']??null)) ?>"></div><div><label>Fin prévisionnelle</label><input type="datetime-local" name="planned_end_at" value="<?= $h($dt($stepForm['planned_end_at']??null)) ?>"></div></div><div class="rtm-two"><div><label>Début réel</label><input type="datetime-local" name="actual_start_at" value="<?= $h($dt($stepForm['actual_start_at']??null)) ?>"></div><div><label>Fin réelle</label><input type="datetime-local" name="actual_end_at" value="<?= $h($dt($stepForm['actual_end_at']??null)) ?>"></div></div><div><label>Motif de blocage</label><textarea rows="2" maxlength="500" name="blocked_reason" placeholder="Obligatoire si le statut est Bloqué"><?= $h($stepForm['blocked_reason']??'') ?></textarea></div><div><label>Description</label><textarea rows="4" name="step_description"><?= $h($stepForm['description']??'') ?></textarea></div><div class="rtm-actions"><button class="rtm-btn primary">Enregistrer l’étape</button><?php if($stepEdit):?><a class="rtm-btn" href="?train=<?= (int)$selected['id'] ?>">Annuler</a><?php endif;?></div></form></article>
<article class="rtm-card"><h3>Étapes</h3><div class="rtm-table-wrap"><table class="rtm-table"><thead><tr><th>Ordre</th><th>Étape</th><th>Statut</th><th>Responsable</th><th>Prévisionnel</th><th>Réel</th><th>Actions</th></tr></thead><tbody><?php foreach($steps as $s):?><tr><td><?= (int)$s['position'] ?></td><td><strong><?= $h($s['title']) ?></strong><?php if(!empty($s['description'])):?><div class="rtm-muted"><?= nl2br($h($s['description'])) ?></div><?php endif;?><?php if(!empty($s['blocked_reason'])):?><div class="rtm-warning"><strong>Blocage :</strong> <?= $h($s['blocked_reason']) ?></div><?php endif;?><div class="rtm-deps"><?php foreach(($s['dependencies']??[]) as $dep):?><div class="rtm-dep"><span class="rtm-badge">Prérequis : <?= $h($dep['depends_on_title']) ?> · <?= $h($stepStatuses[$dep['depends_on_status']]??$dep['depends_on_status']) ?></span><form method="post" action="/public/administration/plateforme/release-trains/dependances/archiver" onsubmit="return confirm('Retirer cette dépendance ?')"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="release_train_id" value="<?= (int)$selected['id'] ?>"><input type="hidden" name="dependency_id" value="<?= (int)$dep['id'] ?>"><button class="rtm-btn danger">Retirer</button></form></div><?php endforeach;?><?php if(!empty($s['unmet_dependencies'])):?><div class="rtm-warning">Cette étape ne peut pas être terminée avant ses prérequis.</div><?php endif;?></div><form class="rtm-actions" method="post" action="/public/administration/plateforme/release-trains/dependances/enregistrer"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="release_train_id" value="<?= (int)$selected['id'] ?>"><input type="hidden" name="step_id" value="<?= (int)$s['id'] ?>"><select name="depends_on_step_id" required><option value="">Ajouter un prérequis…</option><?php foreach($steps as $candidate):?><?php if((int)$candidate['id']!==(int)$s['id']):?><option value="<?= (int)$candidate['id'] ?>"><?= $h($candidate['title']) ?></option><?php endif;?><?php endforeach;?></select><button class="rtm-btn">Ajouter</button></form></td><td><span class="rtm-badge"><?= $h($stepStatuses[$s['status']]??$s['status']) ?></span></td><td><?= $s['responsible_user_id']?'#'.(int)$s['responsible_user_id']:'—' ?></td><td><?= $h($s['planned_start_at']??'—') ?><br><?= $h($s['planned_end_at']??'—') ?></td><td><?= $h($s['actual_start_at']??'—') ?><br><?= $h($s['actual_end_at']??'—') ?></td><td><div class="rtm-actions"><a class="rtm-btn" href="?train=<?= (int)$selected['id'] ?>&step_edit=<?= (int)$s['id'] ?>">Modifier</a><form method="post" action="/public/administration/plateforme/release-trains/etapes/archiver" onsubmit="return confirm('Archiver cette étape ?')"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="release_train_id" value="<?= (int)$selected['id'] ?>"><input type="hidden" name="step_id" value="<?= (int)$s['id'] ?>"><button class="rtm-btn danger">Archiver</button></form></div></td></tr><?php endforeach;?><?php if(!$steps):?><tr><td colspan="7">Aucune étape pour ce release train.</td></tr><?php endif;?></tbody></table></div></article></section>

<?php
$timelineDated=array_values(array_filter($milestones,static fn(array $m):bool=>!empty($m['planned_end_at'])||!empty($m['planned_start_at'])));
$timelineTimestamps=array_map(static fn(array $m):int=>strtotime((string)($m['planned_end_at']?:$m['planned_start_at'])), $timelineDated);
$timelineMin=$timelineTimestamps?min($timelineTimestamps):time();
$timelineMax=$timelineTimestamps?max($timelineTimestamps):$timelineMin;
if($timelineMax===$timelineMin){$timelineMax=$timelineMin+86400;}
?>
<section class="rtm-card">
<div class="rtm-actions" style="justify-content:space-between"><div><h3>Timeline des jalons</h3><p class="rtm-muted">Positionnement selon la date cible prévisionnelle. Les jalons en retard sont signalés automatiquement.</p></div><span class="rtm-badge"><?= count($milestones) ?> jalon(s)</span></div>
<?php if($timelineDated):?>
<div class="rtm-timeline-shell"><div class="rtm-timeline"><div class="rtm-timeline-axis"></div><div class="rtm-timeline-items">
<?php foreach($timelineDated as $m):?><?php
$targetTimestamp=strtotime((string)($m['planned_end_at']?:$m['planned_start_at']));
$position=(($targetTimestamp-$timelineMin)/max(1,$timelineMax-$timelineMin))*100;
$position=max(2,min(98,$position));
$isOverdue=$m['status']!=='completed'&&$m['status']!=='cancelled'&&$targetTimestamp<time();
$timelineClass=(string)$m['status'].($isOverdue?' overdue':'');
?>
<div class="rtm-milestone <?= $h($timelineClass) ?>" style="left:<?= number_format($position,2,'.','') ?>%"><div class="rtm-milestone-dot"></div><div class="rtm-milestone-card"><strong><?= $h($m['title']) ?></strong><span class="rtm-milestone-date"><?= $h(date('d/m/Y',$targetTimestamp)) ?></span><div><span class="rtm-badge"><?= $h($milestoneStatuses[$m['status']]??$m['status']) ?></span></div><?php if($isOverdue):?><div class="rtm-warning" style="margin-top:6px">En retard</div><?php endif;?></div></div>
<?php endforeach;?>
</div></div></div>
<div class="rtm-timeline-legend"><span class="rtm-legend-item"><i class="rtm-legend-dot"></i>Prévu</span><span class="rtm-legend-item"><i class="rtm-legend-dot progress"></i>En cours</span><span class="rtm-legend-item"><i class="rtm-legend-dot done"></i>Terminé</span><span class="rtm-legend-item"><i class="rtm-legend-dot late"></i>En retard</span></div>
<?php else:?><div class="rtm-timeline-empty">Ajoutez une date prévisionnelle à un jalon pour l’afficher sur la Timeline.</div><?php endif;?>
</section>

<section class="rtm-step-layout">
<article class="rtm-card"><h3><?= $milestoneEdit?'Modifier le jalon':'Ajouter un jalon' ?></h3>
<form class="rtm-form" method="post" action="/public/administration/plateforme/release-trains/jalons/enregistrer">
<input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>">
<input type="hidden" name="release_train_id" value="<?= (int)$selected['id'] ?>">
<input type="hidden" name="milestone_id" value="<?= (int)($milestoneForm['id']??0) ?>">
<div><label>Titre du jalon</label><input required maxlength="180" name="milestone_title" value="<?= $h($milestoneForm['title']??'') ?>"></div>
<div class="rtm-two"><div><label>Statut</label><select name="milestone_status"><?php foreach($milestoneStatuses as $k=>$v):?><option value="<?= $k ?>" <?= ($milestoneForm['status']??'planned')===$k?'selected':'' ?>><?= $v ?></option><?php endforeach;?></select></div><div><label>Progression (%)</label><input type="number" min="0" max="100" name="milestone_progress" value="<?= (int)($milestoneForm['progress']??0) ?>"></div></div>
<div class="rtm-two"><div><label>Ordre</label><input type="number" min="1" max="9999" name="milestone_position" value="<?= (int)($milestoneForm['position']??10) ?>"></div><div><label>Responsable — ID utilisateur</label><input type="number" min="1" name="milestone_owner_user_id" value="<?= $h($milestoneForm['owner_user_id']??'') ?>"></div></div>
<div class="rtm-two"><div><label>Début prévisionnel</label><input type="datetime-local" name="milestone_planned_start_at" value="<?= $h($dt($milestoneForm['planned_start_at']??null)) ?>"></div><div><label>Fin prévisionnelle</label><input type="datetime-local" name="milestone_planned_end_at" value="<?= $h($dt($milestoneForm['planned_end_at']??null)) ?>"></div></div>
<div class="rtm-two"><div><label>Début réel</label><input type="datetime-local" name="milestone_actual_start_at" value="<?= $h($dt($milestoneForm['actual_start_at']??null)) ?>"></div><div><label>Fin réelle</label><input type="datetime-local" name="milestone_actual_end_at" value="<?= $h($dt($milestoneForm['actual_end_at']??null)) ?>"></div></div>
<div><label>Description</label><textarea rows="4" name="milestone_description"><?= $h($milestoneForm['description']??'') ?></textarea></div>
<div class="rtm-actions"><button class="rtm-btn primary">Enregistrer le jalon</button><?php if($milestoneEdit):?><a class="rtm-btn" href="?train=<?= (int)$selected['id'] ?>">Annuler</a><?php endif;?></div>
</form></article>
<article class="rtm-card"><h3>Jalons de la release</h3><div class="rtm-table-wrap"><table class="rtm-table"><thead><tr><th>Ordre</th><th>Jalon</th><th>Statut</th><th>Progression</th><th>Dates</th><th>Responsable</th><th>Actions</th></tr></thead><tbody>
<?php foreach($milestones as $m):?><tr><td><?= (int)$m['position'] ?></td><td><strong><?= $h($m['title']) ?></strong><?php if(!empty($m['description'])):?><div class="rtm-muted"><?= nl2br($h($m['description'])) ?></div><?php endif;?></td><td><span class="rtm-badge"><?= $h($milestoneStatuses[$m['status']]??$m['status']) ?></span></td><td><div class="rtm-progress"><span style="width:<?= (int)$m['progress'] ?>%"></span></div><small><?= (int)$m['progress'] ?>%</small></td><td>Prévu : <?= $h($m['planned_start_at']??'—') ?><br><?= $h($m['planned_end_at']??'—') ?><br>Réel : <?= $h($m['actual_start_at']??'—') ?><br><?= $h($m['actual_end_at']??'—') ?></td><td><?= $m['owner_user_id']?'#'.(int)$m['owner_user_id']:'—' ?></td><td><div class="rtm-actions"><a class="rtm-btn" href="?train=<?= (int)$selected['id'] ?>&milestone_edit=<?= (int)$m['id'] ?>">Modifier</a><form method="post" action="/public/administration/plateforme/release-trains/jalons/archiver" onsubmit="return confirm('Archiver ce jalon ?')"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="release_train_id" value="<?= (int)$selected['id'] ?>"><input type="hidden" name="milestone_id" value="<?= (int)$m['id'] ?>"><button class="rtm-btn danger">Archiver</button></form></div></td></tr><?php endforeach;?>
<?php if(!$milestones):?><tr><td colspan="7">Aucun jalon pour ce release train.</td></tr><?php endif;?></tbody></table></div>
<?php if($archivedMilestones):?><h3 style="margin-top:18px">Jalons archivés</h3><div class="rtm-actions"><?php foreach($archivedMilestones as $m):?><form method="post" action="/public/administration/plateforme/release-trains/jalons/restaurer"><input type="hidden" name="_csrf" value="<?= $h(\App\Core\Csrf::token()) ?>"><input type="hidden" name="release_train_id" value="<?= (int)$selected['id'] ?>"><input type="hidden" name="milestone_id" value="<?= (int)$m['id'] ?>"><button class="rtm-btn">Restaurer <?= $h($m['title']) ?></button></form><?php endforeach;?></div><?php endif;?>
</article></section>

<?php endif;?>

<script>
(()=>{
 const board=document.getElementById('rtm-pipeline'); if(!board)return;
 let dragged=null, origin=null;
 const toast=(message,error=false)=>{const old=document.querySelector('.rtm-toast');if(old)old.remove();const node=document.createElement('div');node.className='rtm-toast'+(error?' error':'');node.textContent=message;document.body.appendChild(node);setTimeout(()=>node.remove(),4500)};
 const counts=()=>document.querySelectorAll('.rtm-column').forEach(col=>{const count=col.querySelectorAll('.rtm-step-card').length;const badge=col.querySelector('.rtm-count');if(badge)badge.textContent=String(count)});
 document.querySelectorAll('.rtm-step-card').forEach(card=>{
  card.addEventListener('dragstart',()=>{dragged=card;origin=card.parentElement;card.classList.add('is-dragging')});
  card.addEventListener('dragend',()=>{card.classList.remove('is-dragging');document.querySelectorAll('.rtm-column').forEach(c=>c.classList.remove('is-over'));dragged=null;origin=null});
 });
 document.querySelectorAll('.rtm-column').forEach(column=>{
  column.addEventListener('dragover',event=>{event.preventDefault();column.classList.add('is-over')});
  column.addEventListener('dragleave',()=>column.classList.remove('is-over'));
  column.addEventListener('drop',async event=>{
   event.preventDefault();column.classList.remove('is-over');if(!dragged)return;
   const destination=column.querySelector('.rtm-cards');const previous=origin;destination.appendChild(dragged);counts();
   const body=new URLSearchParams({_csrf:board.dataset.csrf,release_train_id:board.dataset.trainId,step_id:dragged.dataset.stepId,status:column.dataset.status});
   try{
    const response=await fetch('/public/administration/plateforme/release-trains/etapes/deplacer',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8','X-Requested-With':'XMLHttpRequest'},body:body.toString(),credentials:'same-origin'});
    const data=await response.json();if(!response.ok||!data.ok)throw new Error(data.message||'Déplacement refusé.');
    const progress=document.getElementById('rtm-live-progress');if(progress)progress.textContent=String(data.progress.percent)+'% terminé';toast(data.message||'Étape déplacée.');
   }catch(error){if(previous)previous.appendChild(dragged);counts();toast(error.message||'Déplacement impossible.',true)}
  });
 });
})();
</script>
</div>
