i18n: DashboardView traducido (stats, epics, HU table, status labels)
This commit is contained in:
+32
-36
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useProjectsStore } from '@/stores/projects'
|
||||
import { useWorkItemsStore } from '@/stores/workitems'
|
||||
import { Activity, FileText, Layers, Clock, ChevronRight } from 'lucide-vue-next'
|
||||
import { Activity, FileText, Layers, Clock } from 'lucide-vue-next'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
const { t } = useI18n()
|
||||
const projects = useProjectsStore()
|
||||
const workItems = useWorkItemsStore()
|
||||
|
||||
@@ -39,20 +41,14 @@ const statusVariant = (status: string) => {
|
||||
}
|
||||
|
||||
const statusLabel = (status: string) => {
|
||||
const map: Record<string, string> = {
|
||||
backlog: 'Backlog',
|
||||
todo: 'Por hacer',
|
||||
in_progress: 'En progreso',
|
||||
'in progress': 'En progreso',
|
||||
doing: 'Haciendo',
|
||||
wip: 'WIP',
|
||||
done: 'Hecho',
|
||||
completed: 'Completado',
|
||||
blocked: 'Bloqueado',
|
||||
review: 'Revisión',
|
||||
testing: 'Pruebas',
|
||||
}
|
||||
return map[status?.toLowerCase() || ''] || status || '—'
|
||||
const s = status?.toLowerCase() || ''
|
||||
if (['done', 'completed', 'closed', 'finalizado'].includes(s)) return t('status.completed')
|
||||
if (['in_progress', 'doing', 'wip', 'active', 'in progress', 'en progreso'].includes(s)) return t('status.inProgress')
|
||||
if (['blocked', 'bloqueado'].includes(s)) return t('status.blocked')
|
||||
if (['todo', 'por hacer'].includes(s)) return t('status.todo')
|
||||
if (['review', 'revisión'].includes(s)) return t('status.review')
|
||||
if (['testing', 'pruebas'].includes(s)) return t('status.testing')
|
||||
return status || '—'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -70,45 +66,45 @@ const statusLabel = (status: string) => {
|
||||
<div class="grid gap-3 @xl:grid-cols-2 @3xl:grid-cols-4">
|
||||
<Card class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Épicas</CardTitle>
|
||||
<CardTitle class="text-sm font-medium">{{ t('dashboard.epics') }}</CardTitle>
|
||||
<Layers class="size-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ workItems.totalEpics }}</div>
|
||||
<p class="text-xs text-muted-foreground">Actividades del cronograma</p>
|
||||
<p class="text-xs text-muted-foreground">{{ t('dashboard.epicsSubtitle') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">HUs</CardTitle>
|
||||
<CardTitle class="text-sm font-medium">{{ t('dashboard.hus') }}</CardTitle>
|
||||
<FileText class="size-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ workItems.totalHUs }}</div>
|
||||
<p class="text-xs text-muted-foreground">Historias de usuario</p>
|
||||
<p class="text-xs text-muted-foreground">{{ t('dashboard.husSubtitle') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">En progreso</CardTitle>
|
||||
<CardTitle class="text-sm font-medium">{{ t('dashboard.inProgress') }}</CardTitle>
|
||||
<Activity class="size-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ workItems.inProgressHUs }}</div>
|
||||
<p class="text-xs text-muted-foreground">HUs activas</p>
|
||||
<p class="text-xs text-muted-foreground">{{ t('dashboard.activeHus') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card class="bg-gradient-to-t from-primary/5 to-card shadow-xs dark:bg-card">
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Sesiones</CardTitle>
|
||||
<CardTitle class="text-sm font-medium">{{ t('dashboard.sessions') }}</CardTitle>
|
||||
<Clock class="size-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ workItems.totalSessions }}</div>
|
||||
<p class="text-xs text-muted-foreground">Bitácoras</p>
|
||||
<p class="text-xs text-muted-foreground">{{ t('dashboard.sessionsSubtitle') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -116,13 +112,13 @@ const statusLabel = (status: string) => {
|
||||
<!-- Description -->
|
||||
<Card>
|
||||
<CardHeader class="pb-2">
|
||||
<CardTitle class="text-sm font-medium">Descripción</CardTitle>
|
||||
<CardTitle class="text-sm font-medium">{{ t('dashboard.description') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p v-if="project.description" class="text-sm text-muted-foreground leading-relaxed line-clamp-4">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
<p v-else class="text-sm text-muted-foreground/50 italic">Sin descripción</p>
|
||||
<p v-else class="text-sm text-muted-foreground/50 italic">{{ t('dashboard.noDescription') }}</p>
|
||||
<div v-if="project.start_date || project.end_date" class="flex items-center gap-4 mt-3 text-xs text-muted-foreground">
|
||||
<span v-if="project.start_date">📅 {{ project.start_date }}</span>
|
||||
<span v-if="project.end_date">→ {{ project.end_date }}</span>
|
||||
@@ -142,7 +138,7 @@ const statusLabel = (status: string) => {
|
||||
<template v-else-if="workItems.epics.length > 0">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-3">
|
||||
Épicas · {{ workItems.totalEpics }}
|
||||
{{ t('dashboard.epicsCount', { count: workItems.totalEpics }) }}
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
<Card
|
||||
@@ -156,7 +152,7 @@ const statusLabel = (status: string) => {
|
||||
<span class="font-mono text-xs text-muted-foreground flex-shrink-0">
|
||||
{{ epic.code || `EP-${epic.id}` }}
|
||||
</span>
|
||||
<CardTitle class="text-sm truncate">{{ epic.name || epic.title || `Épica ${epic.id}` }}</CardTitle>
|
||||
<CardTitle class="text-sm truncate"> {{ epic.name || epic.title || t('dashboard.epicFallback', { id: epic.id }) }}</CardTitle>
|
||||
</div>
|
||||
<Badge :variant="statusVariant(epic.status || '')" class="text-xs flex-shrink-0">
|
||||
{{ statusLabel(epic.status || '') }}
|
||||
@@ -176,17 +172,17 @@ const statusLabel = (status: string) => {
|
||||
<!-- HUs Table -->
|
||||
<Card>
|
||||
<CardHeader class="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle class="text-sm font-medium">Historias de Usuario</CardTitle>
|
||||
<Badge variant="outline" class="text-xs">{{ workItems.userStories.length }} HUs</Badge>
|
||||
<CardTitle class="text-sm font-medium">{{ t('dashboard.userStoriesTitle') }}</CardTitle>
|
||||
<Badge variant="outline" class="text-xs">{{ t('dashboard.husCount', { count: workItems.userStories.length }) }}</Badge>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table v-if="workItems.userStories.length > 0">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[100px]">Código</TableHead>
|
||||
<TableHead>Título</TableHead>
|
||||
<TableHead class="w-[110px]">Estado</TableHead>
|
||||
<TableHead class="w-[90px] text-right">Prioridad</TableHead>
|
||||
<TableHead class="w-[100px]">{{ t('dashboard.code') }}</TableHead>
|
||||
<TableHead>{{ t('dashboard.title') }}</TableHead>
|
||||
<TableHead class="w-[110px]">{{ t('dashboard.status') }}</TableHead>
|
||||
<TableHead class="w-[90px] text-right">{{ t('dashboard.priority') }}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -209,7 +205,7 @@ const statusLabel = (status: string) => {
|
||||
</TableBody>
|
||||
</Table>
|
||||
<p v-else class="text-xs text-muted-foreground/50 italic text-center py-4">
|
||||
Sin historias de usuario
|
||||
{{ t('dashboard.noUserStories') }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -217,8 +213,8 @@ const statusLabel = (status: string) => {
|
||||
|
||||
<div v-else class="flex flex-1 items-center justify-center">
|
||||
<div class="text-center space-y-2">
|
||||
<p v-if="projects.loading" class="text-sm text-muted-foreground">Cargando...</p>
|
||||
<p v-else class="text-sm text-muted-foreground">Seleccioná un proyecto del panel lateral</p>
|
||||
<p v-if="projects.loading" class="text-sm text-muted-foreground">{{ t('common.loading') }}</p>
|
||||
<p v-else class="text-sm text-muted-foreground">{{ t('dashboard.selectProject') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user