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;
|
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 {
|
.detail-card {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
|
|||||||
@@ -56,12 +56,19 @@
|
|||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
@if (!isEpicIssue) {
|
||||||
<th>Epic</th>
|
<tr>
|
||||||
<td>
|
<th>Epic</th>
|
||||||
<input type="text" [(ngModel)]="issue.epic" (blur)="saveIssue()" />
|
<td>
|
||||||
</td>
|
<select [(ngModel)]="issue.epic" (change)="saveIssue()">
|
||||||
</tr>
|
<option value="">-</option>
|
||||||
|
@for (epicIssue of epicIssues; track epicIssue.id) {
|
||||||
|
<option [value]="epicIssue.name">{{ epicIssue.name }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Depend de</th>
|
<th>Depend de</th>
|
||||||
<td>
|
<td>
|
||||||
@@ -118,4 +125,29 @@
|
|||||||
</table>
|
</table>
|
||||||
</section>
|
</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'][] = [
|
protected readonly typeOptions: IssueEntity['type'][] = [
|
||||||
|
'Epic',
|
||||||
'Bug',
|
'Bug',
|
||||||
'Study',
|
'Study',
|
||||||
'Story',
|
'Story',
|
||||||
@@ -59,6 +60,18 @@ export class IssueDetail {
|
|||||||
this.issue.type = value;
|
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 {
|
protected saveIssue(): void {
|
||||||
this.issuesStore.upsert(this.issue);
|
this.issuesStore.upsert(this.issue);
|
||||||
if (this.isNewIssueRoute) {
|
if (this.isNewIssueRoute) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const ISSUES_STORAGE_KEY = 'bonsai.issues';
|
|||||||
|
|
||||||
export type IssueStatus = 'draft' | 'todo' | 'done' | 'in-progress';
|
export type IssueStatus = 'draft' | 'todo' | 'done' | 'in-progress';
|
||||||
export type IssuePriority = 'Basse' | 'Moyenne' | 'Haute';
|
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 = {
|
export type IssueEntity = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user