From 258fb1cd805ac5fab29a1a8abbc24a0e9e603618 Mon Sep 17 00:00:00 2001 From: Gato Date: Tue, 26 May 2026 07:54:10 +0200 Subject: [PATCH] Modification visuel status et type issue --- .claude/settings.local.json | 10 +++- src/app/issues/issue-detail/issue-detail.css | 43 +++++++++++++++ src/app/issues/issue-detail/issue-detail.html | 53 +++++++++++++++---- .../issues/issue-detail/issue-detail.spec.ts | 30 +++++------ src/app/issues/issue-detail/issue-detail.ts | 36 ++++++++++++- src/app/issues/issues.css | 1 + src/app/issues/issues.html | 29 +++++++--- src/app/issues/issues.ts | 22 ++++++++ src/styles.css | 33 ++++++++++++ 9 files changed, 220 insertions(+), 37 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8594700..6b9aac8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,15 @@ "Bash(npx ng *)", "Bash(npm start *)", "Bash(xargs cat -n)", - "Bash(xargs ls -la)" + "Bash(xargs ls -la)", + "Read(//home/Gato/IdeaProjects/Bonsai-webapp/src/**)", + "Read(//var/home/Gato/IdeaProjects/Bonsai-webapp/src/**)" + ], + "additionalDirectories": [ + "/home/Gato/IdeaProjects/Bonsai-webapp/src/app/issues", + "/var/home/Gato/IdeaProjects/Bonsai-webapp/src/app/issues", + "/home/Gato/IdeaProjects/Bonsai-webapp/src", + "/var/home/Gato/IdeaProjects/Bonsai-webapp/src" ] } } diff --git a/src/app/issues/issue-detail/issue-detail.css b/src/app/issues/issue-detail/issue-detail.css index ea570c7..bdd9d8c 100644 --- a/src/app/issues/issue-detail/issue-detail.css +++ b/src/app/issues/issue-detail/issue-detail.css @@ -27,6 +27,49 @@ font-weight: 400; } +/* Status split button */ +.status-split-wrapper { + position: relative; +} + +.status-main-btn { + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.04em; + padding: 0.28rem 0.6rem; + border: 1px solid; + border-right: none; + border-radius: 3px 0 0 3px; + white-space: nowrap; + cursor: default; +} + +.status-toggle-btn { + padding: 0.28rem 0.45rem; + border: 1px solid; + border-radius: 0 3px 3px 0; + cursor: pointer; + transition: filter 0.1s; +} + +.status-toggle-btn:hover { + filter: brightness(0.92); +} + +.status-backdrop { + position: fixed; + inset: 0; + z-index: 9; +} + +.status-dropdown { + position: absolute; + right: 0; + top: calc(100% + 0.3rem); + min-width: 10rem; + z-index: 10; +} + /* More menu */ .more-wrapper { position: relative; diff --git a/src/app/issues/issue-detail/issue-detail.html b/src/app/issues/issue-detail/issue-detail.html index 473331f..cfe8de5 100644 --- a/src/app/issues/issue-detail/issue-detail.html +++ b/src/app/issues/issue-detail/issue-detail.html @@ -3,20 +3,51 @@
- {{ issue.type }} + {{ typeIcon(issue.type).letter }} #{{ issue.id }}
- +
@if (!isNewIssueRoute) {
@@ -202,7 +233,7 @@ (click)="openComposedIssue(composedIssue.id)" >
- {{ composedIssue.type }} + {{ typeIcon(composedIssue.type).letter }} #{{ composedIssue.id }} – {{ composedIssue.name || 'Sans nom' }}
diff --git a/src/app/issues/issue-detail/issue-detail.spec.ts b/src/app/issues/issue-detail/issue-detail.spec.ts index 6e4e02b..9a38190 100644 --- a/src/app/issues/issue-detail/issue-detail.spec.ts +++ b/src/app/issues/issue-detail/issue-detail.spec.ts @@ -287,34 +287,30 @@ describe('IssueDetail — existing issue', () => { }); }); - describe('getBadgeClass / typeBadgeClass', () => { - it('typeBadgeClass returns class for current issue type', () => { + describe('typeIcon', () => { + it('typeIcon returns correct icon for current issue type', () => { (component as any).issue.type = 'Bug'; - expect((component as any).typeBadgeClass).toBe('text-bg-danger'); + expect((component as any).typeIcon('Bug')).toEqual({ letter: 'B', bg: '#dc2626' }); }); - it('getBadgeClass maps Bug to text-bg-danger', () => { - expect((component as any).getBadgeClass('Bug')).toBe('text-bg-danger'); + it('typeIcon maps Epic correctly', () => { + expect((component as any).typeIcon('Epic')).toEqual({ letter: 'E', bg: '#7c3aed' }); }); - it('getBadgeClass maps Study to text-bg-secondary', () => { - expect((component as any).getBadgeClass('Study')).toBe('text-bg-secondary'); + it('typeIcon maps Story correctly', () => { + expect((component as any).typeIcon('Story')).toEqual({ letter: 'S', bg: '#16a34a' }); }); - it('getBadgeClass maps Story to text-bg-success', () => { - expect((component as any).getBadgeClass('Story')).toBe('text-bg-success'); + it('typeIcon maps Task correctly', () => { + expect((component as any).typeIcon('Task')).toEqual({ letter: 'T', bg: '#2563eb' }); }); - it('getBadgeClass maps Task to text-bg-primary', () => { - expect((component as any).getBadgeClass('Task')).toBe('text-bg-primary'); + it('typeIcon maps Technical Story correctly', () => { + expect((component as any).typeIcon('Technical Story')).toEqual({ letter: 'TS', bg: '#d97706' }); }); - it('getBadgeClass maps Technical Story to text-bg-warning', () => { - expect((component as any).getBadgeClass('Technical Story')).toBe('text-bg-warning'); - }); - - it('getBadgeClass maps Epic to text-bg-info', () => { - expect((component as any).getBadgeClass('Epic')).toBe('text-bg-info'); + it('typeIcon maps Study correctly', () => { + expect((component as any).typeIcon('Study')).toEqual({ letter: 'St', bg: '#6b7280' }); }); }); diff --git a/src/app/issues/issue-detail/issue-detail.ts b/src/app/issues/issue-detail/issue-detail.ts index 43413d8..df1537c 100644 --- a/src/app/issues/issue-detail/issue-detail.ts +++ b/src/app/issues/issue-detail/issue-detail.ts @@ -23,6 +23,7 @@ export class IssueDetail { protected issue: IssueEntity = this.buildIssue(); protected readonly issues = this.issuesStore.issues; protected moreMenuOpen = false; + protected statusMenuOpen = false; constructor() { const idParam = this.route.snapshot.paramMap.get('id'); @@ -209,8 +210,26 @@ export class IssueDetail { return this.sanitizer.bypassSecurityTrustHtml(html); } - protected get typeBadgeClass(): string { - return this.getBadgeClass(this.issueTypeValue); + protected typeIcon(type: IssueEntity['type']): { letter: string; bg: string } { + const map: Record = { + Epic: { letter: 'E', bg: '#7c3aed' }, + Bug: { letter: 'B', bg: '#dc2626' }, + Story: { letter: 'S', bg: '#16a34a' }, + Task: { letter: 'T', bg: '#2563eb' }, + Study: { letter: 'St', bg: '#6b7280' }, + 'Technical Story':{ letter: 'TS', bg: '#d97706' }, + }; + return map[type] ?? { letter: '?', bg: '#6b7280' }; + } + + protected statusBadge(status: IssueEntity['status']): { label: string; bg: string; color: string } { + const map: Record = { + draft: { label: 'BROUILLON', bg: '#e2e8f0', color: '#475569' }, + todo: { label: 'À FAIRE', bg: '#dbeafe', color: '#1d4ed8' }, + 'in-progress': { label: 'EN COURS', bg: '#ffedd5', color: '#9a3412' }, + done: { label: 'TERMINÉ', bg: '#dcfce7', color: '#166534' }, + }; + return map[status] ?? { label: status, bg: '#e2e8f0', color: '#475569' }; } protected priorityDisplay(priority: IssueEntity['priority']): { symbol: string; color: string; label: string } { @@ -284,6 +303,19 @@ export class IssueDetail { this.moreMenuOpen = false; } + protected toggleStatusMenu(): void { + this.statusMenuOpen = !this.statusMenuOpen; + } + + protected closeStatusMenu(): void { + this.statusMenuOpen = false; + } + + protected async selectStatus(status: IssueEntity['status']): Promise { + this.statusMenuOpen = false; + await this.updateStatus(status); + } + private buildIssue(): IssueEntity { const idParam = this.route.snapshot.paramMap.get('id'); const isNewIssueRoute = this.route.snapshot.routeConfig?.path === 'issues/new'; diff --git a/src/app/issues/issues.css b/src/app/issues/issues.css index 13ebb0b..99ee86a 100644 --- a/src/app/issues/issues.css +++ b/src/app/issues/issues.css @@ -25,3 +25,4 @@ } + diff --git a/src/app/issues/issues.html b/src/app/issues/issues.html index aeaccaf..1c3c238 100644 --- a/src/app/issues/issues.html +++ b/src/app/issues/issues.html @@ -18,11 +18,16 @@ @for (type of typeOptions; track type) { + > + {{ typeIcon(type).letter }} + {{ type }} + }
@@ -50,7 +55,13 @@ > #{{ issue.id }} {{ issue.name }} - {{ issue.type }} + + {{ typeIcon(issue.type).letter }} + {{ priorityDisplay(issue.priority).symbol }} - {{ issue.status }} + + {{ statusBadge(issue.status).label }} + {{ issue.assignee }}
diff --git a/src/app/issues/issues.ts b/src/app/issues/issues.ts index 0843bb7..f46b37d 100644 --- a/src/app/issues/issues.ts +++ b/src/app/issues/issues.ts @@ -63,6 +63,18 @@ export class Issues { return map[priority] ?? { symbol: '?', color: '#6c757d', label: priority }; } + protected typeIcon(type: IssueEntity['type']): { letter: string; bg: string } { + const map: Record = { + Epic: { letter: 'E', bg: '#7c3aed' }, + Bug: { letter: 'B', bg: '#dc2626' }, + Story: { letter: 'S', bg: '#16a34a' }, + Task: { letter: 'T', bg: '#2563eb' }, + Study: { letter: 'St', bg: '#6b7280' }, + 'Technical Story':{ letter: 'TS', bg: '#d97706' }, + }; + return map[type] ?? { letter: '?', bg: '#6b7280' }; + } + protected typeBadgeClass(type: IssueEntity['type']): string { const map: Record = { Bug: 'text-bg-danger', @@ -74,4 +86,14 @@ export class Issues { }; return map[type] ?? 'text-bg-secondary'; } + + protected statusBadge(status: IssueEntity['status']): { label: string; bg: string; color: string } { + const map: Record = { + draft: { label: 'BROUILLON', bg: '#e2e8f0', color: '#475569' }, + todo: { label: 'À FAIRE', bg: '#dbeafe', color: '#1d4ed8' }, + 'in-progress': { label: 'EN COURS', bg: '#ffedd5', color: '#9a3412' }, + done: { label: 'TERMINÉ', bg: '#dcfce7', color: '#166534' }, + }; + return map[status] ?? { label: status, bg: '#e2e8f0', color: '#475569' }; + } } diff --git a/src/styles.css b/src/styles.css index 31dc510..b0f0a3e 100644 --- a/src/styles.css +++ b/src/styles.css @@ -9,3 +9,36 @@ body { margin: 0; background-color: #f8f9fa; } + +.markdown-body li { + margin-top: 0; + margin-bottom: 0.15rem; +} + +.markdown-body li > p { + margin: 0; +} + +.type-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 4px; + color: #fff; + font-size: 0.65rem; + font-weight: 700; + line-height: 1; + flex-shrink: 0; +} + +.status-badge { + display: inline-block; + padding: 0.2rem 0.55rem; + border-radius: 3px; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.04em; + white-space: nowrap; +}