Add Epic issue type and enhance issue detail display with epic-related information
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user