i18n: DashboardView traducido (stats, epics, HU table, status labels)

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