Jules was unable to complete the task in time. Please review the work done so far and provide feedback for Jules to continue.

This commit is contained in:
google-labs-jules[bot]
2025-05-30 06:41:49 +00:00
parent 2c43538db3
commit fe014b677b
15 changed files with 2563 additions and 24 deletions

View File

@@ -1,2 +1,181 @@
<template>
<div class="asistencia-card">
<div class="card-header">
<span class="empleado-id">Empleado ID: {{ asistencia.empleado_id }}</span>
<span :class="`estado-asistencia estado-${asistencia.estado?.toLowerCase().replace(/\s+/g, '-')}`">
{{ asistencia.estado || 'N/A' }}
</span>
</div>
<div class="card-body">
<p><strong>Entrada:</strong> {{ formatDateTime(asistencia.entrada) }}</p>
<p><strong>Salida:</strong> {{ asistencia.salida ? formatDateTime(asistencia.salida) : 'No registrada' }}</p>
<p v-if="asistencia.observacion" class="observacion">
<strong>Observación:</strong> {{ asistencia.observacion }}
</p>
<!-- Historial might be too complex for a card, but can be added if needed -->
<!-- <p v-if="asistencia.historial"><strong>Historial:</strong> {{ asistencia.historial }}</p> -->
</div>
<div class="card-actions">
<button @click="editAsistencia" class="action-button edit-button">Editar</button>
<button @click="confirmDeleteAsistencia" class="action-button delete-button">Eliminar</button>
</div>
</div>
</template>
<template></template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import { useAsistenciasStore } from '../../stores/useAsistencias';
const props = defineProps({
asistencia: {
type: Object,
required: true,
},
});
const emit = defineEmits(['edit']);
const asistenciasStore = useAsistenciasStore();
const formatDateTime = (dateTimeString) => {
if (!dateTimeString) return 'N/A';
const date = new Date(dateTimeString);
return date.toLocaleString('es-ES', { // Spanish locale
year: 'numeric',
month: '2-digit', // Using 2-digit for month for brevity in card
day: '2-digit', // Using 2-digit for day
hour: '2-digit',
minute: '2-digit',
// second: '2-digit', // Optional: include seconds
});
};
const editAsistencia = () => {
emit('edit', props.asistencia.id);
};
const confirmDeleteAsistencia = () => {
if (confirm(`¿Está seguro de que desea eliminar esta asistencia (ID: ${props.asistencia.id})?`)) {
deleteAsistenciaInternal();
}
};
const deleteAsistenciaInternal = async () => {
try {
await asistenciasStore.deleteAsistencia(props.asistencia.id);
// Optionally, emit 'deleted' or show notification
} catch (error) {
console.error('Error deleting asistencia:', error);
alert('Ocurrió un error al eliminar la asistencia.');
}
};
</script>
<style scoped>
.asistencia-card {
border: 1px solid #e0e0e0; /* Lighter border for a softer look */
padding: 16px;
margin-bottom: 16px;
border-radius: 8px;
background-color: #fff;
box-shadow: 0 2px 5px rgba(0,0,0,0.05); /* Softer shadow */
display: flex;
flex-direction: column;
transition: box-shadow 0.3s ease-in-out;
}
.asistencia-card:hover {
box-shadow: 0 4px 10px rgba(0,0,0,0.1); /* Enhanced shadow on hover */
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 10px; /* Increased padding */
border-bottom: 1px solid #f0f0f0; /* Even lighter border */
}
.empleado-id {
font-weight: bold;
color: #2c3e50; /* Dark blue/grey */
font-size: 1.05em;
}
.estado-asistencia {
padding: 5px 10px; /* Slightly more padding */
border-radius: 15px; /* Pill shape */
font-size: 0.8em;
font-weight: bold;
color: white;
text-transform: uppercase; /* Uppercase status */
letter-spacing: 0.5px;
}
.card-body p {
margin: 8px 0;
color: #555;
font-size: 0.95em;
line-height: 1.6; /* Improved line spacing */
}
.card-body p strong {
color: #333;
}
.observacion {
font-style: italic;
color: #666;
background-color: #f8f9fa; /* Very light grey background */
padding: 10px; /* More padding */
border-left: 3px solid #007bff; /* Blue accent line */
border-radius: 4px;
margin-top:10px;
font-size: 0.9em; /* Slightly smaller for observation */
}
.card-actions {
margin-top: auto;
padding-top: 16px; /* More space before actions */
display: flex;
justify-content: flex-end;
gap: 10px;
}
.action-button {
padding: 8px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9em;
font-weight: 500; /* Slightly bolder text on buttons */
transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease;
}
.action-button:hover{
transform: translateY(-2px); /* More pronounced lift */
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.edit-button {
background-color: #007bff;
color: white;
}
.edit-button:hover {
background-color: #0056b3;
}
.delete-button {
background-color: #dc3545;
color: white;
}
.delete-button:hover {
background-color: #c82333;
}
/* Estado specific styling */
.estado-pendiente { background-color: #ffc107; color: #212529;} /* Amber, dark text */
.estado-presente,
.estado-confirmada { background-color: #28a745; color: white;} /* Green */
.estado-ausente { background-color: #dc3545; color: white;} /* Red */
.estado-justificada { background-color: #17a2b8; color: white;} /* Info Blue */
.estado-cancelada,
.estado-anulada { background-color: #6c757d; color: white;} /* Gray */
</style>

View File

@@ -1,2 +1,163 @@
<template>
<div class="tabla-asistencias-container">
<table class="tabla-asistencias">
<thead>
<tr>
<th>ID</th>
<th>Empleado ID</th>
<th>Entrada</th>
<th>Salida</th>
<th>Estado</th>
<th>Observación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr v-if="!asistencias || asistencias.length === 0">
<td :colspan="7" style="text-align: center;">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>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<template></template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import { useAsistenciasStore } from '../../stores/useAsistencias';
const props = defineProps({
asistencias: {
type: Array,
required: true,
default: () => [],
},
});
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);
};
const confirmDeleteAsistencia = (asistencia) => {
if (confirm(`¿Está seguro de que desea eliminar la asistencia ID: ${asistencia.id} (Empleado: ${asistencia.empleado_id})?`)) {
deleteAsistenciaInternal(asistencia.id);
}
};
const deleteAsistenciaInternal = async (id) => {
try {
await asistenciasStore.deleteAsistencia(id);
// Optional: Show success notification or emit 'deleted' event
} catch (error) {
console.error(`Error deleting asistencia with id ${id}:`, error);
alert('Ocurrió un error al eliminar la asistencia.');
}
};
</script
<style scoped>
.tabla-asistencias-container {
overflow-x: auto;
}
.tabla-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: #007bff; /* Blue */
color: white;
}
.edit-button:hover {
background-color: #0056b3;
}
.delete-button {
background-color: #dc3545; /* Red */
color: white;
}
.delete-button:hover {
background-color: #c82333;
}
/* 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>