mejoras de ui UX demasido intensas
This commit is contained in:
@@ -8,13 +8,15 @@
|
||||
{{ props.records.length }} registros filtrados
|
||||
</p>
|
||||
</div>
|
||||
<UButton
|
||||
:label="dateFormat === 'short' ? 'Fecha Larga' : 'Fecha Corta'"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-lucide-calendar"
|
||||
@click="toggleDateFormat"
|
||||
/>
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton
|
||||
:label="dateFormat === 'short' ? 'Fecha Larga' : 'Fecha Corta'"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-lucide-calendar"
|
||||
@click="toggleDateFormat"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -46,13 +48,19 @@
|
||||
<!-- Table Component -->
|
||||
<UTable
|
||||
ref="table"
|
||||
v-model:expanded="expanded"
|
||||
:data="limitedRecords"
|
||||
:columns="tableColumns"
|
||||
:global-filter="globalFilter"
|
||||
:get-sub-rows="(row: any) => row.children"
|
||||
sticky
|
||||
class="h-96"
|
||||
:ui="{
|
||||
thead: 'bg-cyan-400/20 [&>tr>th]:text-white [&>tr>th]:font-semibold'
|
||||
thead: 'bg-cyan-400/20 [&>tr>th]:text-white [&>tr>th]:font-semibold',
|
||||
base: 'border-separate border-spacing-0',
|
||||
tbody: '[&>tr]:last:[&>td]:border-b-0',
|
||||
tr: 'group',
|
||||
td: 'empty:p-0 group-has-[td:not(:empty)]:border-b border-default'
|
||||
}"
|
||||
/>
|
||||
|
||||
@@ -100,9 +108,20 @@ import type { IngresoRecord } from '~/composables/useIngresosMetrics'
|
||||
interface ClienteRecord {
|
||||
id: number
|
||||
name: string
|
||||
cedula?: number
|
||||
ubicacion?: string
|
||||
telefono?: string
|
||||
grupo_estudio?: string
|
||||
empleado?: boolean
|
||||
avatar_url?: string
|
||||
idciat?: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface IngresoWithChildren extends IngresoRecord {
|
||||
children?: ClienteRecord[]
|
||||
}
|
||||
|
||||
interface Props {
|
||||
records: IngresoRecord[]
|
||||
clientes?: ClienteRecord[]
|
||||
@@ -111,23 +130,39 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UIcon = resolveComponent('UIcon')
|
||||
const UTooltip = resolveComponent('UTooltip')
|
||||
const UAvatar = resolveComponent('UAvatar')
|
||||
|
||||
const globalFilter = ref('')
|
||||
const table = useTemplateRef<{ tableApi?: any }>('table')
|
||||
const currentPage = ref(1)
|
||||
const recordsPerPage = 100
|
||||
const dateFormat = ref<'short' | 'long'>('short')
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UIcon = resolveComponent('UIcon')
|
||||
const UTooltip = resolveComponent('UTooltip')
|
||||
const expanded = ref<Record<string, boolean>>({})
|
||||
|
||||
function toggleDateFormat() {
|
||||
dateFormat.value = dateFormat.value === 'short' ? 'long' : 'short'
|
||||
}
|
||||
|
||||
// Map records to include cliente as children
|
||||
const recordsWithChildren = computed((): IngresoWithChildren[] => {
|
||||
if (!props.clientes || props.clientes.length === 0) {
|
||||
return props.records as IngresoWithChildren[]
|
||||
}
|
||||
|
||||
return props.records.map(ingreso => {
|
||||
const cliente = props.clientes?.find(c => c.id === ingreso.cliente_id)
|
||||
return {
|
||||
...ingreso,
|
||||
children: cliente ? [cliente] : []
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Paginación
|
||||
const totalRecords = computed(() => props.records.length)
|
||||
const totalRecords = computed(() => recordsWithChildren.value.length)
|
||||
const totalPages = computed(() => Math.ceil(totalRecords.value / recordsPerPage))
|
||||
|
||||
const startRecord = computed(() => {
|
||||
@@ -144,7 +179,7 @@ const endRecord = computed(() => {
|
||||
const limitedRecords = computed(() => {
|
||||
const start = (currentPage.value - 1) * recordsPerPage
|
||||
const end = start + recordsPerPage
|
||||
return (props.records || []).slice(start, end)
|
||||
return recordsWithChildren.value.slice(start, end)
|
||||
})
|
||||
|
||||
function nextPage() {
|
||||
@@ -159,7 +194,7 @@ function previousPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Seleccionar columnas importantes manualmente en lugar de todas las propiedades
|
||||
// Seleccionar columnas importantes manualmente
|
||||
const selectedColumns = [
|
||||
'id',
|
||||
'created_at',
|
||||
@@ -187,7 +222,7 @@ const selectedColumns = [
|
||||
'tara',
|
||||
]
|
||||
|
||||
// Generate table columns only for selected fields
|
||||
// Generate table columns
|
||||
const tableColumns = computed((): TableColumn<Record<string, unknown>>[] => {
|
||||
if (!limitedRecords.value.length) return []
|
||||
|
||||
@@ -197,22 +232,89 @@ const tableColumns = computed((): TableColumn<Record<string, unknown>>[] => {
|
||||
// Solo usar columnas que existen en el primer registro
|
||||
const availableColumns = selectedColumns.filter(col => col in firstRow)
|
||||
|
||||
return availableColumns.map((column: string) => ({
|
||||
accessorKey: column,
|
||||
header: ({ column: tableColumn }) => {
|
||||
const isSorted = tableColumn.getIsSorted()
|
||||
return availableColumns.map((column: string) => {
|
||||
// Columna ID especial con botón de expansión
|
||||
if (column === 'id') {
|
||||
return {
|
||||
accessorKey: column,
|
||||
header: ({ column: tableColumn }) => {
|
||||
const isSorted = tableColumn.getIsSorted()
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label: '#',
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
|
||||
class: '-mx-2.5 text-white hover:text-cyan-100',
|
||||
onClick: () => tableColumn.toggleSorting(tableColumn.getIsSorted() === 'asc')
|
||||
})
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const original = row.original as any
|
||||
const isCliente = 'name' in original && !('tipo' in original)
|
||||
const isIngreso = 'tipo' in original
|
||||
const isParent = row.depth === 0
|
||||
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label: upperFirst(column),
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
|
||||
class: '-mx-2.5 text-white hover:text-cyan-100',
|
||||
onClick: () => tableColumn.toggleSorting(tableColumn.getIsSorted() === 'asc')
|
||||
})
|
||||
},
|
||||
cell: ({ row }) => formatCellValue(row.getValue(column), column)
|
||||
}))
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
paddingLeft: `${row.depth * 2}rem`
|
||||
},
|
||||
class: 'flex items-center gap-2'
|
||||
},
|
||||
[
|
||||
// Botón de expansión solo en parent rows
|
||||
isParent && props.clientes && props.clientes.length > 0 && h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'outline',
|
||||
size: 'xs',
|
||||
icon: row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus',
|
||||
class: !row.getCanExpand() && 'invisible',
|
||||
ui: {
|
||||
base: 'p-0 rounded-sm',
|
||||
leadingIcon: 'size-4'
|
||||
},
|
||||
onClick: row.getToggleExpandedHandler()
|
||||
}),
|
||||
// Badge de ID
|
||||
isIngreso && h(UBadge, {
|
||||
label: `I-${row.getValue('id')}`,
|
||||
color: 'primary',
|
||||
variant: 'subtle',
|
||||
size: 'md',
|
||||
class: 'rounded-full'
|
||||
}),
|
||||
isCliente && h(UBadge, {
|
||||
label: `C-${row.getValue('id')}`,
|
||||
color: 'warning',
|
||||
variant: 'subtle',
|
||||
size: 'md',
|
||||
class: 'rounded-full'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otras columnas normales
|
||||
return {
|
||||
accessorKey: column,
|
||||
header: ({ column: tableColumn }) => {
|
||||
const isSorted = tableColumn.getIsSorted()
|
||||
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label: upperFirst(column),
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
|
||||
class: '-mx-2.5 text-white hover:text-cyan-100',
|
||||
onClick: () => tableColumn.toggleSorting(tableColumn.getIsSorted() === 'asc')
|
||||
})
|
||||
},
|
||||
cell: ({ row }) => formatCellValue(row.getValue(column), column, row.original as any, row.depth)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Column visibility dropdown items
|
||||
@@ -236,20 +338,49 @@ const columnVisibilityItems = computed((): any[] => {
|
||||
})
|
||||
|
||||
|
||||
function formatCellValue(value: unknown, column: string): any {
|
||||
if (value === null || value === undefined) {
|
||||
function formatCellValue(value: unknown, column: string, row: any, depth: number): any {
|
||||
// Detectar si es cliente (child row)
|
||||
const isCliente = 'name' in row && !('tipo' in row)
|
||||
const isIngreso = 'tipo' in row
|
||||
|
||||
// Si es una fila de cliente expandida, mostrar info del cliente
|
||||
if (isCliente && depth > 0) {
|
||||
if (column === 'created_at' || column === 'tipo' || column === 'estado') {
|
||||
// Mostrar información del cliente en columnas relevantes
|
||||
if (column === 'created_at') {
|
||||
return h('div', { class: 'flex flex-col gap-1' }, [
|
||||
h('span', { class: 'font-semibold text-yellow-500' }, row.name),
|
||||
row.ubicacion && h('span', { class: 'text-xs text-gray-400' }, `📍 ${row.ubicacion}`),
|
||||
row.telefono && h('span', { class: 'text-xs text-gray-400' }, `📞 ${row.telefono}`)
|
||||
])
|
||||
}
|
||||
if (column === 'tipo' && row.cedula) {
|
||||
return h(UBadge, {
|
||||
label: String(row.cedula),
|
||||
color: 'neutral',
|
||||
variant: 'soft',
|
||||
size: 'sm',
|
||||
class: 'font-mono'
|
||||
})
|
||||
}
|
||||
if (column === 'estado' && row.empleado !== undefined) {
|
||||
return row.empleado
|
||||
? h(UIcon, {
|
||||
name: 'i-lucide-briefcase',
|
||||
class: 'text-purple-600 w-5 h-5'
|
||||
})
|
||||
: h(UIcon, {
|
||||
name: 'i-lucide-user',
|
||||
class: 'text-gray-400 w-5 h-5'
|
||||
})
|
||||
}
|
||||
}
|
||||
return '—'
|
||||
}
|
||||
|
||||
// ID column - badge con formato I-####
|
||||
if (column === 'id' && typeof value === 'number') {
|
||||
return h(UBadge, {
|
||||
label: `I-${value}`,
|
||||
color: 'primary',
|
||||
variant: 'subtle',
|
||||
size: 'md',
|
||||
class: 'rounded-full'
|
||||
})
|
||||
// Valores null/undefined
|
||||
if (value === null || value === undefined) {
|
||||
return '—'
|
||||
}
|
||||
|
||||
// comercio_id - badge con formato C-#### en cyan
|
||||
|
||||
Reference in New Issue
Block a user