i18n: DashboardView traducido (stats, epics, HU table, status labels)
This commit is contained in:
+34
-38
@@ -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>
|
||||||
@@ -141,9 +137,9 @@ const statusLabel = (status: string) => {
|
|||||||
<!-- Epics -->
|
<!-- Epics -->
|
||||||
<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
|
||||||
v-for="epic in workItems.epics"
|
v-for="epic in workItems.epics"
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user