This commit is contained in:
228
nuxt4-app/app/components/empleados/DetalleAsistencias.vue
Normal file
228
nuxt4-app/app/components/empleados/DetalleAsistencias.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold brand-section-title">Detalle de Asistencias</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge color="gray" variant="soft" size="sm">
|
||||
{{ data.length }} registros
|
||||
</UBadge>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-lucide-copy"
|
||||
@click="copiarTexto"
|
||||
>
|
||||
Copiar Texto
|
||||
</UButton>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-lucide-braces"
|
||||
@click="copiarJSON"
|
||||
>
|
||||
Copiar JSON
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[var(--brand-border)]">
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Empleado</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Fecha</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Entrada</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Salida</th>
|
||||
<th class="text-right py-3 px-3 font-semibold text-[var(--brand-text)]">Horas</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Estado</th>
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Observación</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="asistencia in data"
|
||||
:key="asistencia.asistencia_id"
|
||||
class="border-b border-[var(--brand-border)] hover:bg-[var(--brand-surface)] transition-colors"
|
||||
>
|
||||
<td class="py-3 px-3">
|
||||
<div class="text-[var(--brand-text)] font-medium">{{ asistencia.empleado_nombre }}</div>
|
||||
<div class="text-[var(--brand-text-muted)] text-xs font-mono">{{ asistencia.empleado_cedula || 'N/A' }}</div>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center text-[var(--brand-text)]">
|
||||
{{ formatDate(asistencia.fecha_asistencia) }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<span v-if="asistencia.entrada" class="font-mono text-green-400">
|
||||
{{ formatTime(asistencia.entrada) }}
|
||||
</span>
|
||||
<span v-else class="text-[var(--brand-text-muted)] text-xs italic">
|
||||
Sin registro
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<span v-if="asistencia.salida" class="font-mono text-orange-400">
|
||||
{{ formatTime(asistencia.salida) }}
|
||||
</span>
|
||||
<span v-else class="text-[var(--brand-text-muted)] text-xs italic">
|
||||
Sin registro
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right">
|
||||
<span
|
||||
v-if="asistencia.horas_trabajadas !== null"
|
||||
class="font-mono font-bold text-blue-400"
|
||||
>
|
||||
{{ formatHours(asistencia.horas_trabajadas) }}
|
||||
</span>
|
||||
<span v-else class="text-[var(--brand-text-muted)] text-xs">
|
||||
-
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<UBadge
|
||||
:color="getEstadoColor(asistencia.estado)"
|
||||
variant="soft"
|
||||
size="xs"
|
||||
>
|
||||
{{ asistencia.estado || 'registrada' }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-[var(--brand-text-muted)] text-xs max-w-xs truncate">
|
||||
{{ asistencia.observacion || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot v-if="data.length > 0" class="border-t-2 border-[var(--brand-border)]">
|
||||
<tr class="bg-[var(--brand-surface)]">
|
||||
<td colspan="4" class="py-3 px-3 font-semibold text-[var(--brand-text)]">
|
||||
TOTALES ({{ data.length }} registros)
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right">
|
||||
<span class="font-mono font-bold text-blue-400">
|
||||
{{ formatHours(totalHoras) }}
|
||||
</span>
|
||||
<div class="text-xs text-[var(--brand-text-muted)] mt-1">
|
||||
{{ formatDays(totalHoras) }} días equiv.
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<div v-if="data.length === 0" class="text-center py-12 text-[var(--brand-text-muted)]">
|
||||
<div class="i-lucide-inbox text-4xl mx-auto mb-3 opacity-50"></div>
|
||||
<p>No hay asistencias en el rango de fechas seleccionado</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Asistencia {
|
||||
asistencia_id: number
|
||||
empleado_id: number
|
||||
empleado_nombre: string
|
||||
empleado_cedula: string
|
||||
entrada: string | null
|
||||
salida: string | null
|
||||
horas_trabajadas: number | null
|
||||
estado: string
|
||||
observacion: string | null
|
||||
fecha_asistencia: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: Asistencia[]
|
||||
rangoLegible: string
|
||||
lastUpdated: string
|
||||
}>()
|
||||
|
||||
// Computed para totales
|
||||
const totalHoras = computed(() => {
|
||||
return props.data.reduce((sum, a) => sum + (a.horas_trabajadas || 0), 0)
|
||||
})
|
||||
|
||||
// Funciones helper
|
||||
const getEstadoColor = (estado: string) => {
|
||||
const colores: Record<string, string> = {
|
||||
'registrada': 'green',
|
||||
'pendiente': 'yellow',
|
||||
'anulado': 'red',
|
||||
'justificada': 'blue',
|
||||
'tardanza': 'orange'
|
||||
}
|
||||
return colores[estado?.toLowerCase()] || 'green'
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return 'N/A'
|
||||
const date = new Date(dateString)
|
||||
return new Intl.DateTimeFormat('es-HN', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
const formatTime = (dateTimeString: string) => {
|
||||
if (!dateTimeString) return 'N/A'
|
||||
const date = new Date(dateTimeString)
|
||||
return new Intl.DateTimeFormat('es-HN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
const formatHours = (hours: number) => {
|
||||
if (hours === null || hours === undefined) return '0.00 hrs'
|
||||
return `${hours.toFixed(2)} hrs`
|
||||
}
|
||||
|
||||
const formatDays = (hours: number) => {
|
||||
const days = hours / 8
|
||||
return days.toFixed(2)
|
||||
}
|
||||
|
||||
async function copiarTexto() {
|
||||
const footer = `
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📊 RESUMEN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📅 Rango: ${props.rangoLegible}
|
||||
📋 Asistencias: ${props.data.length}
|
||||
🕐 Generado: ${props.lastUpdated}`
|
||||
|
||||
let texto = `⏰ DETALLE DE ASISTENCIAS\n\n`
|
||||
|
||||
props.data.forEach((asistencia, idx) => {
|
||||
texto += `${idx + 1}. ${asistencia.empleado_nombre} (${asistencia.empleado_cedula || 'N/A'})\n`
|
||||
texto += ` Fecha: ${formatDate(asistencia.fecha_asistencia)}\n`
|
||||
texto += ` Entrada: ${asistencia.entrada ? formatTime(asistencia.entrada) : 'Sin registro'}\n`
|
||||
texto += ` Salida: ${asistencia.salida ? formatTime(asistencia.salida) : 'Sin registro'}\n`
|
||||
texto += ` Horas: ${formatHours(asistencia.horas_trabajadas || 0)}\n`
|
||||
texto += ` Estado: ${asistencia.estado || 'registrada'}\n`
|
||||
if (asistencia.observacion) {
|
||||
texto += ` Observación: ${asistencia.observacion}\n`
|
||||
}
|
||||
texto += `\n`
|
||||
})
|
||||
|
||||
texto += `\n⏱️ TOTAL HORAS: ${formatHours(totalHoras.value)} (${formatDays(totalHoras.value)} días equiv.)${footer}`
|
||||
|
||||
await navigator.clipboard.writeText(texto)
|
||||
alert('✅ Detalle de Asistencias copiado al portapapeles')
|
||||
}
|
||||
|
||||
async function copiarJSON() {
|
||||
const json = JSON.stringify(props.data, null, 2)
|
||||
await navigator.clipboard.writeText(json)
|
||||
alert('✅ JSON copiado al portapapeles')
|
||||
}
|
||||
</script>
|
||||
253
nuxt4-app/app/components/empleados/DetallePlanillas.vue
Normal file
253
nuxt4-app/app/components/empleados/DetallePlanillas.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold brand-section-title">Detalle de Planillas</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge color="gray" variant="soft" size="sm">
|
||||
{{ data.length }} planillas
|
||||
</UBadge>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-lucide-copy"
|
||||
@click="copiarTexto"
|
||||
>
|
||||
Copiar Texto
|
||||
</UButton>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-lucide-braces"
|
||||
@click="copiarJSON"
|
||||
>
|
||||
Copiar JSON
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[var(--brand-border)]">
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Empleado</th>
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Cédula</th>
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Título</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Tareas</th>
|
||||
<th class="text-right py-3 px-3 font-semibold text-[var(--brand-text)]">Suma Precios Tareas</th>
|
||||
<th class="text-right py-3 px-3 font-semibold text-[var(--brand-text)]">Total Pagado</th>
|
||||
<th class="text-right py-3 px-3 font-semibold text-[var(--brand-text)]">Diferencia</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Periodo</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Estado</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="planilla in data"
|
||||
:key="planilla.planilla_id"
|
||||
class="border-b border-[var(--brand-border)] hover:bg-[var(--brand-surface)] transition-colors"
|
||||
>
|
||||
<td class="py-3 px-3 text-[var(--brand-text)]">
|
||||
{{ planilla.empleado_nombre }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-[var(--brand-text-muted)] font-mono text-xs">
|
||||
{{ planilla.empleado_cedula || 'N/A' }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-[var(--brand-text)]">
|
||||
<span class="font-medium">{{ planilla.planilla_titulo }}</span>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<UBadge color="purple" variant="soft" size="xs">
|
||||
{{ planilla.cantidad_tareas }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right font-mono text-blue-400">
|
||||
{{ formatCurrency(planilla.suma_precios_tareas || 0) }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right font-mono text-green-400">
|
||||
{{ formatCurrency(planilla.planilla_total || 0) }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right">
|
||||
<span
|
||||
:class="{
|
||||
'text-green-400': getDiferencia(planilla) > 0,
|
||||
'text-red-400': getDiferencia(planilla) < 0,
|
||||
'text-[var(--brand-text-muted)]': getDiferencia(planilla) === 0
|
||||
}"
|
||||
class="font-mono font-semibold"
|
||||
>
|
||||
{{ getDiferencia(planilla) > 0 ? '+' : '' }}{{ formatCurrency(getDiferencia(planilla)) }}
|
||||
</span>
|
||||
<div v-if="getDiferencia(planilla) !== 0" class="text-xs text-[var(--brand-text-muted)] mt-1">
|
||||
{{ getDiferenciaPorcentaje(planilla) }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center text-xs text-[var(--brand-text-muted)]">
|
||||
{{ formatDate(planilla.planilla_fecha_desde) }}<br />
|
||||
<span class="text-[10px]">al</span><br />
|
||||
{{ formatDate(planilla.planilla_fecha_hasta) }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<UBadge
|
||||
:color="planilla.planilla_estado === 'pagado' ? 'green' : 'orange'"
|
||||
variant="soft"
|
||||
size="xs"
|
||||
>
|
||||
{{ planilla.planilla_estado || 'pendiente' }}
|
||||
</UBadge>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot v-if="data.length > 0" class="border-t-2 border-[var(--brand-border)]">
|
||||
<tr class="bg-[var(--brand-surface)]">
|
||||
<td colspan="3" class="py-3 px-3 font-semibold text-[var(--brand-text)]">
|
||||
TOTALES
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<UBadge color="purple" variant="soft" size="sm">
|
||||
{{ totalTareas }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right font-mono font-bold text-blue-400">
|
||||
{{ formatCurrency(totalSumaPrecios) }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right font-mono font-bold text-green-400">
|
||||
{{ formatCurrency(totalPagado) }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right">
|
||||
<span
|
||||
:class="{
|
||||
'text-green-400': diferenciaTotal > 0,
|
||||
'text-red-400': diferenciaTotal < 0,
|
||||
'text-[var(--brand-text-muted)]': diferenciaTotal === 0
|
||||
}"
|
||||
class="font-mono font-bold"
|
||||
>
|
||||
{{ diferenciaTotal > 0 ? '+' : '' }}{{ formatCurrency(diferenciaTotal) }}
|
||||
</span>
|
||||
</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<div v-if="data.length === 0" class="text-center py-12 text-[var(--brand-text-muted)]">
|
||||
<div class="i-lucide-inbox text-4xl mx-auto mb-3 opacity-50"></div>
|
||||
<p>No hay planillas en el rango de fechas seleccionado</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Planilla {
|
||||
planilla_id: number
|
||||
empleado_id: number
|
||||
empleado_nombre: string
|
||||
empleado_cedula: string
|
||||
planilla_titulo: string
|
||||
planilla_total: number
|
||||
planilla_fecha_desde: string
|
||||
planilla_fecha_hasta: string
|
||||
planilla_estado: string
|
||||
suma_precios_tareas: number
|
||||
cantidad_tareas: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: Planilla[]
|
||||
rangoLegible: string
|
||||
lastUpdated: string
|
||||
}>()
|
||||
|
||||
// Computed para totales
|
||||
const totalTareas = computed(() => {
|
||||
return props.data.reduce((sum, p) => sum + (p.cantidad_tareas || 0), 0)
|
||||
})
|
||||
|
||||
const totalSumaPrecios = computed(() => {
|
||||
return props.data.reduce((sum, p) => sum + (p.suma_precios_tareas || 0), 0)
|
||||
})
|
||||
|
||||
const totalPagado = computed(() => {
|
||||
return props.data.reduce((sum, p) => sum + (p.planilla_total || 0), 0)
|
||||
})
|
||||
|
||||
const diferenciaTotal = computed(() => {
|
||||
return totalPagado.value - totalSumaPrecios.value
|
||||
})
|
||||
|
||||
// Funciones helper
|
||||
const getDiferencia = (planilla: Planilla) => {
|
||||
return (planilla.planilla_total || 0) - (planilla.suma_precios_tareas || 0)
|
||||
}
|
||||
|
||||
const getDiferenciaPorcentaje = (planilla: Planilla) => {
|
||||
const suma = planilla.suma_precios_tareas || 0
|
||||
if (suma === 0) return ''
|
||||
const diff = getDiferencia(planilla)
|
||||
const porcentaje = (diff / suma) * 100
|
||||
return `${porcentaje > 0 ? '+' : ''}${porcentaje.toFixed(1)}%`
|
||||
}
|
||||
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat('es-HN', {
|
||||
style: 'currency',
|
||||
currency: 'HNL',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(value)
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return 'N/A'
|
||||
const date = new Date(dateString)
|
||||
return new Intl.DateTimeFormat('es-HN', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
async function copiarTexto() {
|
||||
const footer = `
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📊 RESUMEN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📅 Rango: ${props.rangoLegible}
|
||||
📋 Planillas: ${props.data.length}
|
||||
🕐 Generado: ${props.lastUpdated}`
|
||||
|
||||
let texto = `📋 DETALLE DE PLANILLAS\n\n`
|
||||
|
||||
props.data.forEach((planilla, idx) => {
|
||||
const diff = getDiferencia(planilla)
|
||||
texto += `${idx + 1}. ${planilla.empleado_nombre} (${planilla.empleado_cedula || 'N/A'})\n`
|
||||
texto += ` Planilla: ${planilla.planilla_titulo}\n`
|
||||
texto += ` Periodo: ${formatDate(planilla.planilla_fecha_desde)} al ${formatDate(planilla.planilla_fecha_hasta)}\n`
|
||||
texto += ` Tareas: ${planilla.cantidad_tareas}\n`
|
||||
texto += ` Suma Precios Tareas: ${formatCurrency(planilla.suma_precios_tareas || 0)}\n`
|
||||
texto += ` Total Pagado: ${formatCurrency(planilla.planilla_total || 0)}\n`
|
||||
texto += ` Diferencia: ${diff > 0 ? '+' : ''}${formatCurrency(diff)} ${getDiferenciaPorcentaje(planilla)}\n`
|
||||
texto += ` Estado: ${planilla.planilla_estado || 'pendiente'}\n\n`
|
||||
})
|
||||
|
||||
texto += `\n📊 TOTALES GENERALES:\n`
|
||||
texto += ` Total Tareas: ${totalTareas.value}\n`
|
||||
texto += ` Total Suma Precios: ${formatCurrency(totalSumaPrecios.value)}\n`
|
||||
texto += ` Total Pagado: ${formatCurrency(totalPagado.value)}\n`
|
||||
texto += ` Diferencia Total: ${diferenciaTotal.value > 0 ? '+' : ''}${formatCurrency(diferenciaTotal.value)}${footer}`
|
||||
|
||||
await navigator.clipboard.writeText(texto)
|
||||
alert('✅ Detalle de Planillas copiado al portapapeles')
|
||||
}
|
||||
|
||||
async function copiarJSON() {
|
||||
const json = JSON.stringify(props.data, null, 2)
|
||||
await navigator.clipboard.writeText(json)
|
||||
alert('✅ JSON copiado al portapapeles')
|
||||
}
|
||||
</script>
|
||||
226
nuxt4-app/app/components/empleados/DetalleTareas.vue
Normal file
226
nuxt4-app/app/components/empleados/DetalleTareas.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold brand-section-title">Detalle de Tareas</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge color="gray" variant="soft" size="sm">
|
||||
{{ data.length }} tareas
|
||||
</UBadge>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-lucide-copy"
|
||||
@click="copiarTexto"
|
||||
>
|
||||
Copiar Texto
|
||||
</UButton>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-lucide-braces"
|
||||
@click="copiarJSON"
|
||||
>
|
||||
Copiar JSON
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[var(--brand-border)]">
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Empleado</th>
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Tarea</th>
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Planilla</th>
|
||||
<th class="text-right py-3 px-3 font-semibold text-[var(--brand-text)]">Precio</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Tipo</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Fecha</th>
|
||||
<th class="text-center py-3 px-3 font-semibold text-[var(--brand-text)]">Estado</th>
|
||||
<th class="text-left py-3 px-3 font-semibold text-[var(--brand-text)]">Observación</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="tarea in data"
|
||||
:key="tarea.tarea_id"
|
||||
class="border-b border-[var(--brand-border)] hover:bg-[var(--brand-surface)] transition-colors"
|
||||
>
|
||||
<td class="py-3 px-3">
|
||||
<div class="text-[var(--brand-text)] font-medium">{{ tarea.empleado_nombre }}</div>
|
||||
<div class="text-[var(--brand-text-muted)] text-xs font-mono">{{ tarea.empleado_cedula || 'N/A' }}</div>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-[var(--brand-text)]">
|
||||
<span class="font-medium">{{ tarea.tarea_titulo }}</span>
|
||||
</td>
|
||||
<td class="py-3 px-3">
|
||||
<div v-if="tarea.planilla_titulo" class="text-[var(--brand-text)] text-xs">
|
||||
{{ tarea.planilla_titulo }}
|
||||
</div>
|
||||
<div v-else class="text-[var(--brand-text-muted)] text-xs italic">
|
||||
Sin planilla
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right">
|
||||
<span class="font-mono font-bold text-blue-400">
|
||||
{{ formatCurrency(tarea.tarea_precio || 0) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<UBadge
|
||||
:color="getTipoColor(tarea.tarea_tipo)"
|
||||
variant="soft"
|
||||
size="xs"
|
||||
>
|
||||
{{ tarea.tarea_tipo || 'general' }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center text-xs text-[var(--brand-text-muted)]">
|
||||
{{ formatDate(tarea.tarea_fecha) }}
|
||||
</td>
|
||||
<td class="py-3 px-3 text-center">
|
||||
<UBadge
|
||||
:color="getEstadoColor(tarea.tarea_estado)"
|
||||
variant="soft"
|
||||
size="xs"
|
||||
>
|
||||
{{ tarea.tarea_estado || 'realizada' }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="py-3 px-3 text-[var(--brand-text-muted)] text-xs max-w-xs truncate">
|
||||
{{ tarea.tarea_observacion || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot v-if="data.length > 0" class="border-t-2 border-[var(--brand-border)]">
|
||||
<tr class="bg-[var(--brand-surface)]">
|
||||
<td colspan="3" class="py-3 px-3 font-semibold text-[var(--brand-text)]">
|
||||
TOTALES ({{ data.length }} tareas)
|
||||
</td>
|
||||
<td class="py-3 px-3 text-right">
|
||||
<span class="font-mono font-bold text-blue-400">
|
||||
{{ formatCurrency(totalPrecios) }}
|
||||
</span>
|
||||
</td>
|
||||
<td colspan="4"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<div v-if="data.length === 0" class="text-center py-12 text-[var(--brand-text-muted)]">
|
||||
<div class="i-lucide-inbox text-4xl mx-auto mb-3 opacity-50"></div>
|
||||
<p>No hay tareas en el rango de fechas seleccionado</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Tarea {
|
||||
tarea_id: number
|
||||
empleado_id: number
|
||||
empleado_nombre: string
|
||||
empleado_cedula: string
|
||||
planilla_id: number | null
|
||||
planilla_titulo: string | null
|
||||
tarea_titulo: string
|
||||
tarea_precio: number
|
||||
tarea_tipo: string
|
||||
tarea_estado: string
|
||||
tarea_observacion: string | null
|
||||
tarea_fecha: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: Tarea[]
|
||||
rangoLegible: string
|
||||
lastUpdated: string
|
||||
}>()
|
||||
|
||||
// Computed para totales
|
||||
const totalPrecios = computed(() => {
|
||||
return props.data.reduce((sum, t) => sum + (t.tarea_precio || 0), 0)
|
||||
})
|
||||
|
||||
// Funciones helper
|
||||
const getTipoColor = (tipo: string) => {
|
||||
const colores: Record<string, string> = {
|
||||
'corte': 'purple',
|
||||
'despulpe': 'orange',
|
||||
'clasificacion': 'blue',
|
||||
'secado': 'yellow',
|
||||
'empaque': 'green',
|
||||
'general': 'gray'
|
||||
}
|
||||
return colores[tipo?.toLowerCase()] || 'gray'
|
||||
}
|
||||
|
||||
const getEstadoColor = (estado: string) => {
|
||||
const colores: Record<string, string> = {
|
||||
'realizada': 'green',
|
||||
'pendiente': 'yellow',
|
||||
'anulado': 'red',
|
||||
'verificada': 'blue'
|
||||
}
|
||||
return colores[estado?.toLowerCase()] || 'green'
|
||||
}
|
||||
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat('es-HN', {
|
||||
style: 'currency',
|
||||
currency: 'HNL',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(value)
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return 'N/A'
|
||||
const date = new Date(dateString)
|
||||
return new Intl.DateTimeFormat('es-HN', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
async function copiarTexto() {
|
||||
const footer = `
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📊 RESUMEN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📅 Rango: ${props.rangoLegible}
|
||||
📋 Tareas: ${props.data.length}
|
||||
🕐 Generado: ${props.lastUpdated}`
|
||||
|
||||
let texto = `📝 DETALLE DE TAREAS\n\n`
|
||||
|
||||
props.data.forEach((tarea, idx) => {
|
||||
texto += `${idx + 1}. ${tarea.empleado_nombre} (${tarea.empleado_cedula || 'N/A'})\n`
|
||||
texto += ` Tarea: ${tarea.tarea_titulo}\n`
|
||||
texto += ` Precio: ${formatCurrency(tarea.tarea_precio || 0)}\n`
|
||||
texto += ` Tipo: ${tarea.tarea_tipo || 'general'}\n`
|
||||
texto += ` Fecha: ${formatDate(tarea.tarea_fecha)}\n`
|
||||
texto += ` Planilla: ${tarea.planilla_titulo || 'Sin planilla'}\n`
|
||||
texto += ` Estado: ${tarea.tarea_estado || 'realizada'}\n`
|
||||
if (tarea.tarea_observacion) {
|
||||
texto += ` Observación: ${tarea.tarea_observacion}\n`
|
||||
}
|
||||
texto += `\n`
|
||||
})
|
||||
|
||||
texto += `\n💰 TOTAL PRECIOS: ${formatCurrency(totalPrecios.value)}${footer}`
|
||||
|
||||
await navigator.clipboard.writeText(texto)
|
||||
alert('✅ Detalle de Tareas copiado al portapapeles')
|
||||
}
|
||||
|
||||
async function copiarJSON() {
|
||||
const json = JSON.stringify(props.data, null, 2)
|
||||
await navigator.clipboard.writeText(json)
|
||||
alert('✅ JSON copiado al portapapeles')
|
||||
}
|
||||
</script>
|
||||
@@ -321,8 +321,6 @@ const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
if (contextMenuVisible.value) {
|
||||
closeContextMenu()
|
||||
} else if (showConfigPanel.value) {
|
||||
showConfigPanel.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +277,30 @@
|
||||
</table>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Detalle de Planillas -->
|
||||
<EmpleadosDetallePlanillas
|
||||
v-if="data.detallePlanillas && data.detallePlanillas.length > 0"
|
||||
:data="data.detallePlanillas"
|
||||
:rango-legible="rangoLegible"
|
||||
:last-updated="lastUpdated"
|
||||
/>
|
||||
|
||||
<!-- Detalle de Tareas -->
|
||||
<EmpleadosDetalleTareas
|
||||
v-if="data.detalleTareas && data.detalleTareas.length > 0"
|
||||
:data="data.detalleTareas"
|
||||
:rango-legible="rangoLegible"
|
||||
:last-updated="lastUpdated"
|
||||
/>
|
||||
|
||||
<!-- Detalle de Asistencias -->
|
||||
<EmpleadosDetalleAsistencias
|
||||
v-if="data.detalleAsistencias && data.detalleAsistencias.length > 0"
|
||||
:data="data.detalleAsistencias"
|
||||
:rango-legible="rangoLegible"
|
||||
:last-updated="lastUpdated"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user