feat: Implement module-specific accent colors and enhance theme integration

This commit introduces module-specific accent colors and further integrates
the primary and secondary theme colors throughout the application's UI components.

Key features:
- Four new customizable accent colors, one for each main module:
    - Empleados
    - Tareas
    - Planillas
    - Asistencias
- These accent colors are configurable in the Settings view and persist in local storage.
- Broader application of global primary and secondary colors to common UI elements like navigation bars.
- Module-specific components now use their designated accent color for key visual elements (e.g., primary buttons, titles, icons, borders), providing better visual differentiation between modules.

Changes include:
- Extended `ui/src/stores/useUi.js` to manage state for the four new module accent colors, including actions and local storage persistence.
- Added a "Module Accent Colors" section with color pickers to `ui/src/views/SettingsView.vue`.
- Updated `ui/src/App.vue` to expose these module accent colors as CSS variables (e.g., `--accent-color-empleados`).
- Systematically updated styles in general UI components (`ui/src/components/ui/`) and all module-specific components (`ui/src/components/{module}/` and `ui/src/views/{module}/`) to utilize the new global and module-specific theme colors.
- Updated unit tests for `useUi.js` and component tests for `SettingsView.vue` to cover the new accent color functionality. A bug related to color input event handling in `SettingsView.vue` was identified and fixed during this process.

This enhancement provides a more visually distinct and customizable experience across different sections of the application.
This commit is contained in:
google-labs-jules[bot]
2025-05-31 00:09:55 +00:00
parent 4f1ec58a99
commit b5c8d88113
27 changed files with 3908 additions and 154 deletions

View File

@@ -98,7 +98,7 @@ const deleteAsistenciaInternal = async () => {
.empleado-id {
font-weight: bold;
color: #2c3e50; /* Dark blue/grey */
color: var(--accent-color-asistencias); /* Accent color */
font-size: 1.05em;
}
@@ -126,7 +126,7 @@ const deleteAsistenciaInternal = async () => {
color: #666;
background-color: #f8f9fa; /* Very light grey background */
padding: 10px; /* More padding */
border-left: 3px solid #007bff; /* Blue accent line */
border-left: 3px solid var(--accent-color-asistencias); /* Accent color for border */
border-radius: 4px;
margin-top:10px;
font-size: 0.9em; /* Slightly smaller for observation */
@@ -155,19 +155,19 @@ const deleteAsistenciaInternal = async () => {
}
.edit-button {
background-color: #007bff;
background-color: var(--accent-color-asistencias);
color: white;
}
.edit-button:hover {
background-color: #0056b3;
filter: brightness(0.9);
}
.delete-button {
background-color: #dc3545;
background-color: var(--warning-color); /* Use warning color for delete */
color: white;
}
.delete-button:hover {
background-color: #c82333;
filter: brightness(0.9);
}
/* Estado specific styling */

View File

@@ -136,19 +136,19 @@ const deleteAsistenciaInternal = async (id) => {
}
.edit-button {
background-color: #007bff; /* Blue */
background-color: var(--accent-color-asistencias);
color: white;
}
.edit-button:hover {
background-color: #0056b3;
filter: brightness(0.9);
}
.delete-button {
background-color: #dc3545; /* Red */
background-color: var(--warning-color); /* Use warning color for delete */
color: white;
}
.delete-button:hover {
background-color: #c82333;
filter: brightness(0.9);
}
/* Estado specific styling (using text color for tables is often cleaner) */

View File

@@ -4,7 +4,7 @@
<img
:src="employee.avatar_url || 'https://via.placeholder.com/150'"
alt="Avatar del empleado"
class="w-20 h-20 rounded-full mr-6 border-2 border-blue-500 object-cover"
class="w-20 h-20 rounded-full mr-6 border-2 object-cover employee-avatar-border"
/>
<div>
<h2 class="text-2xl font-bold text-gray-800">{{ employee.name }}</h2>
@@ -13,24 +13,24 @@
</div>
<div class="space-y-2">
<div v-if="employee.telefono" class="flex items-center text-gray-700">
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.308 1.154a11.042 11.042 0 005.516 5.516l1.154-2.308a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path></svg>
<svg class="w-5 h-5 mr-2 employee-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.308 1.154a11.042 11.042 0 005.516 5.516l1.154-2.308a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path></svg>
<span>{{ employee.telefono }}</span>
</div>
<div v-if="employee.ubicacion" class="flex items-center text-gray-700">
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
<svg class="w-5 h-5 mr-2 employee-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
<span>{{ employee.ubicacion }}</span>
</div>
<div v-if="employee.cedula" class="flex items-center text-gray-700">
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 012-2h2a2 2 0 012 2v1m-4 0h4m-6 10v-5m0 5v0z"></path></svg>
<svg class="w-5 h-5 mr-2 employee-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 012-2h2a2 2 0 012 2v1m-4 0h4m-6 10v-5m0 5v0z"></path></svg>
<span>Cedula: {{ employee.cedula }}</span>
</div>
<div v-if="employee.grupo_estudio" class="flex items-center text-gray-700">
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 14l9-5-9-5-9 5 9 5z"></path><path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-5.998 12.083 12.083 0 01.665-6.479L12 14z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0v3.945m0-3.945L6.161 10.58M17.839 10.58L12 14m5.839-3.42L12 14m0 0l6.161 3.42m-6.161-3.42L5.839 14.002"></path></svg>
<svg class="w-5 h-5 mr-2 employee-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 14l9-5-9-5-9 5 9 5z"></path><path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-5.998 12.083 12.083 0 01.665-6.479L12 14z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0v3.945m0-3.945L6.161 10.58M17.839 10.58L12 14m5.839-3.42L12 14m0 0l6.161 3.42m-6.161-3.42L5.839 14.002"></path></svg>
<span>Grupo Estudio: {{ employee.grupo_estudio }}</span>
</div>
</div>
<div class="mt-6 flex justify-end space-x-3">
<button @click="handleEdit" class="px-4 py-2 bg-blue-500 text-white text-sm font-medium rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2">
<button @click="handleEdit" class="edit-button px-4 py-2 text-white text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2">
Editar
</button>
<button @click="handleViewDetails" class="px-4 py-2 bg-gray-200 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2">
@@ -95,9 +95,29 @@ const handleViewDetails = () => {
flex-shrink: 0; /* Prevent SVGs from shrinking if text is long, ensuring icon consistency */
}
.employee-avatar-border {
border-color: var(--accent-color-empleados);
}
.employee-icon {
color: var(--accent-color-empleados);
}
.edit-button {
background-color: var(--accent-color-empleados);
/* Ensure focus ring also uses accent color if desired, e.g. by adding a specific class or style */
}
.edit-button:hover {
filter: brightness(1.1);
}
.edit-button:focus {
box-shadow: 0 0 0 2px var(--background-color), 0 0 0 4px var(--accent-color-empleados);
}
/* Styling for buttons for a more refined and interactive look */
button {
transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out, transform 0.1s ease-in-out;
transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out, transform 0.1s ease-in-out, filter 0.2s ease-in-out;
}
button:hover {
transform: translateY(-1px); /* Slight lift on hover */

View File

@@ -61,7 +61,7 @@
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<button
@click="handleEdit(employee.id)"
class="text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 p-1 rounded-md hover:bg-indigo-100 transition-all duration-150 ease-in-out"
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">
@@ -144,6 +144,18 @@ td {
}
/* 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);
}
button svg {
transition: transform 0.15s ease-in-out;
}

View File

@@ -82,7 +82,7 @@ const deletePlanilla = async () => {
.planilla-card h3 {
margin-top: 0;
color: #333;
color: var(--accent-color-planillas); /* Accent color for title */
}
.planilla-card p {
@@ -108,14 +108,20 @@ const deletePlanilla = async () => {
}
.actions button:first-child { /* Edit button */
background-color: #007bff;
background-color: var(--accent-color-planillas);
color: white;
}
.actions button:first-child:hover { /* Edit button hover */
filter: brightness(0.9);
}
.actions button:last-child { /* Delete button */
background-color: #dc3545;
background-color: var(--warning-color); /* Using warning color for delete */
color: white;
}
.actions button:last-child:hover { /* Delete button hover */
filter: brightness(0.9);
}
/* Example status styling */
.estado-pagado {

View File

@@ -133,21 +133,21 @@ const deletePlanillaInternal = async (id) => {
}
.edit-button {
background-color: #007bff;
background-color: var(--accent-color-planillas);
color: white;
}
.edit-button:hover {
background-color: #0056b3;
filter: brightness(0.9);
}
.delete-button {
background-color: #dc3545;
background-color: var(--warning-color); /* Using warning color for delete */
color: white;
}
.delete-button:hover {
background-color: #c82333;
filter: brightness(0.9);
}
/* Status styling (similar to cardPlanilla) */

View File

@@ -89,7 +89,7 @@ const deleteTareaInternal = async () => {
.tarea-card h4 {
margin-top: 0;
margin-bottom: 12px;
color: #333;
color: var(--accent-color-tareas); /* Accent color for title */
font-size: 1.15em; /* Slightly smaller than a typical h3 */
}
@@ -120,11 +120,11 @@ const deleteTareaInternal = async () => {
}
.edit-button {
background-color: #007bff; /* Primary blue */
background-color: var(--accent-color-tareas);
color: white;
}
.edit-button:hover {
background-color: #0056b3; /* Darker blue */
filter: brightness(0.9); /* Standard hover effect */
}
.delete-button {

View File

@@ -141,11 +141,11 @@ const deleteTareaInternal = async (id) => {
}
.edit-button {
background-color: #007bff; /* Blue */
background-color: var(--accent-color-tareas);
color: white;
}
.edit-button:hover {
background-color: #0056b3;
filter: brightness(0.9); /* Standard hover effect */
}
.delete-button {

View File

@@ -29,12 +29,18 @@ const sidebarClasses = computed(() => ui.sidebarOpen ? 'translate-x-0' : '-trans
<!-- barra lateral -->
<aside
:class="['fixed left-0 top-0 md:top-14 h-screen w-60 bg-white dark:bg-zinc-900 border-r border-gray-200 dark:border-zinc-800 flex flex-col select-none z-50 transform transition-transform duration-200 ease-in-out', sidebarClasses]">
:class="['fixed left-0 top-0 md:top-14 h-screen w-60 flex flex-col select-none z-50 transform transition-transform duration-200 ease-in-out', sidebarClasses]"
style="background-color: var(--background-color); border-right-color: var(--secondary-color); border-right-width: 1px;">
<!-- encabezado dentro de sidebar -->
<div class="flex items-center justify-between px-4 py-4 md:px-5 md:py-4 border-b border-gray-200 dark:border-zinc-800 md:border-none">
<span class="text-lg font-semibold text-teal-600 dark:text-teal-400 md:hidden">Núcleo</span>
<button class="h-8 w-8 inline-flex items-center justify-center text-gray-500 hover:text-teal-600" @click="ui.toggleSidebar">
<div class="flex items-center justify-between px-4 py-4 md:px-5 md:py-4 md:border-none"
style="border-bottom-color: var(--secondary-color); border-bottom-width: 1px;">
<span class="text-lg font-semibold md:hidden" style="color: var(--primary-color);">Núcleo</span>
<button class="h-8 w-8 inline-flex items-center justify-center text-gray-500"
:style="{ color: 'var(--primary-color)' }"
@mouseover="($event.target.style.color = 'var(--primary-color)')"
@mouseleave="($event.target.style.color = 'var(--secondary-color)')"
@click="ui.toggleSidebar">
</button>
</div>
@@ -45,10 +51,8 @@ const sidebarClasses = computed(() => ui.sidebarOpen ? 'translate-x-0' : '-trans
<li v-for="l in links" :key="l.to">
<RouterLink
:to="l.to"
class="flex items-center gap-3 w-full px-3 py-2 rounded-md font-medium transition group"
:class="activePath.startsWith(l.to)
? 'bg-teal-600 text-white shadow'
: 'text-gray-700 dark:text-gray-100 hover:bg-teal-100 hover:text-teal-900 dark:hover:bg-zinc-800'"
class="nav-link flex items-center gap-3 w-full px-3 py-2 rounded-md font-medium transition group"
:class="activePath.startsWith(l.to) ? 'active' : ''"
@click="ui.closeSidebar()"
>
<span class="text-lg" aria-hidden="true">{{ l.icon }}</span>
@@ -63,9 +67,35 @@ const sidebarClasses = computed(() => ui.sidebarOpen ? 'translate-x-0' : '-trans
<style scoped>
ul { list-style: none; padding-left: 0; }
.nav-link {
color: var(--text-color); /* Default text color */
}
.nav-link:hover {
background-color: var(--secondary-color);
color: var(--primary-color); /* Text color on hover */
}
.nav-link.active {
background-color: var(--primary-color);
color: white; /* Assuming primary color is dark enough for white text */
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px 0 rgba(0,0,0,0.06); /* Tailwind shadow-sm equivalent */
}
/* Scrollbar styling using primary color */
.custom-scroll::-webkit-scrollbar { width: 8px; }
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.4); border-radius: 4px; }
.custom-scroll:hover::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.7); }
.custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(13,148,136,.6) transparent; }
.custom-scroll::-webkit-scrollbar-thumb {
background-color: var(--primary-color);
opacity: 0.4;
border-radius: 4px;
}
.custom-scroll:hover::-webkit-scrollbar-thumb {
background-color: var(--primary-color);
opacity: 0.7;
}
.custom-scroll {
scrollbar-width: thin;
scrollbar-color: var(--primary-color) transparent; /* scrollbar-color: <thumb-color> <track-color> */
}
</style>

View File

@@ -5,19 +5,28 @@ const ui = useUi()
<template>
<!-- barra superior fija -->
<header class="fixed top-0 left-0 right-0 h-14 bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 flex items-center justify-between px-4 md:px-6 z-50 shadow-sm">
<header class="fixed top-0 left-0 right-0 h-14 flex items-center justify-between px-4 md:px-6 z-50 shadow-sm"
:style="{ backgroundColor: 'var(--background-color)', borderBottom: '1px solid var(--secondary-color)' }">
<!-- título -->
<h1 class="text-lg font-semibold tracking-wide text-teal-600 dark:text-teal-400 select-none">Núcleo</h1>
<h1 class="text-lg font-semibold tracking-wide select-none" :style="{ color: 'var(--primary-color)' }">Núcleo</h1>
<!-- botón hamburguesa (visible solo en mobile) -->
<button
@click="ui.toggleSidebar"
class="inline-flex items-center justify-center h-9 w-9 rounded-md bg-teal-600 text-white hover:bg-teal-700 transition ">
class="hamburger-button inline-flex items-center justify-center h-9 w-9 rounded-md text-white transition">
&#9776;
</button>
</header>
</template>
<style scoped>
/* sin estilos extra */
.hamburger-button {
background-color: var(--primary-color);
}
.hamburger-button:hover {
filter: brightness(1.1); /* Slight lighten/darken based on primary color */
}
/* Alternative hover if primary is very light or very dark, requiring specific color adjustment */
/* .hamburger-button:hover { background-color: var(--primary-color-hover); } */
/* Ensure --primary-color-hover is defined or calculated based on --primary-color if this approach is used */
</style>