Ajout des personnage dans la sidebar de la campagne

This commit is contained in:
2026-04-23 14:34:07 +02:00
parent f1989c1d77
commit a4df9fc759
17 changed files with 104 additions and 24 deletions

View File

@@ -1,8 +1,10 @@
import { Observable, forkJoin, of } from 'rxjs';
import { switchMap, map } from 'rxjs/operators';
import { CampaignService } from '../services/campaign.service';
import { CharacterService } from '../services/character.service';
import { TreeItem } from '../services/layout.service';
import { Arc, Chapter, Scene } from '../services/campaign.model';
import { Character } from '../services/character.model';
/**
* Helper — charge l'arborescence complète d'une campagne (arcs -> chapitres -> scènes)
@@ -16,16 +18,21 @@ export interface CampaignTreeData {
arcs: Arc[];
chaptersByArc: Record<string, Chapter[]>;
scenesByChapter: Record<string, Scene[]>;
characters: Character[];
}
export function loadCampaignTreeData(
service: CampaignService,
campaignId: string
campaignId: string,
characterService: CharacterService
): Observable<CampaignTreeData> {
return service.getArcs(campaignId).pipe(
switchMap(arcs => {
return forkJoin({
arcs: service.getArcs(campaignId),
characters: characterService.getByCampaign(campaignId)
}).pipe(
switchMap(({ arcs, characters }) => {
if (arcs.length === 0) {
return of({ arcs, chaptersByArc: {}, scenesByChapter: {} });
return of({ arcs, chaptersByArc: {}, scenesByChapter: {}, characters });
}
const chapterCalls = arcs.map(a =>
service.getChapters(a.id!).pipe(map(chapters => ({ arcId: a.id!, chapters })))
@@ -40,7 +47,7 @@ export function loadCampaignTreeData(
});
if (allChapters.length === 0) {
return of({ arcs, chaptersByArc, scenesByChapter: {} });
return of({ arcs, chaptersByArc, scenesByChapter: {}, characters });
}
const sceneCalls = allChapters.map(c =>
service.getScenes(c.id!).pipe(map(scenes => ({ chapterId: c.id!, scenes })))
@@ -49,7 +56,7 @@ export function loadCampaignTreeData(
map(sceneResults => {
const scenesByChapter: Record<string, Scene[]> = {};
sceneResults.forEach(r => { scenesByChapter[r.chapterId] = r.scenes; });
return { arcs, chaptersByArc, scenesByChapter };
return { arcs, chaptersByArc, scenesByChapter, characters };
})
);
})
@@ -67,9 +74,33 @@ export function buildCampaignTree(campaignId: string, data: CampaignTreeData): T
// IDs préfixés par type pour éviter les collisions dans LayoutService.expanded
// (chaque entité a sa propre séquence IDENTITY en base → arc.id=1 et chapter.id=1
// peuvent coexister et se marchaient sur les pieds dans le Set<string> global).
const sortedCharacters = [...data.characters].sort(byName);
const characterItems: TreeItem[] = sortedCharacters.map(ch => ({
id: `character-${ch.id}`,
label: ch.name,
route: `/campaigns/${campaignId}/characters/${ch.id}/edit`
}));
const charactersNode: TreeItem = {
id: 'characters-root',
label: 'Personnages',
iconKey: 'users',
children: characterItems,
meta: characterItems.length ? String(characterItems.length) : undefined,
sectionHeaderBefore: 'Personnages',
// Note : si pas d'arcs, le filet au-dessus de "Personnages" est masqué par CSS
// (:first-child), ce qui est voulu — on ne veut pas de ligne seule en haut.
createActions: [{
id: 'new-character',
label: 'Nouveau PJ',
route: `/campaigns/${campaignId}/characters/create`,
actionIcon: 'plus'
}]
};
const sortedArcs = [...data.arcs].sort(byName);
return sortedArcs.map(arc => {
const arcNodes: TreeItem[] = sortedArcs.map((arc, idx) => {
const sortedChapters = [...(data.chaptersByArc[arc.id!] ?? [])].sort(byName);
const chapterItems: TreeItem[] = sortedChapters.map(ch => {
@@ -98,6 +129,8 @@ export function buildCampaignTree(campaignId: string, data: CampaignTreeData): T
label: arc.name,
children: chapterItems,
route: `/campaigns/${campaignId}/arcs/${arc.id}`,
sectionHeaderBefore: idx === 0 ? 'Narration' : undefined,
createActions: [{
id: `new-chapter-${arc.id}`,
label: 'Nouveau chapitre',
@@ -106,4 +139,6 @@ export function buildCampaignTree(campaignId: string, data: CampaignTreeData): T
}]
};
});
return [...arcNodes, charactersNode];
}