@@ -578,7 +578,7 @@ describe('IssueComments', () => {
|
||||
describe('statusLabel', () => {
|
||||
it('returns label and colors for done status', () => {
|
||||
const s = (component as any).statusLabel('done');
|
||||
expect(s.label).toBe('Terminé');
|
||||
expect(s.label).toBe('TERMINÉ');
|
||||
expect(s.color).toBe('#166534');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { marked } from 'marked';
|
||||
import { handleImagePaste, insertAtSelection } from '../paste-image.util';
|
||||
import { IssueComment, IssueEntity, IssuesStore } from '../issues.store';
|
||||
import { StatusEntity, StatusesStore } from '../../settings/statuses/statuses.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-issue-comments',
|
||||
@@ -15,6 +16,7 @@ import { IssueComment, IssueEntity, IssuesStore } from '../issues.store';
|
||||
export class IssueComments {
|
||||
private readonly issuesStore = inject(IssuesStore);
|
||||
private readonly sanitizer = inject(DomSanitizer);
|
||||
private readonly statusesStore = inject(StatusesStore);
|
||||
|
||||
readonly issueId = input.required<number>();
|
||||
|
||||
@@ -140,14 +142,8 @@ export class IssueComments {
|
||||
return map[type] ?? { letter: '?', bg: '#6b7280' };
|
||||
}
|
||||
|
||||
protected statusLabel(status: IssueEntity['status']): { label: string; bg: string; color: string } {
|
||||
const map: Record<IssueEntity['status'], { label: string; bg: string; color: string }> = {
|
||||
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 statusLabel(status: IssueEntity['status']): StatusEntity {
|
||||
return this.statusesStore.getById(status) ?? { id: status, label: status, bg: '#e2e8f0', color: '#475569', order: 99 };
|
||||
}
|
||||
|
||||
protected startCreateTask(commentId: number): void {
|
||||
|
||||
@@ -29,19 +29,19 @@
|
||||
@if (statusMenuOpen) {
|
||||
<div class="status-backdrop" (click)="closeStatusMenu()"></div>
|
||||
<ul class="status-dropdown dropdown-menu show">
|
||||
@for (status of statusOptions; track status) {
|
||||
@for (status of statusOptions; track status.id) {
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-item d-flex align-items-center gap-2"
|
||||
[class.active]="issue.status === status"
|
||||
(click)="selectStatus(status)"
|
||||
[class.active]="issue.status === status.id"
|
||||
(click)="selectStatus(status.id)"
|
||||
>
|
||||
<span
|
||||
class="status-badge"
|
||||
[style.background]="statusBadge(status).bg"
|
||||
[style.color]="statusBadge(status).color"
|
||||
>{{ statusBadge(status).label }}</span>
|
||||
[style.background]="status.bg"
|
||||
[style.color]="status.color"
|
||||
>{{ status.label }}</span>
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IssueEntity, IssuesStore } from '../issues.store';
|
||||
import { IssueComments } from '../issue-comments/issue-comments';
|
||||
import { handleImagePaste, insertAtSelection } from '../paste-image.util';
|
||||
import { MilestoneEntity, MilestonesStore } from '../../milestones/milestones.store';
|
||||
import { StatusEntity, StatusesStore } from '../../settings/statuses/statuses.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-issue-detail',
|
||||
@@ -21,6 +22,7 @@ export class IssueDetail {
|
||||
private readonly issuesStore = inject(IssuesStore);
|
||||
private readonly milestonesStore = inject(MilestonesStore);
|
||||
private readonly sanitizer = inject(DomSanitizer);
|
||||
protected readonly statusesStore = inject(StatusesStore);
|
||||
protected readonly isNewIssueRoute = this.route.snapshot.routeConfig?.path === 'issues/new';
|
||||
|
||||
protected issue: IssueEntity = this.buildIssue();
|
||||
@@ -64,12 +66,9 @@ export class IssueDetail {
|
||||
protected showCreateInEpic = false;
|
||||
protected newIssueName = '';
|
||||
|
||||
protected readonly statusOptions: IssueEntity['status'][] = [
|
||||
'draft',
|
||||
'todo',
|
||||
'in-progress',
|
||||
'done',
|
||||
];
|
||||
protected get statusOptions(): StatusEntity[] {
|
||||
return [...this.statusesStore.statuses()].sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
protected readonly typeOptions: IssueEntity['type'][] = [
|
||||
'Epic',
|
||||
@@ -279,14 +278,8 @@ export class IssueDetail {
|
||||
return map[type] ?? { letter: '?', bg: '#6b7280' };
|
||||
}
|
||||
|
||||
protected statusBadge(status: IssueEntity['status']): { label: string; bg: string; color: string } {
|
||||
const map: Record<IssueEntity['status'], { label: string; bg: string; color: string }> = {
|
||||
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 statusBadge(status: IssueEntity['status']): StatusEntity {
|
||||
return this.statusesStore.getById(status) ?? { id: status, label: status, bg: '#e2e8f0', color: '#475569', order: 99 };
|
||||
}
|
||||
|
||||
protected priorityDisplay(priority: IssueEntity['priority']): { symbol: string; color: string; label: string } {
|
||||
|
||||
@@ -69,12 +69,12 @@
|
||||
</button>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
@for (status of statusOptions; track status) {
|
||||
@for (status of statusOptions; track status.id) {
|
||||
<li>
|
||||
<button class="dropdown-item d-flex align-items-center gap-2" (click)="toggleStatus(status, $event)">
|
||||
<span class="filter-check">@if (selectedStatuses.has(status)) { ✓ }</span>
|
||||
<span class="status-badge" [style.background]="statusBadge(status).bg" [style.color]="statusBadge(status).color">
|
||||
{{ statusBadge(status).label }}
|
||||
<button class="dropdown-item d-flex align-items-center gap-2" (click)="toggleStatus(status.id, $event)">
|
||||
<span class="filter-check">@if (selectedStatuses.has(status.id)) { ✓ }</span>
|
||||
<span class="status-badge" [style.background]="status.bg" [style.color]="status.color">
|
||||
{{ status.label }}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -2,7 +2,7 @@ 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 IssueStatus = string;
|
||||
export type IssuePriority = 'TRES_FAIBLE' | 'BASSE' | 'MOYENNE' | 'HAUTE' | 'TRES_HAUTE';
|
||||
export type IssueType = 'Epic' | 'Bug' | 'Study' | 'Story' | 'Task' | 'Technical Story';
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FormsModule } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { IssueEntity, IssueStatus, IssuesStore } from './issues.store';
|
||||
import { MilestoneEntity, MilestonesStore } from '../milestones/milestones.store';
|
||||
import { StatusEntity, StatusesStore } from '../settings/statuses/statuses.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-issues',
|
||||
@@ -14,6 +15,7 @@ export class Issues {
|
||||
private readonly router = inject(Router);
|
||||
private readonly issuesStore = inject(IssuesStore);
|
||||
private readonly milestonesStore = inject(MilestonesStore);
|
||||
protected readonly statusesStore = inject(StatusesStore);
|
||||
|
||||
constructor() {
|
||||
this.issuesStore.load();
|
||||
@@ -33,9 +35,9 @@ export class Issues {
|
||||
'Epic', 'Bug', 'Study', 'Story', 'Task', 'Technical Story',
|
||||
];
|
||||
|
||||
protected readonly statusOptions: IssueStatus[] = [
|
||||
'draft', 'todo', 'in-progress', 'done',
|
||||
];
|
||||
protected get statusOptions(): StatusEntity[] {
|
||||
return [...this.statusesStore.statuses()].sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
protected getMilestoneForIssue(issueId: number): MilestoneEntity | undefined {
|
||||
return this.milestones().find((m) => m.issueIds.includes(issueId));
|
||||
@@ -192,13 +194,7 @@ export class Issues {
|
||||
return map[type] ?? 'text-bg-secondary';
|
||||
}
|
||||
|
||||
protected statusBadge(status: IssueEntity['status']): { label: string; bg: string; color: string } {
|
||||
const map: Record<IssueEntity['status'], { label: string; bg: string; color: string }> = {
|
||||
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 statusBadge(status: IssueStatus): StatusEntity {
|
||||
return this.statusesStore.getById(status) ?? { id: status, label: status, bg: '#e2e8f0', color: '#475569', order: 99 };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user