Ajout diagram de gantt
This commit is contained in:
@@ -47,6 +47,26 @@
|
||||
<div class="card-header section-header">Informations</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="field-label">Date de début</label>
|
||||
<input
|
||||
aria-label="Date de début"
|
||||
class="form-control form-control-sm"
|
||||
type="date"
|
||||
[(ngModel)]="milestone.startDate"
|
||||
(blur)="saveMilestone()"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="field-label">Date de fin</label>
|
||||
<input
|
||||
aria-label="Date de fin"
|
||||
class="form-control form-control-sm"
|
||||
type="date"
|
||||
[(ngModel)]="milestone.endDate"
|
||||
(blur)="saveMilestone()"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="field-label">Date d'échéance</label>
|
||||
<input
|
||||
@@ -237,6 +257,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diagramme Gantt -->
|
||||
@if (!isNewRoute) {
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header section-header">Diagramme Gantt</div>
|
||||
<div class="card-body">
|
||||
<app-gantt-diagram [tasks]="milestoneGanttTasks" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Boutons de création -->
|
||||
@if (isNewRoute) {
|
||||
<div class="d-flex gap-2">
|
||||
|
||||
@@ -31,6 +31,8 @@ const makeMilestone = (overrides: Partial<MilestoneEntity> = {}): MilestoneEntit
|
||||
id: 1,
|
||||
name: 'Sprint 1',
|
||||
description: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
issueIds: [],
|
||||
...overrides,
|
||||
@@ -275,6 +277,46 @@ describe('MilestoneDetail', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('milestoneGanttTasks', () => {
|
||||
it('returns empty array when no linked issues', () => {
|
||||
issuesStore.seed([]);
|
||||
(component as any).milestone.issueIds = [];
|
||||
expect((component as any).milestoneGanttTasks).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('excludes issues missing startDate or endDate', () => {
|
||||
issuesStore.seed([
|
||||
makeIssue({ id: 1, startDate: '2025-01-01', endDate: '' }),
|
||||
makeIssue({ id: 2, startDate: '', endDate: '2025-01-31' }),
|
||||
]);
|
||||
(component as any).milestone.issueIds = [1, 2];
|
||||
expect((component as any).milestoneGanttTasks).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('returns a task for each issue with both dates', () => {
|
||||
issuesStore.seed([
|
||||
makeIssue({ id: 1, name: 'Task A', startDate: '2025-01-01', endDate: '2025-01-15', progress: 50 }),
|
||||
makeIssue({ id: 2, name: 'Task B', startDate: '2025-01-10', endDate: '2025-01-31', progress: 0 }),
|
||||
]);
|
||||
(component as any).milestone.issueIds = [1, 2];
|
||||
const tasks = (component as any).milestoneGanttTasks;
|
||||
expect(tasks).toHaveLength(2);
|
||||
expect(tasks[0]).toMatchObject({ id: 'issue-1', name: '#1 Task A', start: '2025-01-01', end: '2025-01-15', progress: 50 });
|
||||
expect(tasks[1]).toMatchObject({ id: 'issue-2', name: '#2 Task B', start: '2025-01-10', end: '2025-01-31', progress: 0 });
|
||||
});
|
||||
|
||||
it('only includes issues linked to the milestone', () => {
|
||||
issuesStore.seed([
|
||||
makeIssue({ id: 1, startDate: '2025-01-01', endDate: '2025-01-31' }),
|
||||
makeIssue({ id: 2, startDate: '2025-02-01', endDate: '2025-02-28' }),
|
||||
]);
|
||||
(component as any).milestone.issueIds = [1];
|
||||
const tasks = (component as any).milestoneGanttTasks;
|
||||
expect(tasks).toHaveLength(1);
|
||||
expect(tasks[0].id).toBe('issue-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('issueSuggestions', () => {
|
||||
beforeEach(() => {
|
||||
issuesStore.seed([
|
||||
|
||||
@@ -8,10 +8,11 @@ import { IssueEntity, IssuesStore } from '../../issues/issues.store';
|
||||
import { handleImagePaste, insertAtSelection } from '../../issues/paste-image.util';
|
||||
import { MilestoneEntity, MilestonesStore } from '../milestones.store';
|
||||
import { StatusEntity, StatusesStore } from '../../settings/statuses/statuses.store';
|
||||
import { GanttDiagram, GanttTask } from '../../shared/gantt-diagram/gantt-diagram';
|
||||
|
||||
@Component({
|
||||
selector: 'app-milestone-detail',
|
||||
imports: [FormsModule],
|
||||
imports: [FormsModule, GanttDiagram],
|
||||
templateUrl: './milestone-detail.html',
|
||||
styleUrl: './milestone-detail.css',
|
||||
})
|
||||
@@ -94,6 +95,21 @@ export class MilestoneDetail {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(html);
|
||||
}
|
||||
|
||||
protected get milestoneGanttTasks(): GanttTask[] {
|
||||
const tasks: GanttTask[] = [];
|
||||
for (const issue of this.linkedIssues) {
|
||||
if (!issue.startDate || !issue.endDate) continue;
|
||||
tasks.push({
|
||||
id: `issue-${issue.id}`,
|
||||
name: `#${issue.id} ${issue.name}`,
|
||||
start: issue.startDate,
|
||||
end: issue.endDate,
|
||||
progress: issue.progress,
|
||||
});
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
protected get progress(): number {
|
||||
if (this.linkedIssues.length === 0) return 0;
|
||||
return Math.round(
|
||||
@@ -245,9 +261,9 @@ export class MilestoneDetail {
|
||||
|
||||
private buildMilestone(): MilestoneEntity {
|
||||
if (this.route.snapshot.routeConfig?.path === 'milestones/new') {
|
||||
return { id: 0, name: '', description: '', dueDate: '', issueIds: [] };
|
||||
return { id: 0, name: '', description: '', startDate: '', endDate: '', dueDate: '', issueIds: [] };
|
||||
}
|
||||
const id = Number(this.route.snapshot.paramMap.get('id') ?? 0);
|
||||
return this.milestonesStore.getById(id) ?? { id, name: '', description: '', dueDate: '', issueIds: [] };
|
||||
return this.milestonesStore.getById(id) ?? { id, name: '', description: '', startDate: '', endDate: '', dueDate: '', issueIds: [] };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user