Refactor: Standardize UI table components and update README
This commit introduces a standardized approach to table components within the UI. Key changes include: - Refactored tablaAsistencias.vue, tablaEmpleados.vue, tablaPlanillas.vue, and tablaTareas.vue to use Tailwind CSS for consistent styling. - Defined and applied common Tailwind utility patterns for table structure (container, header, body, rows, cells), action buttons (with SVG icons), and status indicators (badges). - Created a shared utility file at `ui/src/utils/formatters.js` for common functions like date formatting, currency formatting, text truncation, and status class generation, reducing code duplication. - Updated table components to use these shared utility functions. - Updated `ui/README.md` to document the UI modules, the standardized table structure, styling conventions, and usage of utility functions. This standardization enhances code maintainability, improves consistency in the user interface, and provides clear guidelines for future table component development.
This commit is contained in:
@@ -1,31 +1,39 @@
|
||||
<template>
|
||||
<div class="tabla-asistencias-container">
|
||||
<table class="tabla-asistencias">
|
||||
<thead>
|
||||
<div class="p-4 sm:p-6 bg-white shadow-md rounded-lg overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Empleado ID</th>
|
||||
<th>Entrada</th>
|
||||
<th>Salida</th>
|
||||
<th>Estado</th>
|
||||
<th>Observación</th>
|
||||
<th>Acciones</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Empleado ID</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Entrada</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Salida</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Estado</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Observación</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-if="!asistencias || asistencias.length === 0">
|
||||
<td :colspan="7" style="text-align: center;">No hay asistencias para mostrar.</td>
|
||||
<td colspan="7" class="px-6 py-10 text-center text-gray-500 text-lg">No hay asistencias para mostrar.</td>
|
||||
</tr>
|
||||
<tr v-for="asistencia in asistencias" :key="asistencia.id">
|
||||
<td>{{ asistencia.id }}</td>
|
||||
<td>{{ asistencia.empleado_id }}</td>
|
||||
<td>{{ formatDateTime(asistencia.entrada) }}</td>
|
||||
<td>{{ asistencia.salida ? formatDateTime(asistencia.salida) : 'N/A' }}</td>
|
||||
<td><span :class="`estado-${asistencia.estado?.toLowerCase().replace(/\s+/g, '-')}`">{{ asistencia.estado }}</span></td>
|
||||
<td :title="asistencia.observacion">{{ truncateText(asistencia.observacion, 50) }}</td>
|
||||
<td>
|
||||
<button @click="editAsistencia(asistencia.id)" class="action-button edit-button">Editar</button>
|
||||
<button @click="confirmDeleteAsistencia(asistencia)" class="action-button delete-button">Eliminar</button>
|
||||
<tr v-for="asistencia in asistencias" :key="asistencia.id" class="hover:bg-gray-100 transition-colors duration-150 ease-in-out">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ asistencia.id }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ asistencia.empleado_id }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ formatDateTime(asistencia.entrada) }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ asistencia.salida ? formatDateTime(asistencia.salida) : 'N/A' }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
<span :class="['px-2.5 py-0.5 rounded-full text-xs font-semibold', getStatusClass(asistencia.estado)]">{{ asistencia.estado }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 text-sm text-gray-700 max-w-xs whitespace-nowrap" :title="asistencia.observacion">{{ truncateText(asistencia.observacion, 50) }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="editAsistencia(asistencia.id)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-blue-600 hover:text-blue-800 hover:bg-blue-100 focus:ring-blue-500" title="Editar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /></svg>
|
||||
</button>
|
||||
<button @click="confirmDeleteAsistencia(asistencia)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-red-600 hover:text-red-800 hover:bg-red-100 focus:ring-red-500" title="Eliminar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12.56 0c1.153 0 2.24.03 3.22.077m3.22-.077L10.879 3.28a2.25 2.25 0 012.244-2.077h.093c.956 0 1.853.543 2.244 2.077L14.74 5.79m-4.858 0l-2.828-2.828A1.875 1.875 0 016.188 2.188l2.828 2.828m6.912 0l2.828-2.828a1.875 1.875 0 00-2.652-2.652L12 5.79M9.26 9h5.48L9.26 9z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -36,6 +44,7 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
||||
import { formatDateTime, truncateText, getStatusClass } from '../../utils/formatters.js';
|
||||
|
||||
const props = defineProps({
|
||||
asistencias: {
|
||||
@@ -49,24 +58,6 @@ const emit = defineEmits(['edit']);
|
||||
|
||||
const asistenciasStore = useAsistenciasStore();
|
||||
|
||||
const formatDateTime = (dateTimeString) => {
|
||||
if (!dateTimeString) return 'N/A';
|
||||
const date = new Date(dateTimeString);
|
||||
// Using UTC methods to ensure consistency if dates are stored in UTC
|
||||
const day = String(date.getUTCDate()).padStart(2, '0');
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0'); // Months are 0-indexed
|
||||
const year = date.getUTCFullYear();
|
||||
const hours = String(date.getUTCHours()).padStart(2, '0');
|
||||
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
||||
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
const truncateText = (text, maxLength) => {
|
||||
if (!text) return 'N/A';
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + '...';
|
||||
};
|
||||
|
||||
const editAsistencia = (id) => {
|
||||
emit('edit', id);
|
||||
};
|
||||
@@ -87,79 +78,3 @@ const deleteAsistenciaInternal = async (id) => {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabla-asistencias-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tabla-asistencias {
|
||||
/* Apply module-specific background color */
|
||||
background-color: var(--table-bg-color-asistencias);
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.tabla-asistencias th,
|
||||
.tabla-asistencias td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
vertical-align: middle; /* Good for table cells */
|
||||
}
|
||||
|
||||
.tabla-asistencias th {
|
||||
background-color: #f4f6f8; /* Light grey for header */
|
||||
font-weight: 600; /* Bolder text for header */
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tabla-asistencias tr:nth-child(even) {
|
||||
background-color: #f9fafb; /* Very light alternating row color */
|
||||
}
|
||||
|
||||
.tabla-asistencias tr:hover {
|
||||
background-color: #f0f0f0; /* Hover effect */
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 6px 10px;
|
||||
margin-right: 6px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
.action-button:hover {
|
||||
transform: translateY(-1px); /* Slight lift effect */
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background-color: var(--accent-color-asistencias);
|
||||
color: white;
|
||||
}
|
||||
.edit-button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: var(--warning-color); /* Use warning color for delete */
|
||||
color: white;
|
||||
}
|
||||
.delete-button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
/* Estado specific styling (using text color for tables is often cleaner) */
|
||||
.estado-pendiente { color: #ffc107; font-weight: bold; } /* Amber */
|
||||
.estado-presente,
|
||||
.estado-confirmada { color: #28a745; font-weight: bold; } /* Green */
|
||||
.estado-ausente { color: #dc3545; font-weight: bold; } /* Red */
|
||||
.estado-justificada { color: #17a2b8; font-weight: bold; } /* Info Blue */
|
||||
.estado-cancelada,
|
||||
.estado-anulada { color: #6c757d; font-weight: bold; } /* Gray */
|
||||
/* If you prefer background colors like in cards, copy those styles here, but they can be visually heavy in tables. */
|
||||
</style>
|
||||
|
||||
@@ -1,83 +1,72 @@
|
||||
<template>
|
||||
<div class="overflow-x-auto bg-white shadow-md rounded-lg p-4">
|
||||
<table class="min-w-full divide-y divide-gray-200 table-empleados">
|
||||
<div class="p-4 sm:p-6 bg-white shadow-md rounded-lg overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th scope="col" class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Avatar
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th scope="col" class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Nombre
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th scope="col" class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Cédula
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th scope="col" class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Teléfono
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th scope="col" class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Ubicación
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th scope="col" class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
ID CIAT
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th scope="col" class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Acciones
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-if="!employees || employees.length === 0">
|
||||
<td colspan="7" class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">
|
||||
<td colspan="7" class="px-6 py-10 text-center text-gray-500 text-lg">
|
||||
No hay empleados para mostrar.
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="employee in employees" :key="employee.id" class="hover:bg-gray-100 transition-colors duration-150 ease-in-out">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap">
|
||||
<img
|
||||
:src="employee.avatar_url || 'https://via.placeholder.com/40'"
|
||||
alt="Avatar"
|
||||
class="w-10 h-10 rounded-full object-cover border border-gray-300 shadow-sm"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm font-semibold text-gray-900">{{ employee.name }}</div>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
<div class="text-sm font-semibold text-gray-800">{{ employee.name }}</div>
|
||||
<div v-if="employee.grupo_estudio" class="text-xs text-gray-500">
|
||||
Grupo: {{ employee.grupo_estudio }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
{{ employee.cedula }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
{{ employee.telefono || '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
{{ employee.ubicacion }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
{{ employee.idciat || '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
|
||||
<button
|
||||
@click="handleEdit(employee.id)"
|
||||
class="edit-action-button p-1 rounded-md transition-all duration-150 ease-in-out"
|
||||
title="Editar Empleado"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
|
||||
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
@click="handleViewDetails(employee.id)"
|
||||
class="text-green-600 hover:text-green-800 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 p-1 rounded-md hover:bg-green-100 transition-all duration-150 ease-in-out"
|
||||
title="Ver Detalles del Empleado"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm font-medium">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="handleEdit(employee.id)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-blue-600 hover:text-blue-800 hover:bg-blue-100 focus:ring-blue-500" title="Editar Empleado">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /></svg>
|
||||
</button>
|
||||
<button @click="handleViewDetails(employee.id)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-green-600 hover:text-green-800 hover:bg-green-100 focus:ring-green-500" title="Ver Detalles">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -123,57 +112,17 @@ const handleViewDetails = (employeeId: string | number) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Enhancing table aesthetics and interactivity */
|
||||
.min-w-full {
|
||||
border-collapse: separate; /* Allows for rounded corners on table if desired, or better spacing */
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 600; /* semibold for headers */
|
||||
color: #4b5563; /* gray-600 for header text */
|
||||
}
|
||||
|
||||
td {
|
||||
color: #374151; /* gray-700 for cell text */
|
||||
}
|
||||
/* Scoped styles can be minimized or removed if Tailwind covers all needs */
|
||||
|
||||
.rounded-full {
|
||||
object-fit: cover; /* Ensures avatar images are displayed nicely within their circular bounds */
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); /* Subtle shadow for avatars */
|
||||
}
|
||||
|
||||
/* Action button icon styling */
|
||||
.edit-action-button {
|
||||
color: var(--accent-color-empleados);
|
||||
}
|
||||
.edit-action-button:hover {
|
||||
background-color: var(--accent-color-empleados);
|
||||
color: white; /* Assuming accent color is dark enough for white text */
|
||||
}
|
||||
.edit-action-button:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--background-color), 0 0 0 4px var(--accent-color-empleados);
|
||||
object-fit: cover; /* Ensures avatar images are displayed nicely */
|
||||
}
|
||||
|
||||
/* Optional: Keep icon transition if not handled by Tailwind's transition utilities on the button */
|
||||
button svg {
|
||||
transition: transform 0.15s ease-in-out;
|
||||
}
|
||||
button:hover svg {
|
||||
transform: scale(1.15); /* Slightly larger pop on hover for icons */
|
||||
}
|
||||
|
||||
/* Ensure consistent padding and alignment */
|
||||
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
||||
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
||||
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
|
||||
|
||||
/* Adding a subtle border to the table container itself */
|
||||
.overflow-x-auto {
|
||||
border: 1px solid #e5e7eb; /* Tailwind's gray-200 */
|
||||
}
|
||||
|
||||
table.table-empleados {
|
||||
background-color: var(--table-bg-color-empleados);
|
||||
transform: scale(1.1); /* Adjusted scale for a subtler effect */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
<template>
|
||||
<div class="tabla-planillas-container">
|
||||
<table class="tabla-planillas">
|
||||
<thead>
|
||||
<div class="p-4 sm:p-6 bg-white shadow-md rounded-lg overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Título</th>
|
||||
<th>Empleado ID</th>
|
||||
<th>Fecha Desde</th>
|
||||
<th>Fecha Hasta</th>
|
||||
<th>Total</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Título</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Empleado ID</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha Desde</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha Hasta</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Estado</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-if="planillas && planillas.length === 0">
|
||||
<td :colspan="8" style="text-align: center;">No hay planillas para mostrar.</td>
|
||||
<td colspan="8" class="px-6 py-10 text-center text-gray-500 text-lg">No hay planillas para mostrar.</td>
|
||||
</tr>
|
||||
<tr v-for="planilla in planillas" :key="planilla.id">
|
||||
<td>{{ planilla.id }}</td>
|
||||
<td>{{ planilla.titulo }}</td>
|
||||
<td>{{ planilla.empleado_id }}</td>
|
||||
<td>{{ formatDate(planilla.fecha_desde) }}</td>
|
||||
<td>{{ formatDate(planilla.fecha_hasta) }}</td>
|
||||
<td>{{ formatCurrency(planilla.total) }}</td>
|
||||
<td><span :class="`estado-${planilla.estado?.toLowerCase()}`">{{ planilla.estado }}</span></td>
|
||||
<td>
|
||||
<button @click="editPlanilla(planilla.id)" class="action-button edit-button">Editar</button>
|
||||
<button @click="confirmDeletePlanilla(planilla)" class="action-button delete-button">Eliminar</button>
|
||||
<tr v-for="planilla in planillas" :key="planilla.id" class="hover:bg-gray-100 transition-colors duration-150 ease-in-out">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ planilla.id }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ planilla.titulo }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ planilla.empleado_id }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ formatDate(planilla.fecha_desde) }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ formatDate(planilla.fecha_hasta) }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ formatCurrency(planilla.total) }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
<span :class="['px-2.5 py-0.5 rounded-full text-xs font-semibold', getStatusClass(planilla.estado)]">{{ planilla.estado }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="editPlanilla(planilla.id)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-blue-600 hover:text-blue-800 hover:bg-blue-100 focus:ring-blue-500" title="Editar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /></svg>
|
||||
</button>
|
||||
<button @click="confirmDeletePlanilla(planilla)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-red-600 hover:text-red-800 hover:bg-red-100 focus:ring-red-500" title="Eliminar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12.56 0c1.153 0 2.24.03 3.22.077m3.22-.077L10.879 3.28a2.25 2.25 0 012.244-2.077h.093c.956 0 1.853.543 2.244 2.077L14.74 5.79m-4.858 0l-2.828-2.828A1.875 1.875 0 016.188 2.188l2.828 2.828m6.912 0l2.828-2.828a1.875 1.875 0 00-2.652-2.652L12 5.79M9.26 9h5.48L9.26 9z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -38,6 +46,7 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { usePlanillasStore } from '../../stores/usePlanillas';
|
||||
import { formatDate, formatCurrency, getStatusClass } from '../../utils/formatters.js';
|
||||
|
||||
const props = defineProps({
|
||||
planillas: {
|
||||
@@ -51,24 +60,6 @@ const emit = defineEmits(['edit']); // Removed 'delete' as it's handled internal
|
||||
|
||||
const planillasStore = usePlanillasStore();
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return 'N/A';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
year: 'numeric',
|
||||
month: '2-digit', // Changed to 2-digit for table compactness
|
||||
day: '2-digit', // Changed to 2-digit for table compactness
|
||||
});
|
||||
};
|
||||
|
||||
const formatCurrency = (value) => {
|
||||
if (value == null) return 'N/A';
|
||||
return Number(value).toLocaleString('es-PY', {
|
||||
style: 'currency',
|
||||
currency: 'PYG',
|
||||
});
|
||||
};
|
||||
|
||||
const editPlanilla = (id) => {
|
||||
emit('edit', id);
|
||||
};
|
||||
@@ -90,79 +81,3 @@ const deletePlanillaInternal = async (id) => {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabla-planillas-container {
|
||||
overflow-x: auto; /* Allows table to be scrolled horizontally if needed */
|
||||
}
|
||||
|
||||
.tabla-planillas {
|
||||
background-color: var(--table-bg-color-planillas); /* Added */
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.tabla-planillas th,
|
||||
.tabla-planillas td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tabla-planillas th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tabla-planillas tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.tabla-planillas tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 5px 10px;
|
||||
margin-right: 5px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background-color: var(--accent-color-planillas);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: var(--warning-color); /* Using warning color for delete */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
/* Status styling (similar to cardPlanilla) */
|
||||
.estado-pagado {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
.estado-pendiente {
|
||||
color: orange;
|
||||
font-weight: bold;
|
||||
}
|
||||
.estado-anulado {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
/* text-decoration: line-through; */ /* Optional for table view */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
<template>
|
||||
<div class="tabla-tareas-container">
|
||||
<table class="tabla-tareas">
|
||||
<thead>
|
||||
<div class="p-4 sm:p-6 bg-white shadow-md rounded-lg overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Título</th>
|
||||
<th>Empleado ID</th>
|
||||
<th>Fecha</th>
|
||||
<th>Estado</th>
|
||||
<th>Tipo</th>
|
||||
<th>Precio</th>
|
||||
<th>Planilla ID</th>
|
||||
<th>Acciones</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Título</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Empleado ID</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Estado</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tipo</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Precio</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Planilla ID</th>
|
||||
<th class="px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-if="!tareas || tareas.length === 0">
|
||||
<td :colspan="9" style="text-align: center;">No hay tareas para mostrar.</td>
|
||||
<td colspan="9" class="px-6 py-10 text-center text-gray-500 text-lg">No hay tareas para mostrar.</td>
|
||||
</tr>
|
||||
<tr v-for="tarea in tareas" :key="tarea.id">
|
||||
<td>{{ tarea.id }}</td>
|
||||
<td>{{ tarea.titulo }}</td>
|
||||
<td>{{ tarea.empleado_id }}</td>
|
||||
<td>{{ formatDate(tarea.fecha) }}</td>
|
||||
<td><span :class="`estado-${tarea.estado?.toLowerCase().replace(/\s+/g, '-')}`">{{ tarea.estado }}</span></td>
|
||||
<td>{{ tarea.tipo || 'N/A' }}</td>
|
||||
<td>{{ tarea.precio != null ? formatCurrency(tarea.precio) : 'N/A' }}</td>
|
||||
<td>{{ tarea.planilla_id || 'N/A' }}</td>
|
||||
<td>
|
||||
<button @click="editTarea(tarea.id)" class="action-button edit-button">Editar</button>
|
||||
<button @click="confirmDeleteTarea(tarea)" class="action-button delete-button">Eliminar</button>
|
||||
<tr v-for="tarea in tareas" :key="tarea.id" class="hover:bg-gray-100 transition-colors duration-150 ease-in-out">
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ tarea.id }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ tarea.titulo }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ tarea.empleado_id }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ formatDate(tarea.fecha) }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
<span :class="['px-2.5 py-0.5 rounded-full text-xs font-semibold', getStatusClass(tarea.estado)]">{{ tarea.estado }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ tarea.tipo || 'N/A' }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ tarea.precio != null ? formatCurrency(tarea.precio) : 'N/A' }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">{{ tarea.planilla_id || 'N/A' }}</td>
|
||||
<td class="px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="editTarea(tarea.id)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-blue-600 hover:text-blue-800 hover:bg-blue-100 focus:ring-blue-500" title="Editar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /></svg>
|
||||
</button>
|
||||
<button @click="confirmDeleteTarea(tarea)" class="p-1.5 sm:p-2 rounded-md transition-all duration-150 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 text-red-600 hover:text-red-800 hover:bg-red-100 focus:ring-red-500" title="Eliminar">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12.56 0c1.153 0 2.24.03 3.22.077m3.22-.077L10.879 3.28a2.25 2.25 0 012.244-2.077h.093c.956 0 1.853.543 2.244 2.077L14.74 5.79m-4.858 0l-2.828-2.828A1.875 1.875 0 016.188 2.188l2.828 2.828m6.912 0l2.828-2.828a1.875 1.875 0 00-2.652-2.652L12 5.79M9.26 9h5.48L9.26 9z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -40,6 +48,7 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useTareasStore } from '../../stores/useTareas';
|
||||
import { formatDate, formatCurrency, getStatusClass } from '../../utils/formatters.js';
|
||||
|
||||
const props = defineProps({
|
||||
tareas: {
|
||||
@@ -53,24 +62,6 @@ const emit = defineEmits(['edit']);
|
||||
|
||||
const tareasStore = useTareasStore();
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return 'N/A';
|
||||
// Assuming dateString might be a full ISO string, ensure it's handled correctly by Date constructor
|
||||
const date = new Date(dateString);
|
||||
const day = String(date.getUTCDate()).padStart(2, '0'); // Use getUTCDate for consistency if dates are stored in UTC
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0'); // Months are 0-indexed
|
||||
const year = date.getUTCFullYear();
|
||||
return `${day}/${month}/${year}`;
|
||||
};
|
||||
|
||||
const formatCurrency = (value) => {
|
||||
if (value == null) return 'N/A';
|
||||
return Number(value).toLocaleString('es-PY', { // Paraguayan Guarani
|
||||
style: 'currency',
|
||||
currency: 'PYG',
|
||||
});
|
||||
};
|
||||
|
||||
const editTarea = (id) => {
|
||||
emit('edit', id);
|
||||
};
|
||||
@@ -92,77 +83,3 @@ const deleteTareaInternal = async (id) => {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabla-tareas-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tabla-tareas {
|
||||
background-color: var(--table-bg-color-tareas); /* Added */
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.tabla-tareas th,
|
||||
.tabla-tareas td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tabla-tareas th {
|
||||
background-color: #f4f6f8;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tabla-tareas tr:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.tabla-tareas tr:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 6px 10px;
|
||||
margin-right: 6px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
.action-button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background-color: var(--accent-color-tareas);
|
||||
color: white;
|
||||
}
|
||||
.edit-button:hover {
|
||||
filter: brightness(0.9); /* Standard hover effect */
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: #dc3545; /* Red */
|
||||
color: white;
|
||||
}
|
||||
.delete-button:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
/* Status styling (consistent with cardTarea) */
|
||||
.estado-pendiente { color: #ff9800; font-weight: bold; } /* Orange */
|
||||
.estado-realizada,
|
||||
.estado-completada, /* Synonyms */
|
||||
.estado-hecho { color: #4caf50; font-weight: bold; } /* Green */
|
||||
.estado-en-progreso { color: #2196f3; font-weight: bold; } /* Blue */
|
||||
.estado-anulada,
|
||||
.estado-cancelada { color: #f44336; font-weight: bold; } /* Red */
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user