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 @@
|
||||
<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">
|
||||
<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)]">
|
||||
@@ -34,8 +34,11 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
||||
import { useUi } from '../../stores/useUi.js';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const ui = useUi();
|
||||
|
||||
const props = defineProps({
|
||||
asistencia: {
|
||||
type: Object,
|
||||
|
||||
@@ -47,8 +47,9 @@ watch(() => chat.items.length, scrollBottom)
|
||||
<!-- mensaje de texto -->
|
||||
<div :class="m.owner==='yo' ? 'flex justify-end' : 'flex justify-start'" v-if="m.type==='text'">
|
||||
<div
|
||||
class="user-message max-w-lg rounded-lg px-4 py-2 shadow break-words"
|
||||
:class="m.owner==='yo' ? 'is-user text-white' : 'bg-white text-gray-900'">
|
||||
class="max-w-lg px-4 py-2 shadow break-words rounded-lg"
|
||||
: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 }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,9 +66,12 @@ watch(() => chat.items.length, scrollBottom)
|
||||
@keydown="handleKey"
|
||||
rows="1"
|
||||
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>
|
||||
</form>
|
||||
@@ -75,39 +79,25 @@ watch(() => chat.items.length, scrollBottom)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* :root definitions for --accent-color-chat-* removed, will use global definitions from style.css */
|
||||
|
||||
.user-message.is-user {
|
||||
background-color: var(--accent-color-chat);
|
||||
/* Default accent color for chat if not provided by CSS variable */
|
||||
:root {
|
||||
--accent-color-chat-fallback: #0D9488; /* Teal-700 as a fallback */
|
||||
--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-track { background: transparent; }
|
||||
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(var(--accent-color-chat-rgb),.35); border-radius: 4px; }
|
||||
.custom-scroll:hover::-webkit-scrollbar-thumb { background-color: rgba(var(--accent-color-chat-rgb),.7); }
|
||||
.custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(var(--accent-color-chat-rgb),.6) transparent; }
|
||||
.custom-scroll::-webkit-scrollbar-thumb {
|
||||
background-color: var(--accent-color-chat-alpha-35, var(--accent-color-chat-alpha-35-fallback));
|
||||
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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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">
|
||||
<img
|
||||
:src="employee.avatar_url || 'https://via.placeholder.com/150'"
|
||||
@@ -39,67 +39,56 @@
|
||||
@mouseleave="buttonHover($event, false)"
|
||||
:class="`focus:ring-[var(--accent-color-empleados)]`"
|
||||
>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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useEmpleadosStore } from '@/stores/useEmpleados.js'; // Adjust path as needed
|
||||
<script setup>
|
||||
import { useUi } from '../../stores/useUi.js';
|
||||
import { useEmpleadosStore } from '../../stores/useEmpleados'; // Ensure correct path
|
||||
|
||||
interface Employee {
|
||||
id: string | number
|
||||
name: string
|
||||
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 ui = useUi();
|
||||
const emit = defineEmits(['edit']);
|
||||
const empleadosStore = useEmpleadosStore();
|
||||
|
||||
const props = defineProps({
|
||||
employee: {
|
||||
type: Object as PropType<Employee>,
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const empleadosStore = useEmpleadosStore();
|
||||
});
|
||||
|
||||
const handleEdit = () => {
|
||||
router.push(`/empleados/${props.employee.id}`)
|
||||
}
|
||||
emit('edit', props.employee.id);
|
||||
};
|
||||
|
||||
const confirmDeleteEmployee = () => {
|
||||
if (confirm(`¿Está seguro de que desea eliminar al empleado "${props.employee.name}" (ID: ${props.employee.id})? Esta acción no se puede deshacer.`)) {
|
||||
deleteEmployee();
|
||||
const confirmDeleteEmpleado = () => {
|
||||
if (confirm(`¿Está seguro de que desea eliminar al empleado "${props.employee.name}" (ID: ${props.employee.id})?`)) {
|
||||
deleteEmpleado();
|
||||
}
|
||||
};
|
||||
|
||||
const deleteEmployee = async () => {
|
||||
const deleteEmpleado = async () => {
|
||||
try {
|
||||
await empleadosStore.deleteEmpleado(props.employee.id);
|
||||
// Optionally, you might want to emit an event or show a success notification
|
||||
// For example: emit('deleted', props.employee.id);
|
||||
// Or use a toast notification system if you have one integrated.
|
||||
// Optionally, show a success notification or emit an event if needed,
|
||||
// though typically the list will update reactively from the store.
|
||||
} catch (error) {
|
||||
console.error('Error deleting employee:', error);
|
||||
// It's good practice to inform the user.
|
||||
// Replace alert with a more sophisticated notification if available (e.g., toast).
|
||||
alert(`Ocurrió un error al eliminar el empleado: ${error.message || 'Error desconocido'}`);
|
||||
alert('Ocurrió un error al eliminar el empleado.');
|
||||
}
|
||||
};
|
||||
|
||||
const buttonHover = (event: MouseEvent, isHovering: boolean) => {
|
||||
const target = event.target as HTMLElement;
|
||||
const buttonHover = (event, isHovering) => {
|
||||
const target = event.target;
|
||||
if (isHovering) {
|
||||
target.style.filter = 'brightness(90%)'; // Darken slightly
|
||||
target.style.filter = 'brightness(90%)';
|
||||
} else {
|
||||
target.style.filter = 'brightness(100%)'; // Back to normal
|
||||
target.style.filter = 'brightness(100%)';
|
||||
}
|
||||
};
|
||||
</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">
|
||||
<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 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">
|
||||
<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 @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="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>
|
||||
@@ -74,58 +74,38 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, defineEmits } from 'vue'
|
||||
// No longer using useRouter directly in this component for navigation
|
||||
// import { useRouter } from 'vue-router'
|
||||
<script setup>
|
||||
import { useUi } from '../../stores/useUi.js';
|
||||
import { useEmpleadosStore } from '../../stores/useEmpleados.js'; // For delete functionality
|
||||
import { useEmpleadosStore } from '../../stores/useEmpleados';
|
||||
|
||||
const ui = useUi();
|
||||
const emit = defineEmits(['edit']);
|
||||
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({
|
||||
employees: {
|
||||
type: Array as PropType<Employee[]>,
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['edit']);
|
||||
|
||||
const handleEdit = (employeeId: string | number) => {
|
||||
emit('edit', employeeId); // Emit event for parent to handle navigation
|
||||
const handleEdit = (employeeId) => {
|
||||
emit('edit', employeeId);
|
||||
};
|
||||
|
||||
// handleViewDetails function is removed
|
||||
|
||||
const confirmDeleteEmployee = (employee: Employee) => {
|
||||
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 confirmDeleteEmpleado = (employee) => {
|
||||
if (confirm(`¿Está seguro de que desea eliminar al empleado "${employee.name}" (ID: ${employee.id})?`)) {
|
||||
deleteEmpleadoInternal(employee.id);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteEmployeeInternal = async (employeeId: string | number) => {
|
||||
const deleteEmpleadoInternal = async (id) => {
|
||||
try {
|
||||
await empleadosStore.deleteEmpleado(employeeId);
|
||||
// Optionally: emit success or use a notification system
|
||||
await empleadosStore.deleteEmpleado(id);
|
||||
} catch (error) {
|
||||
console.error(`Error deleting employee with id ${employeeId}:`, error);
|
||||
alert(`Ocurrió un error al eliminar el empleado: ${error.message || 'Error desconocido'}`);
|
||||
// Optionally: emit error or use a notification system
|
||||
console.error(`Error deleting employee with id ${id}:`, error);
|
||||
// Optional: Show error notification
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -137,5 +117,11 @@ const deleteEmployeeInternal = async (employeeId: string | number) => {
|
||||
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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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">
|
||||
<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)]">
|
||||
@@ -33,6 +33,9 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { usePlanillasStore } from '../../stores/usePlanillas';
|
||||
import { useUi } from '../../stores/useUi.js';
|
||||
|
||||
const ui = useUi();
|
||||
|
||||
const props = defineProps({
|
||||
planilla: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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">
|
||||
<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)]">
|
||||
@@ -36,6 +36,9 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useTareasStore } from '../../stores/useTareas';
|
||||
import { useUi } from '../../stores/useUi.js';
|
||||
|
||||
const ui = useUi();
|
||||
|
||||
const props = defineProps({
|
||||
tarea: {
|
||||
|
||||
Reference in New Issue
Block a user