From ba6a3d08277d2e8beda70e8978d28668aa9c3dad Mon Sep 17 00:00:00 2001 From: Gato Date: Fri, 29 May 2026 07:58:51 +0200 Subject: [PATCH] Ajoute date debut et date de fin Signed-off-by: Gato --- src/app/dashboard/dashboard.spec.ts | 2 + .../issue-comments/issue-comments.spec.ts | 2 + .../issues/issue-comments/issue-comments.ts | 2 + src/app/issues/issue-detail/issue-detail.html | 27 ++++++ .../issues/issue-detail/issue-detail.spec.ts | 82 +++++++++++++++++++ src/app/issues/issue-detail/issue-detail.ts | 23 ++++++ src/app/issues/issues.spec.ts | 2 + src/app/issues/issues.store.spec.ts | 27 ++++++ src/app/issues/issues.store.ts | 4 + .../milestone-detail/milestone-detail.spec.ts | 2 + .../milestone-detail/milestone-detail.ts | 2 + 11 files changed, 175 insertions(+) diff --git a/src/app/dashboard/dashboard.spec.ts b/src/app/dashboard/dashboard.spec.ts index 437f3a5..2bcf53d 100644 --- a/src/app/dashboard/dashboard.spec.ts +++ b/src/app/dashboard/dashboard.spec.ts @@ -12,6 +12,8 @@ const makeIssue = (overrides: Partial = {}): IssueEntity => ({ assignee: '', epic: '', name: 'Test Issue', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, diff --git a/src/app/issues/issue-comments/issue-comments.spec.ts b/src/app/issues/issue-comments/issue-comments.spec.ts index 417e34c..49089a0 100644 --- a/src/app/issues/issue-comments/issue-comments.spec.ts +++ b/src/app/issues/issue-comments/issue-comments.spec.ts @@ -11,6 +11,8 @@ const makeIssue = (overrides: Partial = {}): IssueEntity => ({ assignee: '', epic: '', name: 'Test Issue', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, diff --git a/src/app/issues/issue-comments/issue-comments.ts b/src/app/issues/issue-comments/issue-comments.ts index 4b2dda4..81be269 100644 --- a/src/app/issues/issue-comments/issue-comments.ts +++ b/src/app/issues/issue-comments/issue-comments.ts @@ -170,6 +170,8 @@ export class IssueComments { name, assignee: '', epic: '', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, diff --git a/src/app/issues/issue-detail/issue-detail.html b/src/app/issues/issue-detail/issue-detail.html index bea9ad4..c44f96c 100644 --- a/src/app/issues/issue-detail/issue-detail.html +++ b/src/app/issues/issue-detail/issue-detail.html @@ -153,6 +153,33 @@ +
+
+ + +
+
+ + +
+ @if (dateValidationError) { +
{{ dateValidationError }}
+ } +
diff --git a/src/app/issues/issue-detail/issue-detail.spec.ts b/src/app/issues/issue-detail/issue-detail.spec.ts index ada3755..0f21a2f 100644 --- a/src/app/issues/issue-detail/issue-detail.spec.ts +++ b/src/app/issues/issue-detail/issue-detail.spec.ts @@ -14,6 +14,8 @@ const makeIssue = (overrides: Partial = {}): IssueEntity => ({ assignee: '', epic: '', name: 'Test Issue', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, @@ -213,6 +215,86 @@ describe('IssueDetail — existing issue', () => { (component as any).saveIssue(); expect(store.issues().length).toBe(countBefore); }); + + it('does not persist when dateValidationError is set', async () => { + (component as any).issue.name = 'Has Dates'; + (component as any).issue.startDate = '2026-02-01'; + (component as any).issue.endDate = '2026-01-01'; + await (component as any).saveIssue(); + expect(store.getById(1)?.name).not.toBe('Has Dates'); + }); + }); + + describe('dateValidationError', () => { + it('returns null when both dates are empty', () => { + (component as any).issue.startDate = ''; + (component as any).issue.endDate = ''; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns null when only startDate is set', () => { + (component as any).issue.startDate = '2026-01-01'; + (component as any).issue.endDate = ''; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns null when only endDate is set', () => { + (component as any).issue.startDate = ''; + (component as any).issue.endDate = '2026-01-31'; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns null when startDate equals endDate', () => { + (component as any).issue.startDate = '2026-01-15'; + (component as any).issue.endDate = '2026-01-15'; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns null when startDate is before endDate', () => { + (component as any).issue.startDate = '2026-01-01'; + (component as any).issue.endDate = '2026-01-31'; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns an error when startDate is after endDate', () => { + (component as any).issue.startDate = '2026-02-01'; + (component as any).issue.endDate = '2026-01-01'; + expect((component as any).dateValidationError).toContain('supérieure à la date de fin'); + }); + + it('returns null when dependency has no endDate and startDate is set', async () => { + store.upsert(makeIssue({ id: 10, startDate: '2026-01-01', endDate: '' })); + (component as any).issue.dependsOnIds = [10]; + (component as any).issue.startDate = '2025-12-01'; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns an error when startDate is before dependency endDate (Finish-to-Start)', async () => { + store.upsert(makeIssue({ id: 10, endDate: '2026-02-01' })); + (component as any).issue.dependsOnIds = [10]; + (component as any).issue.startDate = '2026-01-15'; + expect((component as any).dateValidationError).toContain('#10'); + }); + + it('returns null when startDate equals dependency endDate', async () => { + store.upsert(makeIssue({ id: 10, endDate: '2026-02-01' })); + (component as any).issue.dependsOnIds = [10]; + (component as any).issue.startDate = '2026-02-01'; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns null when startDate is after dependency endDate', async () => { + store.upsert(makeIssue({ id: 10, endDate: '2026-02-01' })); + (component as any).issue.dependsOnIds = [10]; + (component as any).issue.startDate = '2026-02-15'; + expect((component as any).dateValidationError).toBeNull(); + }); + + it('returns null when there are no dependencies', () => { + (component as any).issue.dependsOnIds = []; + (component as any).issue.startDate = '2026-01-01'; + expect((component as any).dateValidationError).toBeNull(); + }); }); describe('deleteIssue', () => { diff --git a/src/app/issues/issue-detail/issue-detail.ts b/src/app/issues/issue-detail/issue-detail.ts index 39cb3a9..ac1e094 100644 --- a/src/app/issues/issue-detail/issue-detail.ts +++ b/src/app/issues/issue-detail/issue-detail.ts @@ -170,6 +170,8 @@ export class IssueDetail { assignee: '', epic: this.issue.name, name, + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, @@ -235,6 +237,22 @@ export class IssueDetail { return !!this.issue.epic; } + protected get dateValidationError(): string | null { + const { startDate, endDate } = this.issue; + if (startDate && endDate && startDate > endDate) { + return 'La date de début ne peut pas être supérieure à la date de fin.'; + } + if (startDate && this.issue.dependsOnIds.length > 0) { + for (const depId of this.issue.dependsOnIds) { + const dep = this.issuesStore.getById(depId); + if (dep?.endDate && startDate < dep.endDate) { + return `La date de début ne peut pas être antérieure à la date de fin de la dépendance #${depId}.`; + } + } + } + return null; + } + protected startEditDescription(): void { this._descriptionBeforeEdit = this.issue.description; this.editingDescription = true; @@ -358,6 +376,7 @@ export class IssueDetail { protected async saveIssue(explicit = false): Promise { if (this.isNewIssueRoute && !explicit) return; if (!this.issue.name.trim()) return; + if (this.dateValidationError) return; const saved = await this.issuesStore.upsert(this.issue); this.issue = { ...saved }; if (this.isNewIssueRoute) { @@ -413,6 +432,8 @@ export class IssueDetail { assignee: '', epic: '', name: '', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, @@ -434,6 +455,8 @@ export class IssueDetail { assignee: '', epic: '', name: '', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, diff --git a/src/app/issues/issues.spec.ts b/src/app/issues/issues.spec.ts index 609aba3..a0b75d9 100644 --- a/src/app/issues/issues.spec.ts +++ b/src/app/issues/issues.spec.ts @@ -13,6 +13,8 @@ const makeIssue = (overrides: Partial = {}): IssueEntity => ({ assignee: '', epic: '', name: 'Test Issue', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, diff --git a/src/app/issues/issues.store.spec.ts b/src/app/issues/issues.store.spec.ts index a176185..2db28f4 100644 --- a/src/app/issues/issues.store.spec.ts +++ b/src/app/issues/issues.store.spec.ts @@ -10,6 +10,8 @@ const makeIssue = (overrides: Partial = {}): IssueEntity => ({ assignee: '', epic: '', name: 'Test Issue', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, @@ -156,6 +158,31 @@ describe('IssuesStore', () => { await p; expect(store.getById(994)?.estimatedTime).toBeNull(); }); + + it('defaults startDate to empty string when missing in API response', async () => { + const issue = { ...makeIssue({ id: 0 }), startDate: undefined } as any; + const p = store.upsert(issue); + httpMock.expectOne({ method: 'POST', url: `${API_BASE_URL}/issues` }).flush({ ...makeIssue({ id: 993 }), startDate: undefined }); + await p; + expect(store.getById(993)?.startDate).toBe(''); + }); + + it('defaults endDate to empty string when missing in API response', async () => { + const issue = { ...makeIssue({ id: 0 }), endDate: undefined } as any; + const p = store.upsert(issue); + httpMock.expectOne({ method: 'POST', url: `${API_BASE_URL}/issues` }).flush({ ...makeIssue({ id: 992 }), endDate: undefined }); + await p; + expect(store.getById(992)?.endDate).toBe(''); + }); + + it('preserves startDate and endDate when provided', async () => { + const issue = makeIssue({ id: 0, startDate: '2026-01-01', endDate: '2026-01-31' }); + const p = store.upsert(issue); + httpMock.expectOne({ method: 'POST', url: `${API_BASE_URL}/issues` }).flush(makeIssue({ id: 991, startDate: '2026-01-01', endDate: '2026-01-31' })); + await p; + expect(store.getById(991)?.startDate).toBe('2026-01-01'); + expect(store.getById(991)?.endDate).toBe('2026-01-31'); + }); }); describe('deleteById', () => { diff --git a/src/app/issues/issues.store.ts b/src/app/issues/issues.store.ts index 2af2a26..3a1d0c8 100644 --- a/src/app/issues/issues.store.ts +++ b/src/app/issues/issues.store.ts @@ -20,6 +20,8 @@ export type IssueEntity = { assignee: string; epic: string; name: string; + startDate: string; + endDate: string; dueDate: string; description: string; estimatedTime: number | null; @@ -110,6 +112,8 @@ export class IssuesStore { return { ...issue, type: issue.type ?? 'Story', + startDate: issue.startDate ?? '', + endDate: issue.endDate ?? '', estimatedTime: issue.estimatedTime ?? null, dependsOnIds: normalizedDependencies, comments: Array.isArray(issue.comments) diff --git a/src/app/milestones/milestone-detail/milestone-detail.spec.ts b/src/app/milestones/milestone-detail/milestone-detail.spec.ts index 07d53b5..e67a699 100644 --- a/src/app/milestones/milestone-detail/milestone-detail.spec.ts +++ b/src/app/milestones/milestone-detail/milestone-detail.spec.ts @@ -14,6 +14,8 @@ const makeIssue = (overrides: Partial = {}): IssueEntity => ({ assignee: '', epic: '', name: 'Test Issue', + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null, diff --git a/src/app/milestones/milestone-detail/milestone-detail.ts b/src/app/milestones/milestone-detail/milestone-detail.ts index bbdadf5..ba33c26 100644 --- a/src/app/milestones/milestone-detail/milestone-detail.ts +++ b/src/app/milestones/milestone-detail/milestone-detail.ts @@ -141,6 +141,8 @@ export class MilestoneDetail { assignee: '', epic: '', name, + startDate: '', + endDate: '', dueDate: '', description: '', estimatedTime: null,