i18n: UsersView completamente traducido (headers, labels, empty/error states)

This commit is contained in:
2026-05-27 20:02:53 -05:00
parent 27303ab3d4
commit efa68d8705
+21 -19
View File
@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { AgGridVue } from 'ag-grid-vue3' import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef } from 'ag-grid-community' import type { ColDef } from 'ag-grid-community'
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community' import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community'
@@ -18,6 +19,7 @@ import {
ModuleRegistry.registerModules([AllCommunityModule]) ModuleRegistry.registerModules([AllCommunityModule])
const { t } = useI18n()
const store = useUsersStore() const store = useUsersStore()
const gridReady = ref(false) const gridReady = ref(false)
@@ -63,10 +65,10 @@ const stats = computed(() => ({
}, {} as Record<string, number>), }, {} as Record<string, number>),
})) }))
const columnDefs: ColDef[] = [ const columnDefs = computed<ColDef[]>(() => [
{ {
field: 'full_name', field: 'full_name',
headerName: 'Nombre', headerName: t('users.name'),
flex: 2, flex: 2,
minWidth: 180, minWidth: 180,
filter: true, filter: true,
@@ -78,10 +80,10 @@ const columnDefs: ColDef[] = [
</div>` </div>`
}, },
}, },
{ field: 'email', headerName: 'Email', flex: 2, minWidth: 200, filter: true }, { field: 'email', headerName: t('users.email'), flex: 2, minWidth: 200, filter: true },
{ {
field: 'role', field: 'role',
headerName: 'Rol', headerName: t('users.role'),
width: 90, width: 90,
filter: true, filter: true,
cellRenderer: (params: any) => { cellRenderer: (params: any) => {
@@ -90,11 +92,11 @@ const columnDefs: ColDef[] = [
return `<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${cls}">${params.value}</span>` return `<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${cls}">${params.value}</span>`
}, },
}, },
{ field: 'cell', headerName: 'Célula', width: 110, filter: true, cellRenderer: (p: any) => p.value || '—' }, { field: 'cell', headerName: t('users.cell'), width: 110, filter: true, cellRenderer: (p: any) => p.value || '—' },
{ field: 'seniority', headerName: 'Seniority', width: 100, filter: true, cellRenderer: (p: any) => p.value || '—' }, { field: 'seniority', headerName: t('users.seniority'), width: 100, filter: true, cellRenderer: (p: any) => p.value || '—' },
{ {
field: 'projects_count', field: 'projects_count',
headerName: 'Proyectos', headerName: t('users.projects'),
width: 100, width: 100,
filter: 'agNumberColumnFilter', filter: 'agNumberColumnFilter',
cellRenderer: (params: any) => { cellRenderer: (params: any) => {
@@ -105,7 +107,7 @@ const columnDefs: ColDef[] = [
}, },
{ {
field: 'projects', field: 'projects',
headerName: 'Asignaciones', headerName: t('users.assignments'),
flex: 3, flex: 3,
minWidth: 200, minWidth: 200,
cellRenderer: (params: any) => { cellRenderer: (params: any) => {
@@ -114,7 +116,7 @@ const columnDefs: ColDef[] = [
return p.map((n: string) => `<span class="inline-flex rounded-full bg-muted px-2 py-0.5 text-xs mr-1">${n}</span>`).join('') return p.map((n: string) => `<span class="inline-flex rounded-full bg-muted px-2 py-0.5 text-xs mr-1">${n}</span>`).join('')
}, },
}, },
] ])
onMounted(() => { onMounted(() => {
store.fetchAll() store.fetchAll()
@@ -126,13 +128,13 @@ onMounted(() => {
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<h2 class="text-2xl font-bold tracking-tight">Equipo</h2> <h2 class="text-2xl font-bold tracking-tight">{{ t('users.teamTitle') }}</h2>
<p class="text-muted-foreground"> <p class="text-muted-foreground">
{{ store.users.length }} miembros · {{ store.employees.length }} asignaciones en KAPPA {{ t('users.teamSubtitle', { users: store.users.length, emps: store.employees.length }) }}
</p> </p>
</div> </div>
<Badge variant="outline" class="text-sm"> <Badge variant="outline" class="text-sm">
{{ stats.active }} activos {{ t('users.activeCount', { count: stats.active }) }}
</Badge> </Badge>
</div> </div>
@@ -150,13 +152,13 @@ onMounted(() => {
<template v-else-if="store.error"> <template v-else-if="store.error">
<div class="flex flex-col items-center gap-3 py-12 text-center"> <div class="flex flex-col items-center gap-3 py-12 text-center">
<IconExclamationCircle class="size-10 text-destructive" /> <IconExclamationCircle class="size-10 text-destructive" />
<p class="text-lg font-medium">Error al cargar usuarios</p> <p class="text-lg font-medium">{{ t('users.loadError') }}</p>
<p class="text-sm text-muted-foreground">{{ store.error }}</p> <p class="text-sm text-muted-foreground">{{ store.error }}</p>
<button <button
class="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90" class="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
@click="store.fetchAll()" @click="store.fetchAll()"
> >
Reintentar {{ t('common.retry') }}
</button> </button>
</div> </div>
</template> </template>
@@ -165,8 +167,8 @@ onMounted(() => {
<template v-else-if="store.users.length === 0"> <template v-else-if="store.users.length === 0">
<div class="flex flex-col items-center gap-3 py-12 text-center"> <div class="flex flex-col items-center gap-3 py-12 text-center">
<IconUsers class="size-10 text-muted-foreground" /> <IconUsers class="size-10 text-muted-foreground" />
<p class="text-lg font-medium">Sin usuarios</p> <p class="text-lg font-medium">{{ t('users.emptyTitle') }}</p>
<p class="text-sm text-muted-foreground">No se encontraron usuarios en KAPPA.</p> <p class="text-sm text-muted-foreground">{{ t('users.emptyDescription') }}</p>
</div> </div>
</template> </template>
@@ -194,7 +196,7 @@ onMounted(() => {
<!-- Team Member Cards --> <!-- Team Member Cards -->
<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">
Miembros del equipo · {{ teamMembers.length }} {{ t('users.teamMembers', { count: teamMembers.length }) }}
</h3> </h3>
<div class="grid grid-cols-1 gap-3 @xl:grid-cols-2 @3xl:grid-cols-3"> <div class="grid grid-cols-1 gap-3 @xl:grid-cols-2 @3xl:grid-cols-3">
<Card <Card
@@ -232,7 +234,7 @@ onMounted(() => {
{{ user.cell }} {{ user.cell }}
</span> </span>
<span v-if="user.seniority" class="capitalize">{{ user.seniority }}</span> <span v-if="user.seniority" class="capitalize">{{ user.seniority }}</span>
<span class="font-medium text-foreground">{{ user.projects_count }} proyecto{{ user.projects_count !== 1 ? 's' : '' }}</span> <span class="font-medium text-foreground">{{ t('users.projectCount', { count: user.projects_count }) }}</span>
</div> </div>
<div v-if="user.projects.length" class="flex flex-wrap gap-1 mt-2"> <div v-if="user.projects.length" class="flex flex-wrap gap-1 mt-2">
<span <span
@@ -254,7 +256,7 @@ onMounted(() => {
<!-- AG Grid table --> <!-- AG Grid table -->
<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">
Todos los usuarios {{ t('users.allUsers') }}
</h3> </h3>
<div <div
class="ag-theme-alpha-shadcn w-full rounded-lg border overflow-hidden" class="ag-theme-alpha-shadcn w-full rounded-lg border overflow-hidden"