This commit is contained in:
2026-05-24 09:27:01 +02:00
parent e946436a42
commit 14156a23fb
9 changed files with 154 additions and 150 deletions
+44 -119
View File
@@ -1,6 +1,6 @@
import { Injectable, signal } from '@angular/core';
const ISSUES_STORAGE_KEY = 'bonsai.issues';
import { Injectable, inject, signal } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { IssuesApiService } from './issues-api.service';
export type IssueStatus = 'draft' | 'todo' | 'done' | 'in-progress';
export type IssuePriority = 'Basse' | 'Moyenne' | 'Haute';
@@ -29,108 +29,60 @@ export type IssueEntity = {
progress: number;
};
const DEFAULT_ISSUES: IssueEntity[] = [
{
id: 1,
type: 'Bug',
assignee: 'Marie',
epic: 'EPIC-UI',
name: 'Bug affichage menu mobile',
dueDate: '2026-06-10',
description: 'Corriger le comportement du menu sur petits ecrans.',
estimatedTime: 8,
dependsOnIds: [],
comments: [],
priority: 'Haute',
status: 'in-progress',
progress: 35,
},
{
id: 2,
type: 'Study',
assignee: 'Nabil',
epic: 'EPIC-FORM',
name: 'Erreur validation formulaire projet',
dueDate: '2026-06-12',
description: 'Fiabiliser les regles de validation du formulaire projet.',
estimatedTime: 16,
dependsOnIds: [],
comments: [],
priority: 'Moyenne',
status: 'todo',
progress: 20,
},
{
id: 3,
type: 'Story',
assignee: 'Sonia',
epic: 'EPIC-CONTENT',
name: 'Mise a jour message de bienvenue',
dueDate: '2026-06-18',
description: 'Mettre a jour le wording d accueil selon la charte produit.',
estimatedTime: 4,
dependsOnIds: [],
comments: [],
priority: 'Basse',
status: 'done',
progress: 100,
},
];
@Injectable({ providedIn: 'root' })
export class IssuesStore {
private readonly data = signal<IssueEntity[]>(DEFAULT_ISSUES);
constructor() {
const cachedIssues = this.readFromStorage();
if (cachedIssues) {
this.data.set(cachedIssues.map((issue) => this.normalizeIssue(issue)));
}
}
private readonly api = inject(IssuesApiService);
private readonly data = signal<IssueEntity[]>([]);
readonly loading = signal(false);
readonly loaded = signal(false);
readonly issues = this.data.asReadonly();
getById(id: number): IssueEntity | undefined {
return this.data().find((issue) => issue.id === id);
return this.data().find((i) => i.id === id);
}
getNextId(): number {
const ids = this.data().map((issue) => issue.id);
return ids.length > 0 ? Math.max(...ids) + 1 : 1;
async load(): Promise<void> {
if (this.loaded()) return;
this.loading.set(true);
try {
const issues = await firstValueFrom(this.api.getAll());
this.data.set(issues.map((i) => this.normalizeIssue(i)));
this.loaded.set(true);
} finally {
this.loading.set(false);
}
}
upsert(issue: IssueEntity): void {
const normalizedIssue = this.normalizeIssue(issue);
this.data.update((issues) => {
const existingIndex = issues.findIndex((current) => current.id === issue.id);
if (existingIndex === -1) {
const created = [...issues, normalizedIssue];
this.persistToStorage(created);
return created;
}
const updated = [...issues];
updated[existingIndex] = normalizedIssue;
this.persistToStorage(updated);
async upsert(issue: IssueEntity): Promise<IssueEntity> {
const normalized = this.normalizeIssue(issue);
if (!normalized.id) {
const { id: _id, ...body } = normalized;
const created = this.normalizeIssue(await firstValueFrom(this.api.create(body)));
this.data.update((issues) => [...issues, created]);
return created;
} else {
const updated = this.normalizeIssue(
await firstValueFrom(this.api.update(normalized.id, normalized)),
);
this.data.update((issues) => {
const idx = issues.findIndex((i) => i.id === normalized.id);
if (idx === -1) return issues;
const copy = [...issues];
copy[idx] = updated;
return copy;
});
return updated;
});
}
}
deleteById(id: number): void {
this.data.update((issues) => {
const updated = issues
.filter((issue) => issue.id !== id)
.map((issue) => ({
...issue,
dependsOnIds: issue.dependsOnIds.filter((dependencyId) => dependencyId !== id),
}));
this.persistToStorage(updated);
return updated;
});
async deleteById(id: number): Promise<void> {
await firstValueFrom(this.api.remove(id));
this.data.update((issues) =>
issues
.filter((i) => i.id !== id)
.map((i) => ({ ...i, dependsOnIds: i.dependsOnIds.filter((d) => d !== id) })),
);
}
private normalizeIssue(
@@ -149,31 +101,4 @@ export class IssuesStore {
comments: Array.isArray(issue.comments) ? issue.comments : [],
} as IssueEntity;
}
private readFromStorage(): IssueEntity[] | null {
if (typeof window === 'undefined') {
return null;
}
const rawIssues = window.localStorage.getItem(ISSUES_STORAGE_KEY);
if (!rawIssues) {
return null;
}
try {
const parsed = JSON.parse(rawIssues);
return Array.isArray(parsed) ? (parsed as IssueEntity[]) : null;
} catch {
return null;
}
}
private persistToStorage(issues: IssueEntity[]): void {
if (typeof window === 'undefined') {
return;
}
window.localStorage.setItem(ISSUES_STORAGE_KEY, JSON.stringify(issues));
}
}