Files
LoreMind/web/src/app/shared/session-prep-panel/session-prep-panel.component.html
IETM_FIXE\ietm6 b2c3800bf8
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m38s
Build & Push Images / build (web) (push) Successful in 1m53s
Build & Push Images / build (core) (push) Successful in 3m40s
Build & Push Images / build-switcher (push) Successful in 16s
Co-MJ v1 : quêtes de première classe, guidage de préparation, assistant IA,
mode séance, battlemaps multiples et export Foundry ciblé

- Quêtes (Niveau 1) : entité Quest orthogonale à l'arbre, rattachée à un arc HUB
  ou libre (migrations V9-V12, V18, V20 ; réconciliation des jumeaux V10).
  Fusion quête/conteneur dans la sidebar, progression par partie (statuts
  disponible/en cours/terminée), et quêtes libres avec espace de scènes créé
  automatiquement (arc technique « Quêtes libres », V21 : levée de la
  contrainte arcs.type héritée du baseline).
- Guidage (Pilier B) : bilan de préparation 100 % dérivé (règles arc/chapitre/
  scène/quête), panneau « Prochaines étapes » avec boutons « Corriger », et
  pastilles détaillées (tooltip des manques) dans l'arbre.
- Assistant IA (Pilier A) : étoffage champ par champ (scène, chapitre, arc) et
  brouillons de scènes en propose→applique (brain : narrative-fields,
  scene-drafts).
- Horloges & menaces (V15-V17) : clocks à segments avec déclencheurs, fronts.
- Mode séance : préparation de séance (readiness + quêtes dispo), scène
  épinglée (V19), récap « précédemment » (brain : session-recap), onglet
  « Partie » du panneau de référence, graphe amélioré (pan/zoom, éditeur de
  liens).
- Perf : endpoint agrégé GET /api/campaigns/{id}/tree — la sidebar charge en
  1 requête au lieu de ~15.
- Battlemaps multiples par scène (variantes jour/nuit, étages…) : liste JSON
  étiquetée (V22, reprise automatique de la carte existante), rendu PDF avec
  légendes, export/import rétro-compatible.
- Export Foundry ciblé : modale de périmètre (cartes+ennemis / journaux /
  tables), bundle filtré côté serveur (zip allégé), module Foundry à jour
  (respect du périmètre + une Scene Foundry par variante de carte,
  rétro-compatible anciens bundles).
2026-07-03 15:29:23 +02:00

131 lines
5.6 KiB
HTML

@if (report) {
<div class="prep-panel">
<!-- Dernière séance -->
@if (report.lastSession; as last) {
<p class="prep-last">
<lucide-icon [img]="CalendarCheck" [size]="14"></lucide-icon>
{{ (last.active ? 'sessionPrep.lastSessionActive' : 'sessionPrep.lastSessionEnded') | translate:{ name: last.name } }}
</p>
}
@if (allQuiet) {
<p class="prep-quiet">
<lucide-icon [img]="CheckCircle2" [size]="15"></lucide-icon>
{{ 'sessionPrep.allQuiet' | translate }}
</p>
}
<!-- Où en sont les joueurs -->
@if (report.questsInProgress.length > 0 || report.questsAvailable.length > 0) {
<div class="prep-section">
<h3><lucide-icon [img]="Compass" [size]="14"></lucide-icon> {{ 'sessionPrep.positionTitle' | translate }}</h3>
@if (report.questsInProgress.length > 0) {
<div class="prep-chip-row">
<span class="prep-chip-label">{{ 'sessionPrep.inProgress' | translate }}</span>
@for (q of report.questsInProgress; track q.id) {
<span class="prep-chip-group">
<a class="prep-chip in-progress" [routerLink]="['/campaigns', campaignId, 'quests', q.id]">{{ q.name }}</a>
<button type="button" class="chip-action" (click)="completeQuest(q)"
[title]="'sessionPrep.markCompleted' | translate">
<lucide-icon [img]="Check" [size]="12"></lucide-icon>
</button>
</span>
}
</div>
}
@if (report.questsAvailable.length > 0) {
<div class="prep-chip-row">
<span class="prep-chip-label">{{ 'sessionPrep.available' | translate }}</span>
@for (q of report.questsAvailable; track q.id) {
<span class="prep-chip-group">
<a class="prep-chip available" [routerLink]="['/campaigns', campaignId, 'quests', q.id]">{{ q.name }}</a>
<button type="button" class="chip-action" (click)="startQuest(q)"
[title]="'sessionPrep.markStarted' | translate">
<lucide-icon [img]="Play" [size]="12"></lucide-icon>
</button>
<button type="button" class="chip-action" (click)="completeQuest(q)"
[title]="'sessionPrep.markCompleted' | translate">
<lucide-icon [img]="Check" [size]="12"></lucide-icon>
</button>
</span>
}
</div>
}
</div>
}
<!-- Quêtes terminées : rappel discret + rouvrir en cas d'erreur -->
@if (report.questsCompleted.length > 0) {
<div class="prep-chip-row prep-completed">
<span class="prep-chip-label">{{ 'sessionPrep.completed' | translate }}</span>
@for (q of report.questsCompleted; track q.id) {
<span class="prep-chip-group">
<a class="prep-chip done" [routerLink]="['/campaigns', campaignId, 'quests', q.id]">{{ q.name }}</a>
<button type="button" class="chip-action" (click)="reopenQuest(q)"
[title]="'sessionPrep.reopen' | translate">
<lucide-icon [img]="RotateCcw" [size]="12"></lucide-icon>
</button>
</span>
}
</div>
}
<!-- Contenu probable de la prochaine séance -->
@if (report.hotspots.length > 0) {
<div class="prep-section">
<h3><lucide-icon [img]="MapPin" [size]="14"></lucide-icon> {{ 'sessionPrep.hotspotsTitle' | translate }}</h3>
<div class="prep-chip-row">
@for (node of report.hotspots; track node.nodeType + node.id) {
<a class="prep-chip node" [routerLink]="nodeLink(node)">
{{ node.name }}
<span class="node-kind">{{ ('sessionPrep.nodeKind.' + node.nodeType) | translate }}</span>
</a>
}
</div>
</div>
}
<!-- À combler avant de jouer -->
@if (report.gaps.length > 0) {
<div class="prep-section">
<h3 class="warn"><lucide-icon [img]="AlertTriangle" [size]="14"></lucide-icon> {{ 'sessionPrep.gapsTitle' | translate }}</h3>
<ul class="prep-gap-list">
@for (gap of report.gaps; track gap.ruleId + gap.entityId) {
<li class="prep-gap" [class.blocking]="gap.severity === 'BLOCKING'">
<span class="gap-msg">{{ gap.message }}</span>
<a class="gap-cta" [routerLink]="gapLink(gap)" [queryParams]="gapQuery(gap)">
{{ 'sessionPrep.fix' | translate }}
<lucide-icon [img]="ArrowRight" [size]="12"></lucide-icon>
</a>
</li>
}
</ul>
</div>
}
@if (report.otherGapCount > 0) {
<p class="prep-other">
<a [routerLink]="['/campaigns', campaignId]">{{ 'sessionPrep.otherGaps' | translate:{ n: report.otherGapCount } }}</a>
</p>
}
<!-- Menaces en mouvement -->
@if (report.clocks.length > 0) {
<div class="prep-section">
<h3><lucide-icon [img]="Timer" [size]="14"></lucide-icon> {{ 'sessionPrep.clocksTitle' | translate }}</h3>
<div class="prep-chip-row">
@for (clock of report.clocks; track clock.id) {
<span class="prep-clock" [class.nearly]="isNearlyFull(clock)" [class.full]="isFull(clock)">
{{ clock.name }} — {{ clock.filled }}/{{ clock.segments }}
@if (clock.frontName) { <span class="clock-front">({{ clock.frontName }})</span> }
@if (isFull(clock)) { <strong>{{ 'sessionPrep.clockFull' | translate }}</strong> }
@else if (isNearlyFull(clock)) { <strong>{{ 'sessionPrep.clockNearly' | translate }}</strong> }
</span>
}
</div>
</div>
}
</div>
}