Add Epic issue type and enhance issue detail display with epic-related information

This commit is contained in:
Cédric OLIVIER
2026-05-22 18:57:06 +02:00
parent dc6135ee95
commit 8bd2b4853f
4 changed files with 124 additions and 7 deletions
@@ -150,6 +150,78 @@
min-height: 8rem;
}
.epic-issues-card {
margin-top: 1rem;
background-color: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
padding: 1rem;
}
.epic-issues-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
}
.epic-issues-header h2 {
margin: 0;
font-size: 1.1rem;
}
.epic-issues-header span {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
padding: 0.2rem 0.5rem;
border-radius: 999px;
background: #dbeafe;
color: #1d4ed8;
font-weight: 700;
}
.epic-empty {
margin: 0;
color: #6b7280;
}
.epic-issues-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.75rem;
}
.epic-issue-item {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.85rem 1rem;
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
background: #f9fafb;
}
.epic-issue-item strong,
.epic-issue-item p {
display: block;
}
.epic-issue-item p {
margin: 0.25rem 0 0;
color: #6b7280;
}
.epic-issue-item span {
color: #374151;
font-weight: 600;
white-space: nowrap;
}
.detail-card {
background-color: #ffffff;
border: 1px solid #e5e7eb;
+38 -6
View File
@@ -56,12 +56,19 @@
</select>
</td>
</tr>
<tr>
<th>Epic</th>
<td>
<input type="text" [(ngModel)]="issue.epic" (blur)="saveIssue()" />
</td>
</tr>
@if (!isEpicIssue) {
<tr>
<th>Epic</th>
<td>
<select [(ngModel)]="issue.epic" (change)="saveIssue()">
<option value="">-</option>
@for (epicIssue of epicIssues; track epicIssue.id) {
<option [value]="epicIssue.name">{{ epicIssue.name }}</option>
}
</select>
</td>
</tr>
}
<tr>
<th>Depend de</th>
<td>
@@ -118,4 +125,29 @@
</table>
</section>
@if (isEpicIssue) {
<section class="epic-issues-card" aria-label="Issues composant cet epic">
<div class="epic-issues-header">
<h2>Issues composant cet Epic</h2>
<span>{{ composedIssues.length }}</span>
</div>
@if (composedIssues.length === 0) {
<p class="epic-empty">Aucune issue ne compose encore cet Epic.</p>
} @else {
<ul class="epic-issues-list">
@for (composedIssue of composedIssues; track composedIssue.id) {
<li class="epic-issue-item">
<div>
<strong>#{{ composedIssue.id }} - {{ composedIssue.name || 'Sans nom' }}</strong>
<p>{{ composedIssue.type }} · {{ composedIssue.status }}</p>
</div>
<span>{{ composedIssue.assignee || 'Non assigné' }}</span>
</li>
}
</ul>
}
</section>
}
@@ -27,6 +27,7 @@ export class IssueDetail {
];
protected readonly typeOptions: IssueEntity['type'][] = [
'Epic',
'Bug',
'Study',
'Story',
@@ -59,6 +60,18 @@ export class IssueDetail {
this.issue.type = value;
}
protected get epicIssues(): IssueEntity[] {
return this.issues().filter((issue) => issue.type === 'Epic');
}
protected get composedIssues(): IssueEntity[] {
return this.issues().filter((issue) => issue.dependsOnIds.includes(this.issue.id));
}
protected get isEpicIssue(): boolean {
return this.issueTypeValue === 'Epic';
}
protected saveIssue(): void {
this.issuesStore.upsert(this.issue);
if (this.isNewIssueRoute) {
+1 -1
View File
@@ -4,7 +4,7 @@ const ISSUES_STORAGE_KEY = 'bonsai.issues';
export type IssueStatus = 'draft' | 'todo' | 'done' | 'in-progress';
export type IssuePriority = 'Basse' | 'Moyenne' | 'Haute';
export type IssueType = 'Bug' | 'Study' | 'Story' | 'Technical Story';
export type IssueType = 'Epic' | 'Bug' | 'Study' | 'Story' | 'Technical Story';
export type IssueEntity = {
id: number;