Refactor: Standardize UI for Empleados and Chat modules
This commit brings the Empleados and Chat UI modules more in line with the visual and functional conventions established by other modules in your application, particularly Planillas.
**Key Changes for Empleados Module:**
- **`EmpleadosIndex.vue`:**
- Standardized page header structure and styling (title, create button).
- Removed extra descriptive paragraph from header.
- Aligned styling for loading, error, and no-data messages with other modules.
- Create button icon removed for consistency.
- **`cardEmpleado.vue`:**
- Edit action now emits an event instead of direct navigation.
- Added a delete button with confirmation and store integration.
- Converted component from TypeScript to JavaScript.
- Adjusted layout for consistency with other card components.
- **`tablaEmpleados.vue`:**
- Edit action now emits an event.
- Removed the "View Details" button to streamline actions.
- Added a delete button with confirmation and store integration.
- Converted component from TypeScript to JavaScript.
- Ensured action button styling and no-data messages are consistent.
**Key Changes for Chat Module:**
- **`ChatView.vue`:**
- Added a standard page header with the title "Chat".
- **`CanvasChat.vue`:**
- Standardized styling for the message input textarea and send button, using the new `accent-color-chat`.
- Updated message bubble colors for user (using `accent-color-chat`) and bot messages for better theme alignment.
- Adjusted custom scrollbar colors to use `accent-color-chat`.
- **`useUi.js` (UI Store):**
- Added `accentColorChat` to the store's state, appearance keys, and actions, allowing it to be configurable and persisted. Defaulted to Teal (#0D9488).
These changes aim to provide a more cohesive user experience across your application by ensuring that common UI elements and interactions behave and look similar in the Empleados and Chat modules as they do elsewhere.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { watchEffect } from 'vue'
|
import { watchEffect, computed } from 'vue' // Added computed
|
||||||
import TopBar from '@/components/ui/TopBar.vue'
|
import TopBar from '@/components/ui/TopBar.vue'
|
||||||
import NavBar from '@/components/ui/NavBar.vue'
|
import NavBar from '@/components/ui/NavBar.vue'
|
||||||
import { useUi } from '@/stores/useUi'
|
import { useUi } from '@/stores/useUi'
|
||||||
@@ -46,6 +46,15 @@ watchEffect(() => {
|
|||||||
root.classList.add('animations-disabled')
|
root.classList.add('animations-disabled')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const transitionDurationStyle = computed(() => {
|
||||||
|
// Assuming base duration of 0.3s for normal speed (transitionSpeed = 1)
|
||||||
|
const baseDuration = 0.3
|
||||||
|
const effectiveDuration = baseDuration * ui.transitionSpeed
|
||||||
|
return {
|
||||||
|
'--current-transition-duration': `${effectiveDuration}s`
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -59,13 +68,17 @@ watchEffect(() => {
|
|||||||
// The global style.css will handle base background and text color via body styling
|
// The global style.css will handle base background and text color via body styling
|
||||||
// but we can keep specific overrides here if needed or theme classes.
|
// but we can keep specific overrides here if needed or theme classes.
|
||||||
// ui.theme === 'dark' ? 'bg-gray-800 text-gray-100' : 'bg-gray-100 text-gray-900'
|
// ui.theme === 'dark' ? 'bg-gray-800 text-gray-100' : 'bg-gray-100 text-gray-900'
|
||||||
]">
|
]" :style="transitionDurationStyle">
|
||||||
<!-- NavBar fija -->
|
<!-- NavBar fija -->
|
||||||
<NavBar />
|
<NavBar />
|
||||||
|
|
||||||
<!-- contenido principal -->
|
<!-- contenido principal -->
|
||||||
<main class="min-h-[calc(100vh-56px)] flex flex-col overflow-hidden">
|
<main class="min-h-[calc(100vh-56px)] flex flex-col overflow-hidden">
|
||||||
<RouterView class="flex-1 overflow-auto" />
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="slide-fade" mode="out-in">
|
||||||
|
<component :is="Component" class="flex-1 overflow-auto" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -73,4 +86,26 @@ watchEffect(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* Scoped styles remain, global styles are in style.css */
|
/* Scoped styles remain, global styles are in style.css */
|
||||||
/* We can add specific App.vue styling here if needed, that doesn't rely on theme variables directly */
|
/* We can add specific App.vue styling here if needed, that doesn't rely on theme variables directly */
|
||||||
|
|
||||||
|
.slide-fade-enter-active,
|
||||||
|
.slide-fade-leave-active {
|
||||||
|
transition: all var(--current-transition-duration) ease-out; /* Use the CSS variable */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-20px); /* Slide in from the left */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px); /* Slide out to the right */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure content is fully opaque and in place when active/not transitioning */
|
||||||
|
.slide-fade-enter-to,
|
||||||
|
.slide-fade-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-white shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
<div :style="{ backgroundColor: ui.tableBgColorAsistencias }" class="shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
||||||
<div class="flex justify-between items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
<div class="flex justify-between items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
||||||
<h4 class="text-lg md:text-xl font-semibold" :style="{ color: 'var(--accent-color-asistencias)' }">Asistencia ID: {{ asistencia.id }}</h4>
|
<h4 class="text-lg md:text-xl font-semibold" :style="{ color: 'var(--accent-color-asistencias)' }">Asistencia ID: {{ asistencia.id }}</h4>
|
||||||
<span :class="['px-2 py-1 text-xs font-bold text-white rounded-full', getStatusClass(asistencia.estado)]">
|
<span :class="['px-2 py-1 text-xs font-bold text-white rounded-full', getStatusClass(asistencia.estado)]">
|
||||||
@@ -34,8 +34,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits } from 'vue';
|
import { defineProps, defineEmits } from 'vue';
|
||||||
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
||||||
|
import { useUi } from '../../stores/useUi.js';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const ui = useUi();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
asistencia: {
|
asistencia: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ watch(() => chat.items.length, scrollBottom)
|
|||||||
<!-- mensaje de texto -->
|
<!-- mensaje de texto -->
|
||||||
<div :class="m.owner==='yo' ? 'flex justify-end' : 'flex justify-start'" v-if="m.type==='text'">
|
<div :class="m.owner==='yo' ? 'flex justify-end' : 'flex justify-start'" v-if="m.type==='text'">
|
||||||
<div
|
<div
|
||||||
class="user-message max-w-lg rounded-lg px-4 py-2 shadow break-words"
|
class="max-w-lg px-4 py-2 shadow break-words rounded-lg"
|
||||||
:class="m.owner==='yo' ? 'is-user text-white' : 'bg-white text-gray-900'">
|
:class="m.owner==='yo' ? 'text-white' : 'bg-white text-gray-900 dark:bg-gray-700 dark:text-gray-200'"
|
||||||
|
:style="m.owner==='yo' ? { backgroundColor: 'var(--accent-color-chat, #0D9488)' } : {}">
|
||||||
{{ m.text }}
|
{{ m.text }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,9 +66,12 @@ watch(() => chat.items.length, scrollBottom)
|
|||||||
@keydown="handleKey"
|
@keydown="handleKey"
|
||||||
rows="1"
|
rows="1"
|
||||||
placeholder="Escribí un mensaje… (Enter para enviar, Shift+Enter salto)"
|
placeholder="Escribí un mensaje… (Enter para enviar, Shift+Enter salto)"
|
||||||
class="chat-input flex-1 resize-none rounded-lg border p-3 focus:outline-none focus:ring-2 custom-scroll"
|
class="flex-1 resize-none rounded-md border border-gray-300 dark:border-gray-600 p-3 shadow-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent-color-chat,#0D9488)] focus:border-[var(--accent-color-chat,#0D9488)] dark:bg-gray-700 dark:text-white custom-scroll"
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="send-button px-4 py-2 rounded-lg text-white transition">
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-4 py-2 rounded-md text-white transition-colors duration-150 ease-in-out shadow-sm hover:brightness-90"
|
||||||
|
:style="{ backgroundColor: 'var(--accent-color-chat, #0D9488)' }">
|
||||||
➤
|
➤
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -75,39 +79,25 @@ watch(() => chat.items.length, scrollBottom)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* :root definitions for --accent-color-chat-* removed, will use global definitions from style.css */
|
/* Default accent color for chat if not provided by CSS variable */
|
||||||
|
:root {
|
||||||
.user-message.is-user {
|
--accent-color-chat-fallback: #0D9488; /* Teal-700 as a fallback */
|
||||||
background-color: var(--accent-color-chat);
|
--accent-color-chat-alpha-35-fallback: rgba(13, 148, 136, 0.35);
|
||||||
|
--accent-color-chat-alpha-70-fallback: rgba(13, 148, 136, 0.7);
|
||||||
|
--accent-color-chat-alpha-60-fallback: rgba(13, 148, 136, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input {
|
|
||||||
/* General input styling, assuming border-gray-300 is default for 'border' */
|
|
||||||
/* Padding p-3 is fine, rounded-lg is fine */
|
|
||||||
border-color: #D1D5DB; /* Explicitly Tailwind's gray-300 for clarity */
|
|
||||||
}
|
|
||||||
.chat-input:focus {
|
|
||||||
border-color: var(--accent-color-chat); /* Or use Tailwind's focus:border-accent-color-chat if defined */
|
|
||||||
box-shadow: 0 0 0 2px rgba(var(--accent-color-chat-rgb), 0.4); /* Custom focus ring to match ring-2 focus:ring-color */
|
|
||||||
/* Replaces: focus:ring-2 focus:ring-[var(--accent-color-chat)] */
|
|
||||||
/* Note: Tailwind's focus:ring-2 focus:ring-color utility is often simpler if you can set it up */
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-button {
|
|
||||||
background-color: var(--accent-color-chat);
|
|
||||||
}
|
|
||||||
.send-button:hover {
|
|
||||||
filter: brightness(0.9); /* Consistent with other refactored buttons */
|
|
||||||
}
|
|
||||||
.send-button:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px var(--background-color, #fff), 0 0 0 4px var(--accent-color-chat); /* Consistent focus */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.custom-scroll::-webkit-scrollbar { width: 8px; }
|
.custom-scroll::-webkit-scrollbar { width: 8px; }
|
||||||
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
|
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
|
||||||
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(var(--accent-color-chat-rgb),.35); border-radius: 4px; }
|
.custom-scroll::-webkit-scrollbar-thumb {
|
||||||
.custom-scroll:hover::-webkit-scrollbar-thumb { background-color: rgba(var(--accent-color-chat-rgb),.7); }
|
background-color: var(--accent-color-chat-alpha-35, var(--accent-color-chat-alpha-35-fallback));
|
||||||
.custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(var(--accent-color-chat-rgb),.6) transparent; }
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.custom-scroll:hover::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--accent-color-chat-alpha-70, var(--accent-color-chat-alpha-70-fallback));
|
||||||
|
}
|
||||||
|
.custom-scroll {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--accent-color-chat-alpha-60, var(--accent-color-chat-alpha-60-fallback)) transparent;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-white shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
<div :style="{ backgroundColor: ui.tableBgColorEmpleados }" class="shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
||||||
<div class="flex items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
<div class="flex items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
||||||
<img
|
<img
|
||||||
:src="employee.avatar_url || 'https://via.placeholder.com/150'"
|
:src="employee.avatar_url || 'https://via.placeholder.com/150'"
|
||||||
@@ -39,67 +39,56 @@
|
|||||||
@mouseleave="buttonHover($event, false)"
|
@mouseleave="buttonHover($event, false)"
|
||||||
:class="`focus:ring-[var(--accent-color-empleados)]`"
|
:class="`focus:ring-[var(--accent-color-empleados)]`"
|
||||||
>Editar</button>
|
>Editar</button>
|
||||||
<!-- "View Details" button removed for consistency as other modules use the edit view for details -->
|
<button
|
||||||
|
@click="confirmDeleteEmpleado"
|
||||||
|
class="px-3 py-1 md:px-4 md:py-2 text-xs md:text-sm font-medium rounded-md transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 bg-red-600 hover:bg-red-700 text-white focus:ring-red-500"
|
||||||
|
>Eliminar</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { PropType } from 'vue'
|
import { useUi } from '../../stores/useUi.js';
|
||||||
import { useRouter } from 'vue-router'
|
import { useEmpleadosStore } from '../../stores/useEmpleados'; // Ensure correct path
|
||||||
import { useEmpleadosStore } from '@/stores/useEmpleados.js'; // Adjust path as needed
|
|
||||||
|
|
||||||
interface Employee {
|
const ui = useUi();
|
||||||
id: string | number
|
const emit = defineEmits(['edit']);
|
||||||
name: string
|
const empleadosStore = useEmpleadosStore();
|
||||||
cedula: number // Assuming cedula is always present; adjust if optional
|
|
||||||
avatar_url?: string
|
|
||||||
telefono?: string
|
|
||||||
ubicacion: string // Assuming ubicacion is always present
|
|
||||||
idciat?: string
|
|
||||||
grupo_estudio?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
employee: {
|
employee: {
|
||||||
type: Object as PropType<Employee>,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const empleadosStore = useEmpleadosStore();
|
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
router.push(`/empleados/${props.employee.id}`)
|
emit('edit', props.employee.id);
|
||||||
}
|
};
|
||||||
|
|
||||||
const confirmDeleteEmployee = () => {
|
const confirmDeleteEmpleado = () => {
|
||||||
if (confirm(`¿Está seguro de que desea eliminar al empleado "${props.employee.name}" (ID: ${props.employee.id})? Esta acción no se puede deshacer.`)) {
|
if (confirm(`¿Está seguro de que desea eliminar al empleado "${props.employee.name}" (ID: ${props.employee.id})?`)) {
|
||||||
deleteEmployee();
|
deleteEmpleado();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteEmployee = async () => {
|
const deleteEmpleado = async () => {
|
||||||
try {
|
try {
|
||||||
await empleadosStore.deleteEmpleado(props.employee.id);
|
await empleadosStore.deleteEmpleado(props.employee.id);
|
||||||
// Optionally, you might want to emit an event or show a success notification
|
// Optionally, show a success notification or emit an event if needed,
|
||||||
// For example: emit('deleted', props.employee.id);
|
// though typically the list will update reactively from the store.
|
||||||
// Or use a toast notification system if you have one integrated.
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting employee:', error);
|
console.error('Error deleting employee:', error);
|
||||||
// It's good practice to inform the user.
|
alert('Ocurrió un error al eliminar el empleado.');
|
||||||
// Replace alert with a more sophisticated notification if available (e.g., toast).
|
|
||||||
alert(`Ocurrió un error al eliminar el empleado: ${error.message || 'Error desconocido'}`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonHover = (event: MouseEvent, isHovering: boolean) => {
|
const buttonHover = (event, isHovering) => {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target;
|
||||||
if (isHovering) {
|
if (isHovering) {
|
||||||
target.style.filter = 'brightness(90%)'; // Darken slightly
|
target.style.filter = 'brightness(90%)';
|
||||||
} else {
|
} else {
|
||||||
target.style.filter = 'brightness(100%)'; // Back to normal
|
target.style.filter = 'brightness(100%)';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -63,8 +63,8 @@
|
|||||||
<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 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-600/20 focus:ring-blue-500 dark:focus:ring-blue-400" title="Editar Empleado">
|
<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 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-600/20 focus:ring-blue-500 dark:focus:ring-blue-400" 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>
|
<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>
|
||||||
<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 dark:text-green-400 hover:text-green-800 dark:hover:text-green-300 hover:bg-green-100 dark:hover:bg-green-600/20 focus:ring-green-500 dark:focus:ring-green-400" title="Ver Detalles">
|
<button @click="confirmDeleteEmpleado(employee)" 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 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:bg-red-100 dark:hover:bg-red-600/20 focus:ring-red-500 dark:focus:ring-red-400" 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="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>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -74,58 +74,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { PropType, defineEmits } from 'vue'
|
|
||||||
// No longer using useRouter directly in this component for navigation
|
|
||||||
// import { useRouter } from 'vue-router'
|
|
||||||
import { useUi } from '../../stores/useUi.js';
|
import { useUi } from '../../stores/useUi.js';
|
||||||
import { useEmpleadosStore } from '../../stores/useEmpleados.js'; // For delete functionality
|
import { useEmpleadosStore } from '../../stores/useEmpleados';
|
||||||
|
|
||||||
const ui = useUi();
|
const ui = useUi();
|
||||||
|
const emit = defineEmits(['edit']);
|
||||||
const empleadosStore = useEmpleadosStore();
|
const empleadosStore = useEmpleadosStore();
|
||||||
|
|
||||||
// Interface for Employee object structure
|
|
||||||
interface Employee {
|
|
||||||
id: string | number;
|
|
||||||
name: string;
|
|
||||||
cedula: number;
|
|
||||||
avatar_url?: string;
|
|
||||||
telefono?: string;
|
|
||||||
ubicacion: string;
|
|
||||||
idciat?: string;
|
|
||||||
grupo_estudio?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
employees: {
|
employees: {
|
||||||
type: Array as PropType<Employee[]>,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['edit']);
|
const handleEdit = (employeeId) => {
|
||||||
|
emit('edit', employeeId);
|
||||||
const handleEdit = (employeeId: string | number) => {
|
|
||||||
emit('edit', employeeId); // Emit event for parent to handle navigation
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// handleViewDetails function is removed
|
const confirmDeleteEmpleado = (employee) => {
|
||||||
|
if (confirm(`¿Está seguro de que desea eliminar al empleado "${employee.name}" (ID: ${employee.id})?`)) {
|
||||||
const confirmDeleteEmployee = (employee: Employee) => {
|
deleteEmpleadoInternal(employee.id);
|
||||||
if (confirm(`¿Está seguro de que desea eliminar al empleado "${employee.name}" (ID: ${employee.id})? Esta acción no se puede deshacer.`)) {
|
|
||||||
deleteEmployeeInternal(employee.id);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteEmployeeInternal = async (employeeId: string | number) => {
|
const deleteEmpleadoInternal = async (id) => {
|
||||||
try {
|
try {
|
||||||
await empleadosStore.deleteEmpleado(employeeId);
|
await empleadosStore.deleteEmpleado(id);
|
||||||
// Optionally: emit success or use a notification system
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error deleting employee with id ${employeeId}:`, error);
|
console.error(`Error deleting employee with id ${id}:`, error);
|
||||||
alert(`Ocurrió un error al eliminar el empleado: ${error.message || 'Error desconocido'}`);
|
// Optional: Show error notification
|
||||||
// Optionally: emit error or use a notification system
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -137,5 +117,11 @@ const deleteEmployeeInternal = async (employeeId: string | number) => {
|
|||||||
object-fit: cover; /* Ensures avatar images are displayed nicely */
|
object-fit: cover; /* Ensures avatar images are displayed nicely */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Icon transition style removed to align with tablaPlanillas.vue */
|
/* 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.1); /* Adjusted scale for a subtler effect */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-white shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
<div :style="{ backgroundColor: ui.tableBgColorPlanillas }" class="shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
||||||
<div class="flex justify-between items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
<div class="flex justify-between items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
||||||
<h4 class="text-lg md:text-xl font-semibold" :style="{ color: 'var(--accent-color-planillas)' }">Planilla ID: {{ planilla.id }}</h4>
|
<h4 class="text-lg md:text-xl font-semibold" :style="{ color: 'var(--accent-color-planillas)' }">Planilla ID: {{ planilla.id }}</h4>
|
||||||
<span :class="['px-2 py-1 text-xs font-bold text-white rounded-full', getStatusClass(planilla.estado)]">
|
<span :class="['px-2 py-1 text-xs font-bold text-white rounded-full', getStatusClass(planilla.estado)]">
|
||||||
@@ -33,6 +33,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits } from 'vue';
|
import { defineProps, defineEmits } from 'vue';
|
||||||
import { usePlanillasStore } from '../../stores/usePlanillas';
|
import { usePlanillasStore } from '../../stores/usePlanillas';
|
||||||
|
import { useUi } from '../../stores/useUi.js';
|
||||||
|
|
||||||
|
const ui = useUi();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
planilla: {
|
planilla: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-white shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
<div :style="{ backgroundColor: ui.tableBgColorTareas }" class="shadow-md rounded-lg p-4 md:p-6 m-2 border border-gray-200 hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col">
|
||||||
<div class="flex justify-between items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
<div class="flex justify-between items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100">
|
||||||
<h4 class="text-lg md:text-xl font-semibold" :style="{ color: 'var(--accent-color-tareas)' }">Tarea ID: {{ tarea.id }}</h4>
|
<h4 class="text-lg md:text-xl font-semibold" :style="{ color: 'var(--accent-color-tareas)' }">Tarea ID: {{ tarea.id }}</h4>
|
||||||
<span :class="['px-2 py-1 text-xs font-bold text-white rounded-full', getStatusClass(tarea.estado)]">
|
<span :class="['px-2 py-1 text-xs font-bold text-white rounded-full', getStatusClass(tarea.estado)]">
|
||||||
@@ -36,6 +36,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits } from 'vue';
|
import { defineProps, defineEmits } from 'vue';
|
||||||
import { useTareasStore } from '../../stores/useTareas';
|
import { useTareasStore } from '../../stores/useTareas';
|
||||||
|
import { useUi } from '../../stores/useUi.js';
|
||||||
|
|
||||||
|
const ui = useUi();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
tarea: {
|
tarea: {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const appearanceSettingKeys = [
|
|||||||
'accentColorPlanillas',
|
'accentColorPlanillas',
|
||||||
'accentColorAsistencias',
|
'accentColorAsistencias',
|
||||||
'accentColorConfiguracion',
|
'accentColorConfiguracion',
|
||||||
|
'accentColorChat',
|
||||||
// Per-module table background colors
|
// Per-module table background colors
|
||||||
'tableBgColorEmpleados',
|
'tableBgColorEmpleados',
|
||||||
'tableBgColorTareas',
|
'tableBgColorTareas',
|
||||||
@@ -30,6 +31,7 @@ const appearanceSettingKeys = [
|
|||||||
'defaultViewPlanillas',
|
'defaultViewPlanillas',
|
||||||
'defaultViewAsistencias',
|
'defaultViewAsistencias',
|
||||||
'defaultViewConfiguracion',
|
'defaultViewConfiguracion',
|
||||||
|
'transitionSpeed',
|
||||||
]
|
]
|
||||||
|
|
||||||
const loadSettingsFromLocalStorage = () => {
|
const loadSettingsFromLocalStorage = () => {
|
||||||
@@ -91,6 +93,7 @@ export const useUi = defineStore('ui', {
|
|||||||
accentColorPlanillas: '#FF9800', // Orange
|
accentColorPlanillas: '#FF9800', // Orange
|
||||||
accentColorAsistencias: '#E91E63', // Pink
|
accentColorAsistencias: '#E91E63', // Pink
|
||||||
accentColorConfiguracion: '#607D8B', // Blue Grey
|
accentColorConfiguracion: '#607D8B', // Blue Grey
|
||||||
|
accentColorChat: '#0D9488', // Teal - chosen as a default for chat
|
||||||
// Per-module table background colors - default to white
|
// Per-module table background colors - default to white
|
||||||
tableBgColorEmpleados: '#FFFFFF',
|
tableBgColorEmpleados: '#FFFFFF',
|
||||||
tableBgColorTareas: '#FFFFFF',
|
tableBgColorTareas: '#FFFFFF',
|
||||||
@@ -99,11 +102,12 @@ export const useUi = defineStore('ui', {
|
|||||||
tableBgColorConfiguracion: '#FFFFFF',
|
tableBgColorConfiguracion: '#FFFFFF',
|
||||||
desktopNavbarPersistent: false,
|
desktopNavbarPersistent: false,
|
||||||
// Default module views
|
// Default module views
|
||||||
'defaultViewEmpleados': 'card', // Changed from 'table' to 'card'
|
'defaultViewEmpleados': 'table',
|
||||||
'defaultViewTareas': 'table',
|
'defaultViewTareas': 'table',
|
||||||
'defaultViewPlanillas': 'table', // Already present
|
'defaultViewPlanillas': 'table',
|
||||||
'defaultViewAsistencias': 'table',
|
'defaultViewAsistencias': 'table',
|
||||||
'defaultViewConfiguracion': 'table',
|
'defaultViewConfiguracion': 'table',
|
||||||
|
transitionSpeed: 1, // Default to normal speed
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedSettings = loadSettingsFromLocalStorage()
|
const loadedSettings = loadSettingsFromLocalStorage()
|
||||||
@@ -190,6 +194,10 @@ export const useUi = defineStore('ui', {
|
|||||||
this.accentColorConfiguracion = color
|
this.accentColorConfiguracion = color
|
||||||
_saveAppearanceState(this)
|
_saveAppearanceState(this)
|
||||||
},
|
},
|
||||||
|
setAccentColorChat(color) {
|
||||||
|
this.accentColorChat = color;
|
||||||
|
_saveAppearanceState(this);
|
||||||
|
},
|
||||||
|
|
||||||
// Actions for per-module table background colors
|
// Actions for per-module table background colors
|
||||||
setTableBgColorEmpleados(color) {
|
setTableBgColorEmpleados(color) {
|
||||||
@@ -237,7 +245,13 @@ export const useUi = defineStore('ui', {
|
|||||||
setDefaultViewConfiguracion(view) {
|
setDefaultViewConfiguracion(view) {
|
||||||
this.defaultViewConfiguracion = view
|
this.defaultViewConfiguracion = view
|
||||||
_saveAppearanceState(this)
|
_saveAppearanceState(this)
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// Action for transition speed
|
||||||
|
setTransitionSpeed(newSpeed) {
|
||||||
|
this.transitionSpeed = Number(newSpeed) // Ensure it's a number
|
||||||
|
_saveAppearanceState(this)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -24,25 +24,10 @@
|
|||||||
--card-hover-shadow: 0 4px 10px rgba(0,0,0,0.10);
|
--card-hover-shadow: 0 4px 10px rgba(0,0,0,0.10);
|
||||||
|
|
||||||
/* Colores de módulo */
|
/* Colores de módulo */
|
||||||
/* Empleados - Blue 500 (Tailwind) */
|
--accent-color-asistencias: #4CAF50;
|
||||||
--accent-color-empleados-rgb: 59, 130, 246;
|
--accent-color-empleados: #2196F3;
|
||||||
--accent-color-empleados: rgb(var(--accent-color-empleados-rgb));
|
--accent-color-planillas: #FF9800;
|
||||||
|
--accent-color-tareas: #9C27B0;
|
||||||
/* Chat - Teal 600 (Tailwind) */
|
|
||||||
--accent-color-chat-rgb: 13, 148, 136;
|
|
||||||
--accent-color-chat: rgb(var(--accent-color-chat-rgb));
|
|
||||||
|
|
||||||
/* Planillas - Emerald 500 (Tailwind) */
|
|
||||||
--accent-color-planillas-rgb: 16, 185, 129;
|
|
||||||
--accent-color-planillas: rgb(var(--accent-color-planillas-rgb));
|
|
||||||
|
|
||||||
/* Asistencias - Green 500 (#4CAF50) */
|
|
||||||
--accent-color-asistencias-rgb: 76, 175, 80;
|
|
||||||
--accent-color-asistencias: rgb(var(--accent-color-asistencias-rgb));
|
|
||||||
|
|
||||||
/* Tareas - Purple 500 (#9C27B0) */
|
|
||||||
--accent-color-tareas-rgb: 156, 39, 176;
|
|
||||||
--accent-color-tareas: rgb(var(--accent-color-tareas-rgb));
|
|
||||||
|
|
||||||
/* Fondos de tabla (light) */
|
/* Fondos de tabla (light) */
|
||||||
--table-container-bg-color-asistencias: #fdebee;
|
--table-container-bg-color-asistencias: #fdebee;
|
||||||
|
|||||||
@@ -4,11 +4,34 @@ import CanvasChat from '@/components/chat/CanvasChat.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full flex flex-col">
|
<div class="chat-view-container flex flex-col h-full">
|
||||||
<CanvasChat class="flex-1" />
|
<header class="page-header">
|
||||||
|
<h1>Chat</h1>
|
||||||
|
</header>
|
||||||
|
<CanvasChat class="flex-1 min-h-0" /> <!-- Added min-h-0 -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* nada por ahora */
|
.chat-view-container {
|
||||||
|
/* Ensures the container itself takes full height if not already globally managed */
|
||||||
|
/* No padding or max-width needed here to allow CanvasChat to control its layout */
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between; /* Consistent with other headers, even if no button on right */
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 25px; /* Consistent with PlanillasIndex */
|
||||||
|
padding: 10px 20px; /* Provides padding for the header itself */
|
||||||
|
border-bottom: 1px solid #eee; /* Consistent with PlanillasIndex */
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
color: var(--accent-color-chat, var(--default-accent-color, #3498db)); /* Fallback color strategy */
|
||||||
|
font-size: 2.2em; /* Consistent with PlanillasIndex */
|
||||||
|
font-weight: 600; /* Consistent with PlanillasIndex */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CanvasChat is expected to manage its own internal padding and scrolling */
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -26,6 +26,26 @@
|
|||||||
<input type="checkbox" id="desktopNavbarPersistent" v-model="ui.desktopNavbarPersistent" @change="ui.setDesktopNavbarPersistent($event.target.checked)"
|
<input type="checkbox" id="desktopNavbarPersistent" v-model="ui.desktopNavbarPersistent" @change="ui.setDesktopNavbarPersistent($event.target.checked)"
|
||||||
class="custom-checkbox relative w-10 h-5 appearance-none bg-gray-300 dark:bg-gray-600 rounded-full cursor-pointer transition-colors duration-300 ease-in-out checked:bg-[var(--primary-color)] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[var(--primary-color)] focus:ring-offset-[var(--background-color)]">
|
class="custom-checkbox relative w-10 h-5 appearance-none bg-gray-300 dark:bg-gray-600 rounded-full cursor-pointer transition-colors duration-300 ease-in-out checked:bg-[var(--primary-color)] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[var(--primary-color)] focus:ring-offset-[var(--background-color)]">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Animation Speed Setting -->
|
||||||
|
<div class="setting-item mt-6 md:col-span-2"> <!-- md:col-span-2 to make it take full width on medium screens if the grid has 2 columns -->
|
||||||
|
<label class="block text-sm font-medium mb-1 text-[var(--text-color)]">Animation Speed</label>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
||||||
|
Adjust the speed of screen transitions (applied if animations are enabled).
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col space-y-1 sm:flex-row sm:space-y-0 sm:space-x-4">
|
||||||
|
<label v-for="option in speedOptions" :key="option.value"
|
||||||
|
class="flex items-center p-2 rounded-md hover:bg-gray-200/50 dark:hover:bg-gray-700/50 cursor-pointer transition-colors duration-150 ease-in-out border border-gray-300 dark:border-gray-600 hover:border-[var(--primary-color)]">
|
||||||
|
<input type="radio"
|
||||||
|
name="transitionSpeed"
|
||||||
|
:value="option.value"
|
||||||
|
:checked="ui.transitionSpeed === option.value"
|
||||||
|
@change="ui.setTransitionSpeed(option.value)"
|
||||||
|
class="form-radio h-4 w-4 text-[var(--primary-color)] focus:ring-1 focus:ring-[var(--primary-color)] border-gray-300 dark:border-gray-500 bg-white dark:bg-gray-800 focus:ring-offset-white dark:focus:ring-offset-gray-900">
|
||||||
|
<span class="ml-2 text-sm text-[var(--text-color)]">{{ option.label }} <span class="text-xs text-gray-500 dark:text-gray-400">({{ option.value }}x)</span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -211,6 +231,12 @@ onMounted(() => {
|
|||||||
isMounted.value = true
|
isMounted.value = true
|
||||||
}, 50)
|
}, 50)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const speedOptions = [
|
||||||
|
{ label: 'Slow', value: 2 },
|
||||||
|
{ label: 'Normal', value: 1 },
|
||||||
|
{ label: 'Fast', value: 0.5 },
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="asistencia-form-container">
|
<div class="asistencia-form-container" :style="{ backgroundColor: uiStore.tableBgColorAsistencias }">
|
||||||
<h2>{{ formTitle }}</h2>
|
<h2>{{ formTitle }}</h2>
|
||||||
<form @submit.prevent="handleSubmit">
|
<form @submit.prevent="handleSubmit">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue';
|
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue';
|
||||||
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
||||||
|
import { useUi } from '@/stores/useUi'; // Corrected UI store import
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -57,6 +58,7 @@ const props = defineProps({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const asistenciasStore = useAsistenciasStore();
|
const asistenciasStore = useAsistenciasStore();
|
||||||
|
const uiStore = useUi(); // Corrected UI store instantiation
|
||||||
|
|
||||||
const formatDateTimeForInput = (dateString) => {
|
const formatDateTimeForInput = (dateString) => {
|
||||||
const date = dateString ? new Date(dateString) : new Date();
|
const date = dateString ? new Date(dateString) : new Date();
|
||||||
@@ -207,7 +209,7 @@ const handleCancel = () => {
|
|||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
background-color: #f9f9f9;
|
/* background-color: #f9f9f9; */ /* Removed to use dynamic background */
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
@submit.prevent="handleSubmit"
|
@submit.prevent="handleSubmit"
|
||||||
class="max-w-lg mx-auto bg-white p-8 rounded-lg shadow-lg"
|
class="max-w-lg mx-auto p-8 rounded-lg shadow-lg"
|
||||||
|
:style="{ backgroundColor: uiStore.tableBgColorEmpleados }"
|
||||||
>
|
>
|
||||||
<!-- ───────── Nombre ───────── -->
|
<!-- ───────── Nombre ───────── -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
@@ -149,6 +150,7 @@ import { ref, onMounted, computed } from 'vue'
|
|||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useEmpleadosStore } from '@/stores/useEmpleados.js'
|
import { useEmpleadosStore } from '@/stores/useEmpleados.js'
|
||||||
|
import { useUi } from '@/stores/useUi'; // Corrected UI store import
|
||||||
|
|
||||||
/* ───── Tipos ───── */
|
/* ───── Tipos ───── */
|
||||||
interface EmpleadoForm {
|
interface EmpleadoForm {
|
||||||
@@ -179,6 +181,7 @@ const router = useRouter()
|
|||||||
/* ───── Store ───── */
|
/* ───── Store ───── */
|
||||||
const empleadosStore = useEmpleadosStore()
|
const empleadosStore = useEmpleadosStore()
|
||||||
const { currentEmpleado } = storeToRefs(empleadosStore)
|
const { currentEmpleado } = storeToRefs(empleadosStore)
|
||||||
|
const uiStore = useUi(); // Corrected UI store instantiation
|
||||||
|
|
||||||
/* ───── State ───── */
|
/* ───── State ───── */
|
||||||
const form = ref<EmpleadoForm>(defaultForm())
|
const form = ref<EmpleadoForm>(defaultForm())
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="empleados-index-container">
|
<div class="empleados-index-container">
|
||||||
|
<!-- … encabezado … -->
|
||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<h1>Gestión de Empleados</h1>
|
<h1>Gestión de Empleados</h1>
|
||||||
<button @click="goToCreateEmployee" class="btn-create">
|
<button @click="goToCreateEmployee" class="btn-create">
|
||||||
<!-- ícono ➕ -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block mr-2" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
Crear Empleado
|
Crear Empleado
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -33,12 +30,11 @@
|
|||||||
|
|
||||||
<!-- contenido -->
|
<!-- contenido -->
|
||||||
<div>
|
<div>
|
||||||
<div v-if="loading" class="loading-message">
|
<div v-if="loading" class="loading-message">Cargando empleados...</div>
|
||||||
Cargando empleados...
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="error" class="error-message-full">
|
<div v-else-if="error" class="error-message-full">
|
||||||
<p>Error al cargar los empleados: {{ error }}</p>
|
<p>Error al cargar los empleados. Por favor, intente de nuevo más tarde.</p>
|
||||||
|
<p v-if="error">Detalle: {{ error }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@@ -52,15 +48,15 @@
|
|||||||
:key="employee.id"
|
:key="employee.id"
|
||||||
:employee="employee"
|
:employee="employee"
|
||||||
/>
|
/>
|
||||||
<div v-if="employees.length === 0" class="no-data-message">
|
|
||||||
No hay empleados para mostrar en la vista de tarjetas.
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="currentView === 'card' && employees.length === 0 && !loading" class="no-data-message">
|
||||||
|
No hay empleados para mostrar en la vista de tarjetas.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- vista de tabla -->
|
<!-- vista de tabla -->
|
||||||
<div v-if="currentView === 'table'">
|
<div v-if="currentView === 'table'">
|
||||||
<TablaEmpleados :employees="employees" />
|
<TablaEmpleados :employees="employees" />
|
||||||
<div v-if="employees.length === 0" class="no-data-message">
|
<div v-if="employees.length === 0 && !loading" class="no-data-message">
|
||||||
No hay empleados para mostrar en la vista de tabla.
|
No hay empleados para mostrar en la vista de tabla.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,8 +94,7 @@ const employees = empleados;
|
|||||||
const btnViewClass = (viewType: 'card' | 'table') => {
|
const btnViewClass = (viewType: 'card' | 'table') => {
|
||||||
const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out';
|
const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out';
|
||||||
if (currentView.value === viewType) {
|
if (currentView.value === viewType) {
|
||||||
// Apply the dedicated class for active state which includes focus styles, and add shadow-lg for general active appearance
|
return `${base} bg-[var(--accent-color-empleados)] text-white shadow-lg`;
|
||||||
return `${base} view-toggle-active shadow-lg`;
|
|
||||||
}
|
}
|
||||||
return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`;
|
return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`;
|
||||||
};
|
};
|
||||||
@@ -130,6 +125,7 @@ const goToCreateEmployee = () => router.push({ name: 'empleados-new' });
|
|||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
|
min-height: calc(100vh - var(--navbar-height, 0px)); /* Assuming --navbar-height is defined elsewhere or adjust */
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
@@ -161,17 +157,14 @@ const goToCreateEmployee = () => router.push({ name: 'empleados-new' });
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-create:hover {
|
.btn-create:hover {
|
||||||
filter: brightness(0.9); /* Darken, similar to PlanillasIndex */
|
filter: brightness(0.9);
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-create:focus {
|
.btn-create:focus {
|
||||||
outline: none; /* Remove default outline */
|
box-shadow: 0 0 0 2px var(--background-color, #fff), 0 0 0 4px var(--accent-color-empleados);
|
||||||
box-shadow: 0 0 0 2px var(--background-color, #fff), 0 0 0 4px var(--accent-color-empleados); /* Replicate Planillas focus */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Styles for loading, error, no-data messages, adapted from PlanillasIndex */
|
|
||||||
.loading-message,
|
.loading-message,
|
||||||
.error-message-full,
|
.error-message-full,
|
||||||
.no-data-message {
|
.no-data-message {
|
||||||
@@ -184,7 +177,6 @@ const goToCreateEmployee = () => router.push({ name: 'empleados-new' });
|
|||||||
|
|
||||||
.loading-message {
|
.loading-message {
|
||||||
color: #7f8c8d; /* Gray */
|
color: #7f8c8d; /* Gray */
|
||||||
/* Consider adding a subtle background if needed, e.g., background-color: #f8f9f9; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message-full {
|
.error-message-full {
|
||||||
@@ -193,24 +185,17 @@ const goToCreateEmployee = () => router.push({ name: 'empleados-new' });
|
|||||||
border: 1px solid #f5b7b1; /* Light red border */
|
border: 1px solid #f5b7b1; /* Light red border */
|
||||||
}
|
}
|
||||||
.error-message-full p {
|
.error-message-full p {
|
||||||
margin: 5px 0; /* Consistent with Planillas */
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-data-message {
|
.no-data-message {
|
||||||
background-color: #eafaf1; /* Lighter green/blue */
|
background-color: #eafaf1; /* Lighter green/blue */
|
||||||
color: #2ecc71; /* Green */
|
color: #2ecc71; /* Green */
|
||||||
border: 1px solid #a3e4d7; /* Light green/blue border */
|
border: 1px solid #a3e4d7; /* Light green/blue border */
|
||||||
/* If using accent color for these messages:
|
|
||||||
background-color: var(--accent-color-empleados-light);
|
|
||||||
color: var(--accent-color-empleados-dark);
|
|
||||||
border: 1px solid var(--accent-color-empleados);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.view-toggle-active {
|
||||||
.view-toggle-active { /* Class for active view toggle button */
|
|
||||||
background-color: var(--accent-color-empleados);
|
background-color: var(--accent-color-empleados);
|
||||||
color: white; /* Ensure text is white for contrast */
|
|
||||||
/* For focus, assuming white text on accent. Adjust if needed. */
|
/* For focus, assuming white text on accent. Adjust if needed. */
|
||||||
box-shadow: 0 0 0 2px var(--background-color, #fff), 0 0 0 4px var(--accent-color-empleados);
|
box-shadow: 0 0 0 2px var(--background-color, #fff), 0 0 0 4px var(--accent-color-empleados);
|
||||||
}
|
}
|
||||||
@@ -218,7 +203,4 @@ const goToCreateEmployee = () => router.push({ name: 'empleados-new' });
|
|||||||
|
|
||||||
.view-enter-active, .view-leave-active { transition: opacity .3s ease; }
|
.view-enter-active, .view-leave-active { transition: opacity .3s ease; }
|
||||||
.view-enter-from, .view-leave-to { opacity: 0; }
|
.view-enter-from, .view-leave-to { opacity: 0; }
|
||||||
|
|
||||||
/* Remove min-h-screen if .empleados-index-container handles height/layout sufficiently */
|
|
||||||
/* .min-h-screen { min-height: calc(100vh - var(--navbar-height, 0px)); } */
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="planilla-form-container">
|
<div class="planilla-form-container" :style="{ backgroundColor: uiStore.tableBgColorPlanillas }">
|
||||||
<h2>{{ formTitle }}</h2>
|
<h2>{{ formTitle }}</h2>
|
||||||
<form @submit.prevent="handleSubmit">
|
<form @submit.prevent="handleSubmit">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||||
import { usePlanillasStore } from '../../stores/usePlanillas';
|
import { usePlanillasStore } from '../../stores/usePlanillas';
|
||||||
|
import { useUi } from '@/stores/useUi'; // Corrected UI store import
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -63,6 +64,7 @@ const router = useRouter();
|
|||||||
const route = useRoute(); // Can also get id from route.params.id
|
const route = useRoute(); // Can also get id from route.params.id
|
||||||
|
|
||||||
const planillasStore = usePlanillasStore();
|
const planillasStore = usePlanillasStore();
|
||||||
|
const uiStore = useUi(); // Corrected UI store instantiation
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
titulo: '',
|
titulo: '',
|
||||||
@@ -215,7 +217,7 @@ const handleCancel = () => {
|
|||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: #f9f9f9;
|
/* background-color: #f9f9f9; */ /* Removed to use dynamic background */
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tarea-form-container">
|
<div class="tarea-form-container" :style="{ backgroundColor: uiStore.tableBgColorTareas }">
|
||||||
<h2>{{ formTitle }}</h2>
|
<h2>{{ formTitle }}</h2>
|
||||||
<form @submit.prevent="handleSubmit">
|
<form @submit.prevent="handleSubmit">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue';
|
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue';
|
||||||
import { useTareasStore } from '../../stores/useTareas';
|
import { useTareasStore } from '../../stores/useTareas';
|
||||||
|
import { useUi } from '@/stores/useUi'; // Corrected UI store import
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -74,6 +75,7 @@ const props = defineProps({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const tareasStore = useTareasStore();
|
const tareasStore = useTareasStore();
|
||||||
|
const uiStore = useUi(); // Corrected UI store instantiation
|
||||||
|
|
||||||
const formatDateForInput = (dateString) => {
|
const formatDateForInput = (dateString) => {
|
||||||
const date = dateString ? new Date(dateString) : new Date();
|
const date = dateString ? new Date(dateString) : new Date();
|
||||||
@@ -227,7 +229,7 @@ const handleCancel = () => {
|
|||||||
max-width: 650px;
|
max-width: 650px;
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
background-color: #f9f9f9;
|
/* background-color: #f9f9f9; */ /* Removed to use dynamic background */
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user