@@ -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;
+}
|