mejoras UI/UX potentes
This commit is contained in:
122
nuxt4-app/app/components/clientes/ClienteCard.vue
Normal file
122
nuxt4-app/app/components/clientes/ClienteCard.vue
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<!-- nuxt4-app/app/components/clientes/ClienteCard.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="relative overflow-hidden rounded-lg border border-gray-400/30 bg-gradient-to-br from-gray-100/10 to-gray-300/5 backdrop-blur-sm p-5 shadow-lg transition-all duration-300 hover:border-gray-300/50 hover:shadow-xl">
|
||||||
|
<!-- Header with Avatar and Name -->
|
||||||
|
<div class="flex items-start gap-4 mb-4">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<div class="h-14 w-14 rounded-full bg-gradient-to-br from-gray-300/40 to-gray-500/20 flex items-center justify-center text-xl font-bold text-gray-200 border border-gray-400/30">
|
||||||
|
{{ clienteInitials }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<h3 class="text-lg font-bold text-gray-100 truncate mb-1">{{ cliente.name }}</h3>
|
||||||
|
<div class="flex flex-wrap gap-2 text-xs">
|
||||||
|
<span v-if="cliente.cedula" class="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-gray-500/20 text-gray-300 border border-gray-400/20">
|
||||||
|
<UIcon name="i-lucide-credit-card" class="w-3 h-3" />
|
||||||
|
{{ cliente.cedula }}
|
||||||
|
</span>
|
||||||
|
<span v-if="cliente.empleado" class="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-blue-500/20 text-blue-300 border border-blue-400/30">
|
||||||
|
<UIcon name="i-lucide-briefcase" class="w-3 h-3" />
|
||||||
|
Empleado
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Remove Button -->
|
||||||
|
<button
|
||||||
|
@click="$emit('remove')"
|
||||||
|
class="flex-shrink-0 p-1.5 rounded-full hover:bg-red-500/20 text-gray-400 hover:text-red-400 transition-colors"
|
||||||
|
title="Quitar cliente"
|
||||||
|
>
|
||||||
|
<UIcon name="i-lucide-x" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Details Grid -->
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<div v-if="cliente.ubicacion" class="flex items-start gap-2">
|
||||||
|
<UIcon name="i-lucide-map-pin" class="w-4 h-4 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="text-xs text-gray-500 uppercase tracking-wide">Ubicación</div>
|
||||||
|
<div class="text-sm text-gray-200 truncate">{{ cliente.ubicacion }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="cliente.telefono" class="flex items-start gap-2">
|
||||||
|
<UIcon name="i-lucide-phone" class="w-4 h-4 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="text-xs text-gray-500 uppercase tracking-wide">Teléfono</div>
|
||||||
|
<div class="text-sm text-gray-200 truncate">{{ cliente.telefono }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="cliente.grupo_estudio" class="flex items-start gap-2">
|
||||||
|
<UIcon name="i-lucide-users" class="w-4 h-4 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="text-xs text-gray-500 uppercase tracking-wide">Grupo</div>
|
||||||
|
<div class="text-sm text-gray-200 truncate">{{ cliente.grupo_estudio }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="cliente.idciat" class="flex items-start gap-2">
|
||||||
|
<UIcon name="i-lucide-hash" class="w-4 h-4 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="text-xs text-gray-500 uppercase tracking-wide">ID CIAT</div>
|
||||||
|
<div class="text-sm text-gray-200">{{ cliente.idciat }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer with metadata -->
|
||||||
|
<div class="mt-4 pt-3 border-t border-gray-400/20 flex items-center justify-between text-xs text-gray-500">
|
||||||
|
<span>ID: {{ cliente.id }}</span>
|
||||||
|
<span v-if="cliente.created_at">Desde {{ formatDate(cliente.created_at) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Decorative gradient overlay -->
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-tr from-transparent via-transparent to-white/5 pointer-events-none rounded-lg"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface ClienteRecord {
|
||||||
|
id: number
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
name: string
|
||||||
|
cedula?: number
|
||||||
|
ubicacion?: string
|
||||||
|
grupo_estudio?: string
|
||||||
|
empleado?: boolean
|
||||||
|
avatar_url?: string
|
||||||
|
telefono?: string
|
||||||
|
idciat?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
cliente: ClienteRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
remove: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Compute initials from name
|
||||||
|
const clienteInitials = computed(() => {
|
||||||
|
const names = props.cliente.name.trim().split(' ')
|
||||||
|
if (names.length >= 2) {
|
||||||
|
return (names[0][0] + names[1][0]).toUpperCase()
|
||||||
|
}
|
||||||
|
return names[0].substring(0, 2).toUpperCase()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Format date helper
|
||||||
|
const formatDate = (dateStr: string) => {
|
||||||
|
const date = new Date(dateStr)
|
||||||
|
return date.toLocaleDateString('es-ES', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -34,6 +34,18 @@
|
|||||||
<MetadatosCard v-if="clientesMetadata" :metadata="clientesMetadata" />
|
<MetadatosCard v-if="clientesMetadata" :metadata="clientesMetadata" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Clientes Seleccionados Cards -->
|
||||||
|
<div v-if="clientesSeleccionados.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<TransitionGroup name="cliente-card">
|
||||||
|
<ClientesClienteCard
|
||||||
|
v-for="cliente in clientesSeleccionados"
|
||||||
|
:key="cliente.id"
|
||||||
|
:cliente="cliente"
|
||||||
|
@remove="removeCliente(cliente.id)"
|
||||||
|
/>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 🔻 Card de Filtros -->
|
<!-- 🔻 Card de Filtros -->
|
||||||
<UCard class="brand-card border border-transparent">
|
<UCard class="brand-card border border-transparent">
|
||||||
<template #header>
|
<template #header>
|
||||||
@@ -273,6 +285,17 @@ function isClienteSelected(clienteId: number): boolean {
|
|||||||
return selectedClienteIds.value.includes(clienteId)
|
return selectedClienteIds.value.includes(clienteId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get selected clientes for display cards
|
||||||
|
const clientesSeleccionados = computed((): ClienteRecord[] => {
|
||||||
|
if (selectedClienteIds.value.length === 0) return []
|
||||||
|
return (clientes.value ?? []).filter(c => selectedClienteIds.value.includes(c.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove a cliente from selection
|
||||||
|
function removeCliente(clienteId: number) {
|
||||||
|
selectedClienteIds.value = selectedClienteIds.value.filter(id => id !== clienteId)
|
||||||
|
}
|
||||||
|
|
||||||
// Filtrados que alimentan los métricos
|
// Filtrados que alimentan los métricos
|
||||||
const ingresosFiltrados = computed(() => {
|
const ingresosFiltrados = computed(() => {
|
||||||
return (ingresos.value ?? [])
|
return (ingresos.value ?? [])
|
||||||
@@ -350,3 +373,24 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.cliente-card-enter-active,
|
||||||
|
.cliente-card-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cliente-card-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9) translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cliente-card-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9) translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cliente-card-move {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user