206 lines
6.6 KiB
Vue
206 lines
6.6 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { AgGridVue } from 'ag-grid-vue3'
|
|
import type { ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'
|
|
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community'
|
|
import { useUsersStore } from '@/stores/users'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
import { IconExclamationCircle, IconUsers } from '@tabler/icons-vue'
|
|
|
|
ModuleRegistry.registerModules([AllCommunityModule])
|
|
|
|
const store = useUsersStore()
|
|
const gridApi = ref<GridApi | null>(null)
|
|
|
|
const columnDefs: ColDef[] = [
|
|
{
|
|
field: 'full_name',
|
|
headerName: 'Nombre',
|
|
flex: 2,
|
|
minWidth: 180,
|
|
sort: 'asc',
|
|
filter: 'agTextColumnFilter',
|
|
cellRenderer: (params: any) => {
|
|
return `<div class="flex items-center gap-2">
|
|
<div class="flex items-center justify-center size-7 rounded-full bg-primary/10 text-primary text-xs font-semibold">
|
|
${params.data.first_name?.[0] || ''}${params.data.last_name?.[0] || ''}
|
|
</div>
|
|
<span class="font-medium">${params.value}</span>
|
|
</div>`
|
|
},
|
|
},
|
|
{
|
|
field: 'email',
|
|
headerName: 'Email',
|
|
flex: 2,
|
|
minWidth: 200,
|
|
filter: 'agTextColumnFilter',
|
|
},
|
|
{
|
|
field: 'role',
|
|
headerName: 'Rol',
|
|
flex: 1,
|
|
minWidth: 100,
|
|
filter: 'agTextColumnFilter',
|
|
cellRenderer: (params: any) => {
|
|
if (!params.value) return `<span class="text-muted-foreground text-xs">—</span>`
|
|
const colors: Record<string, string> = {
|
|
BA: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
|
DEV: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400',
|
|
PM: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400',
|
|
QA: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400',
|
|
PO: 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-400',
|
|
TL: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400',
|
|
}
|
|
const cls = colors[params.value] || 'bg-muted text-muted-foreground'
|
|
return `<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls}">${params.value}</span>`
|
|
},
|
|
},
|
|
{
|
|
field: 'cell',
|
|
headerName: 'Célula',
|
|
flex: 1,
|
|
minWidth: 100,
|
|
filter: 'agTextColumnFilter',
|
|
cellRenderer: (params: any) => {
|
|
if (!params.value) return `<span class="text-muted-foreground text-xs">—</span>`
|
|
return `<span class="inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium">${params.value}</span>`
|
|
},
|
|
},
|
|
{
|
|
field: 'seniority',
|
|
headerName: 'Seniority',
|
|
flex: 1,
|
|
minWidth: 100,
|
|
filter: 'agTextColumnFilter',
|
|
cellRenderer: (params: any) => {
|
|
if (!params.value) return `<span class="text-muted-foreground text-xs">—</span>`
|
|
const map: Record<string, string> = {
|
|
jr: 'Jr',
|
|
mid: 'Mid',
|
|
sr: 'Sr',
|
|
lead: 'Lead',
|
|
}
|
|
return `<span class="text-sm">${map[params.value] || params.value}</span>`
|
|
},
|
|
},
|
|
{
|
|
field: 'projects_count',
|
|
headerName: 'Proyectos',
|
|
flex: 1,
|
|
minWidth: 110,
|
|
filter: 'agNumberColumnFilter',
|
|
cellRenderer: (params: any) => {
|
|
const count = params.value || 0
|
|
if (count === 0) return `<span class="text-muted-foreground text-xs">—</span>`
|
|
const cls = count >= 5 ? 'text-destructive' : count >= 3 ? 'text-amber-500' : 'text-emerald-500'
|
|
return `<span class="font-semibold text-sm ${cls}">${count}</span>`
|
|
},
|
|
},
|
|
{
|
|
field: 'projects',
|
|
headerName: 'Proyectos asignados',
|
|
flex: 3,
|
|
minWidth: 220,
|
|
filter: 'agTextColumnFilter',
|
|
cellRenderer: (params: any) => {
|
|
const projects: string[] = params.value || []
|
|
if (projects.length === 0) return `<span class="text-muted-foreground text-xs">Sin asignación</span>`
|
|
const badges = projects.map((p: string) =>
|
|
`<span class="inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs mr-1 mb-1">${p}</span>`
|
|
).join('')
|
|
return `<div class="flex flex-wrap gap-1 py-0.5">${badges}</div>`
|
|
},
|
|
autoHeight: true,
|
|
},
|
|
]
|
|
|
|
const defaultColDef: ColDef = {
|
|
sortable: true,
|
|
resizable: true,
|
|
wrapText: true,
|
|
}
|
|
|
|
function onGridReady(params: GridReadyEvent) {
|
|
gridApi.value = params.api
|
|
params.api.sizeColumnsToFit()
|
|
}
|
|
|
|
onMounted(() => {
|
|
store.fetchAll()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-col gap-4 px-4 lg:px-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h2 class="text-2xl font-bold tracking-tight">Equipo</h2>
|
|
<p class="text-muted-foreground">
|
|
{{ store.users.length }} miembros · {{ store.employees.length }} asignaciones
|
|
</p>
|
|
</div>
|
|
<Badge variant="outline" class="text-sm">
|
|
{{ store.activeUsers.length }} activos
|
|
</Badge>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div v-if="store.loading" class="space-y-2">
|
|
<Skeleton v-for="i in 5" :key="i" class="h-10 w-full" />
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div v-else-if="store.error" class="flex flex-col items-center gap-3 py-12 text-center">
|
|
<IconExclamationCircle class="size-10 text-destructive" />
|
|
<p class="text-lg font-medium">Error al cargar usuarios</p>
|
|
<p class="text-sm text-muted-foreground">{{ store.error }}</p>
|
|
<button
|
|
class="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
|
@click="store.fetchAll()"
|
|
>
|
|
Reintentar
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Empty -->
|
|
<div v-else-if="store.users.length === 0" class="flex flex-col items-center gap-3 py-12 text-center">
|
|
<IconUsers class="size-10 text-muted-foreground" />
|
|
<p class="text-lg font-medium">Sin usuarios</p>
|
|
<p class="text-sm text-muted-foreground">
|
|
No se encontraron usuarios en KAPPA.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Grid -->
|
|
<div v-else class="ag-theme-alpha w-full" style="height: calc(100vh - 14rem)">
|
|
<AgGridVue
|
|
:row-data="store.users"
|
|
:column-defs="columnDefs"
|
|
:default-col-def="defaultColDef"
|
|
:pagination="true"
|
|
:pagination-page-size="20"
|
|
:pagination-page-size-selector="[10, 20, 50, 100]"
|
|
:animate-rows="false"
|
|
row-selection="single"
|
|
dom-layout="normal"
|
|
@grid-ready="onGridReady"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
:deep(.ag-cell) {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
:deep(.ag-header-cell) {
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--muted-foreground);
|
|
}
|
|
</style>
|