Ajoute du tableau de bord
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
<!-- suppress HtmlUnknownAttribute -->
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-3 mb-4">
|
||||
<div>
|
||||
<h1 class="h2 mb-2">Tableau de bord</h1>
|
||||
<p class="text-secondary mb-0">Vue d'ensemble du projet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card shadow-sm kpi-card" role="button" tabindex="0" (click)="navigateToIssues()" (keydown.enter)="navigateToIssues()">
|
||||
<div class="card-body">
|
||||
<div class="kpi-label">Issues totales</div>
|
||||
<div class="kpi-value">{{ totalIssues() }}</div>
|
||||
<div class="kpi-sub text-secondary">{{ completionRate() }}% terminées</div>
|
||||
<div class="progress mt-2" style="height: 4px;">
|
||||
<div class="progress-bar bg-success" role="progressbar" [style.width.%]="completionRate()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card shadow-sm kpi-card">
|
||||
<div class="card-body">
|
||||
<div class="kpi-label">En cours</div>
|
||||
<div class="kpi-value" style="color: #9a3412;">{{ statusCounts().inProgress }}</div>
|
||||
<div class="kpi-sub text-secondary">{{ statusCounts().todo }} à faire</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card shadow-sm kpi-card" [class.kpi-card--alert]="overdueCount() > 0">
|
||||
<div class="card-body">
|
||||
<div class="kpi-label">En retard</div>
|
||||
<div class="kpi-value" [class.text-danger]="overdueCount() > 0" [class.text-secondary]="overdueCount() === 0">
|
||||
{{ overdueCount() }}
|
||||
</div>
|
||||
<div class="kpi-sub text-secondary">issues dépassées</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card shadow-sm kpi-card" role="button" tabindex="0" (click)="navigateToMilestones()" (keydown.enter)="navigateToMilestones()">
|
||||
<div class="card-body">
|
||||
<div class="kpi-label">Milestones</div>
|
||||
<div class="kpi-value">{{ totalMilestones() }}</div>
|
||||
<div class="kpi-sub text-secondary">{{ activeMilestones().length }} en cours</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statuts & Types -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white fw-semibold small">Répartition par statut</div>
|
||||
<div class="card-body">
|
||||
@for (item of statusItems(); track item.status) {
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<span
|
||||
class="status-badge"
|
||||
[style.background]="statusBadge(item.status).bg"
|
||||
[style.color]="statusBadge(item.status).color"
|
||||
>{{ statusBadge(item.status).label }}</span>
|
||||
<span class="text-secondary small">{{ item.count }}</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 5px;">
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
[style.width.%]="totalIssues() > 0 ? (item.count / totalIssues() * 100) : 0"
|
||||
[style.background]="statusBadge(item.status).color"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (totalIssues() === 0) {
|
||||
<p class="text-secondary text-center py-2 mb-0">Aucune issue.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white fw-semibold small">Répartition par type</div>
|
||||
<div class="card-body">
|
||||
@for (item of issuesByType(); track item.type) {
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span class="type-icon" [style.background]="item.icon.bg">{{ item.icon.letter }}</span>
|
||||
<span class="flex-grow-1 small">{{ item.type }}</span>
|
||||
<span class="badge bg-secondary rounded-pill">{{ item.count }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (issuesByType().length === 0) {
|
||||
<p class="text-secondary text-center py-2 mb-0">Aucune issue.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Haute priorité & Milestones en cours -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold small">Haute priorité — ouvertes</span>
|
||||
@if (highPriorityIssues().length > 0) {
|
||||
<span class="badge bg-danger rounded-pill">{{ highPriorityIssues().length }}</span>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@for (issue of highPriorityIssues(); track issue.id) {
|
||||
<div
|
||||
class="issue-row"
|
||||
tabindex="0"
|
||||
(click)="openIssue(issue.id)"
|
||||
(keydown.enter)="openIssue(issue.id)"
|
||||
>
|
||||
<span class="type-icon" [style.background]="typeIcon(issue.type).bg">{{ typeIcon(issue.type).letter }}</span>
|
||||
<span class="flex-grow-1 small text-truncate">{{ issue.name }}</span>
|
||||
<span
|
||||
class="priority-symbol"
|
||||
[style.color]="priorityDisplay(issue.priority).color"
|
||||
[title]="priorityDisplay(issue.priority).label"
|
||||
>{{ priorityDisplay(issue.priority).symbol }}</span>
|
||||
<span
|
||||
class="status-badge-sm"
|
||||
[style.background]="statusBadge(issue.status).bg"
|
||||
[style.color]="statusBadge(issue.status).color"
|
||||
>{{ statusBadge(issue.status).label }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (highPriorityIssues().length === 0) {
|
||||
<p class="text-secondary text-center py-4 mb-0">Aucune issue haute priorité ouverte.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white fw-semibold small">Milestones en cours</div>
|
||||
<div class="card-body p-0">
|
||||
@for (m of activeMilestones(); track m.id) {
|
||||
<div
|
||||
class="milestone-row"
|
||||
tabindex="0"
|
||||
(click)="openMilestone(m.id)"
|
||||
(keydown.enter)="openMilestone(m.id)"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<span class="small fw-semibold text-truncate flex-grow-1 me-2">{{ m.name }}</span>
|
||||
<span class="small text-secondary text-nowrap">{{ m.progress }}%</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="progress flex-grow-1" style="height: 5px;">
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
[style.width.%]="m.progress"
|
||||
[attr.aria-valuenow]="m.progress"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
<span class="small text-secondary text-nowrap">{{ formatDate(m.dueDate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (activeMilestones().length === 0) {
|
||||
<p class="text-secondary text-center py-4 mb-0">Tous les milestones sont terminés.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Échéances proches -->
|
||||
@if (upcomingIssues().length > 0) {
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold small">Échéances dans les 14 prochains jours</span>
|
||||
<span class="badge bg-warning text-dark rounded-pill">{{ upcomingIssues().length }}</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@for (issue of upcomingIssues(); track issue.id) {
|
||||
<div
|
||||
class="issue-row"
|
||||
tabindex="0"
|
||||
(click)="openIssue(issue.id)"
|
||||
(keydown.enter)="openIssue(issue.id)"
|
||||
>
|
||||
<span class="type-icon" [style.background]="typeIcon(issue.type).bg">{{ typeIcon(issue.type).letter }}</span>
|
||||
<span class="flex-grow-1 small text-truncate">{{ issue.name }}</span>
|
||||
<span class="small text-secondary text-nowrap">{{ formatDate(issue.dueDate) }}</span>
|
||||
<span
|
||||
class="status-badge-sm"
|
||||
[style.background]="statusBadge(issue.status).bg"
|
||||
[style.color]="statusBadge(issue.status).color"
|
||||
>{{ statusBadge(issue.status).label }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
Reference in New Issue
Block a user