diff --git a/ui/README.md b/ui/README.md index d608b10..02f6020 100644 --- a/ui/README.md +++ b/ui/README.md @@ -8,45 +8,98 @@ This template should help get you started developing with Vue 3 in Vite. The tem Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support). -## UI Structure -The UI is organized into several modules, each handling a specific domain of the application. Common UI elements, like tables, have been standardized for a consistent look and feel. +## Standardized Card Components -## UI Modules +This section documents the standardized card components used throughout the UI for modules like Asistencias, Empleados, Planillas, and Tareas. These components have been refactored for a consistent structure, styling approach, and user experience. -The application currently includes the following primary UI modules: +### Overview -* **Asistencias (Attendance):** Manages attendance records. -* **Empleados (Employees):** Manages employee information. -* **Planillas (Payrolls/Sheets):** Manages payrolls or general data sheets. -* **Tareas (Tasks):** Manages tasks and assignments. +Card components provide a summarized display of an item (e.g., an employee, a task) and offer quick actions. The standardization ensures that users encounter a familiar layout and interaction pattern across different modules. -Each module typically provides views for listing, creating, and editing items within its domain. +### Common HTML Structure -## Standardized Table Components +All standardized card components share a common HTML structure: -To ensure consistency across the application, table components used for displaying lists of data (e.g., `tablaAsistencias.vue`, `tablaEmpleados.vue`) have been standardized. +- **Root Element:** `
| `) use `px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider`. - * **Body (` | `):** Uses `bg-white divide-y divide-gray-200`. Data rows (`
|---|
`) use `px-4 py-3 sm:px-6 sm:py-4 whitespace-nowrap text-sm text-gray-700`.
- * **No Data Message:** A standardized message "No hay [items] para mostrar." is displayed if the table is empty, styled with `px-6 py-10 text-center text-gray-500 text-lg`.
-* **Action Buttons:**
- * Common actions like "Edit" and "Delete" are standardized.
- * Buttons use SVG icons (Heroicons outline style) and consistent Tailwind CSS classes for styling and hover/focus states (e.g., `text-blue-600 hover:text-blue-800` for edit, `text-red-600 hover:text-red-800` for delete).
- * Actions are typically grouped in a `div` with `flex items-center space-x-2`.
-* **Status Indicators:**
- * Status fields (e.g., "Estado") are visually represented using badges with consistent styling (`px-2.5 py-0.5 rounded-full text-xs font-semibold` along with color classes like `bg-green-100 text-green-800`).
-* **Utility Functions:**
- * Common data formatting (dates, currency, text truncation) and status styling logic is centralized in `ui/src/utils/formatters.js`. Components import and use these functions to maintain consistency and reduce code duplication.
-* **Theming:**
- * While the overall table structure is standardized, module-specific accent colors (defined as CSS variables like `var(--accent-color-module)`) are used in the parent views (e.g., `AsistenciasIndex.vue`) for elements like page titles and "Create New" buttons. This allows some thematic distinction per module.
-* **Creating New Tables:**
- * When adding new tables to the UI, they should adhere to these established standards to maintain a cohesive user experience. Refer to existing components like `tablaAsistencias.vue` or `tablaPlanillas.vue` as a template.
+The visual appearance of the cards (layout, spacing, typography, borders, shadows) is managed by [Tailwind CSS](https://tailwindcss.com/).
+- Utility classes are applied directly in the component templates (`` section of `.vue` files).
+- The Tailwind configuration can be found in `ui/tailwind.config.js`.
+- Core Tailwind directives (`@tailwind base; @tailwind components; @tailwind utilities;`) are included in `ui/src/style.css`.
+
+### Theming with CSS Variables
+
+While Tailwind handles most styling, module-specific theming (especially accent colors) and some global style properties are controlled by CSS variables. These variables are defined in the `:root` scope in `ui/src/style.css`.
+
+**Key CSS Variables:**
+
+- **Module Accent Colors:**
+ - `--accent-color-asistencias`: Accent color for the Asistencias module (e.g., for headers, buttons).
+ - `--accent-color-empleados`: Accent color for the Empleados module.
+ - `--accent-color-planillas`: Accent color for the Planillas module.
+ - `--accent-color-tareas`: Accent color for the Tareas module.
+- **Common Colors & Styles:**
+ - `--warning-color`: Used for delete buttons and other warning indicators (e.g., `#dc3545`).
+ - `--background-color`: Default background for the application.
+ - `--text-color`: Default text color.
+ - `--muted-text-color`: For less prominent text.
+ - `--border-color`: Default border color.
+ - `--card-shadow`: Default shadow for cards.
+ - `--card-hover-shadow`: Shadow for cards on hover.
+
+**Usage Example (in a Vue component):**
+
+```html
+Employee Name+ + +``` +Alternatively, Tailwind's arbitrary value support can be used with CSS variables: +```html +Employee Name+ +``` +The latter approach (Tailwind arbitrary values) is generally preferred for consistency with other Tailwind classes, but direct `:style` binding is also used, especially for properties not easily covered by Tailwind utilities or for dynamic hover effects managed in JavaScript. + +### Functionality + +- **Actions:** + - **Edit:** Navigates to the form view for editing the item (e.g., `/empleados/:id`). + - **Delete:** Initiates a delete process for the item. +- **Delete Confirmation:** A standardized JavaScript `confirm()` dialog is used before deleting an item, with a message format like: `¿Está seguro de que desea eliminar [tipo de item] "[nombre/ID del item]" (ID: [ID])?`. + +### Component Usage + +Each card component is designed to be used within its respective module's views. They typically expect a prop containing the item data and emit an `edit` event. + +**Example (`cardAsistencia.vue`):** +```vue + +
-
- Asistencia ID: {{ asistencia.id }}- +
+
+
- Asistencia ID: {{ asistencia.id }}+ {{ asistencia.estado || 'N/A' }}
- Empleado ID: {{ asistencia.empleado_id }} -Entrada: {{ formatDateTime(asistencia.entrada) }} -Salida: {{ asistencia.salida ? formatDateTime(asistencia.salida) : 'No registrada' }} -- Observación: {{ asistencia.observacion }} +
+
- Empleado ID: {{ asistencia.empleado_id }} +Entrada: {{ formatDateTime(asistencia.entrada) }} +Salida: {{ asistencia.salida ? formatDateTime(asistencia.salida) : 'No registrada' }} ++ Observación: {{ asistencia.observacion }} - -
-
-
+
@@ -26,6 +34,7 @@
diff --git a/ui/src/components/empleados/cardEmpleado.vue b/ui/src/components/empleados/cardEmpleado.vue
index d25fede..1abb0cf 100644
--- a/ui/src/components/empleados/cardEmpleado.vue
+++ b/ui/src/components/empleados/cardEmpleado.vue
@@ -1,41 +1,45 @@
-
+
+
-
+
+
-
{{ employee.name }}-ID: {{ employee.id }} +{{ employee.name }}+ID: {{ employee.id }}
-
-
- {{ employee.telefono }}
-
-
-
- {{ employee.ubicacion }}
-
-
-
- Cedula: {{ employee.cedula }}
-
-
-
- Grupo Estudio: {{ employee.grupo_estudio }}
-
+
+
- + + Teléfono: {{ employee.telefono }} + ++ + Ubicación: {{ employee.ubicacion }} + ++ + Cédula: {{ employee.cedula }} + ++ + Grupo Estudio: {{ employee.grupo_estudio }} +
-
-
+
@@ -44,18 +48,15 @@
import { PropType } from 'vue'
import { useRouter } from 'vue-router'
-// Define the structure of the employee object based on the Prisma schema
interface Employee {
- id: string | number // Changed from BigInt to string | number for easier handling in frontend
+ id: string | number
name: string
- cedula: number // Changed from BigInt
+ cedula: number
avatar_url?: string
telefono?: string
ubicacion: string
idciat?: string
grupo_estudio?: string
- // created_at and updated_at are usually not displayed directly in a summary card
- // empleado: boolean // This is implicit as it's an employee card
}
const props = defineProps({
@@ -68,65 +69,29 @@ const props = defineProps({
const router = useRouter()
const handleEdit = () => {
- // Ensure employee.id is available and correctly typed for URL
- router.push(`/empleados/edit/${props.employee.id}`)
+ // The router pushes to `/empleados/:id` as per current router config,
+ // which maps to `EmpleadoForm.vue`. This form serves for both editing and viewing details.
+ router.push(`/empleados/${props.employee.id}`)
}
-const handleViewDetails = () => {
- // This could navigate to a more detailed employee page if one exists
- // For now, it can also navigate to an edit page or a specific detail view
- // Depending on the application's routing structure, this might be the same as edit or a different view
- router.push(`/empleados/view/${props.employee.id}`) // Assuming a dedicated view route exists or will be created
-}
+// handleViewDetails method removed for consistency
+
+const buttonHover = (event: MouseEvent, isHovering: boolean) => {
+ const target = event.target as HTMLElement;
+ if (isHovering) {
+ target.style.filter = 'brightness(90%)';
+ } else {
+ target.style.filter = 'brightness(100%)';
+ }
+};
diff --git a/ui/src/components/planillas/cardPlanilla.vue b/ui/src/components/planillas/cardPlanilla.vue
index 40bcda9..92f7fe0 100644
--- a/ui/src/components/planillas/cardPlanilla.vue
+++ b/ui/src/components/planillas/cardPlanilla.vue
@@ -1,15 +1,31 @@
-
+
+
- Planilla ID: {{ planilla.id }}-Título: {{ planilla.titulo }} -Empleado ID: {{ planilla.empleado_id }} -Desde: {{ formatDate(planilla.fecha_desde) }} -Hasta: {{ formatDate(planilla.fecha_hasta) }} -Total: {{ formatCurrency(planilla.total) }} -Estado: {{ planilla.estado }} -
-
-
+
+
@@ -36,9 +52,7 @@ const formatDate = (dateString) => {
};
const formatCurrency = (value) => {
- if (value == null) return 'N/A'; // Handle null or undefined totals
- // Assuming the value is a number or can be converted to one.
- // Adjust 'es-PY' and currency 'PYG' (Paraguayan Guarani) as needed.
+ if (value == null) return 'N/A';
return Number(value).toLocaleString('es-PY', {
style: 'currency',
currency: 'PYG'
@@ -50,8 +64,7 @@ const editPlanilla = () => {
};
const confirmDeletePlanilla = () => {
- // In a real app, you'd use a confirmation dialog here
- if (confirm(`¿Está seguro de que desea eliminar la planilla "${props.planilla.titulo}"?`)) {
+ if (confirm(`¿Está seguro de que desea eliminar la planilla "${props.planilla.titulo}" (ID: ${props.planilla.id})?`)) {
deletePlanilla();
}
};
@@ -59,85 +72,35 @@ const confirmDeletePlanilla = () => {
const deletePlanilla = async () => {
try {
await planillasStore.deletePlanilla(props.planilla.id);
- // Optionally, emit an event or show a notification upon successful deletion
- // For example: emit('deleted', props.planilla.id);
} catch (error) {
console.error('Error deleting planilla:', error);
- // Handle error (e.g., show a notification to the user)
+ alert('Ocurrió un error al eliminar la planilla.');
}
};
+
+const getStatusClass = (status) => {
+ if (!status) return 'bg-gray-400';
+ const statusNormalized = status.toLowerCase().replace(/\s+/g, '-');
+ switch (statusNormalized) {
+ case 'pagado': return 'bg-green-500';
+ case 'pendiente': return 'bg-yellow-500 text-gray-800';
+ case 'anulado': return 'bg-red-500';
+ case 'borrador': return 'bg-gray-500';
+ default: return 'bg-gray-400';
+ }
+};
+
+const buttonHover = (event, isHovering) => {
+ if (isHovering) {
+ event.target.style.filter = 'brightness(90%)';
+ } else {
+ event.target.style.filter = 'brightness(100%)';
+ }
+};
+
diff --git a/ui/src/components/tareas/cardTarea.vue b/ui/src/components/tareas/cardTarea.vue
index bac3921..3e15c10 100644
--- a/ui/src/components/tareas/cardTarea.vue
+++ b/ui/src/components/tareas/cardTarea.vue
@@ -1,17 +1,34 @@
-
+
+ Planilla ID: {{ planilla.id }}+ + {{ planilla.estado || 'N/A' }} + +
+
+ Título: {{ planilla.titulo }} +Empleado ID: {{ planilla.empleado_id }} +Desde: {{ formatDate(planilla.fecha_desde) }} +Hasta: {{ formatDate(planilla.fecha_hasta) }} +Total: {{ formatCurrency(planilla.total) }} +
+
+
- Tarea ID: {{ tarea.id }}-Título: {{ tarea.titulo }} -Empleado ID: {{ tarea.empleado_id }} -Fecha: {{ formatDate(tarea.fecha) }} -Estado: {{ tarea.estado }} -Tipo: {{ tarea.tipo || 'N/A' }} -Precio: {{ formatCurrency(tarea.precio) }} -Observación: {{ tarea.observacion }} - -
-
-
+
+
@@ -39,7 +56,7 @@ const formatDate = (dateString) => {
const formatCurrency = (value) => {
if (value == null) return '';
- return Number(value).toLocaleString('es-PY', { // Paraguayan Guarani
+ return Number(value).toLocaleString('es-PY', {
style: 'currency',
currency: 'PYG',
});
@@ -50,7 +67,7 @@ const editTarea = () => {
};
const confirmDeleteTarea = () => {
- if (confirm(`¿Está seguro de que desea eliminar la tarea "${props.tarea.titulo}"?`)) {
+ if (confirm(`¿Está seguro de que desea eliminar la tarea "${props.tarea.titulo}" (ID: ${props.tarea.id})?`)) {
deleteTareaInternal();
}
};
@@ -58,99 +75,42 @@ const confirmDeleteTarea = () => {
const deleteTareaInternal = async () => {
try {
await tareasStore.deleteTarea(props.tarea.id);
- // Optionally, emit a 'deleted' event: emit('deleted', props.tarea.id);
- // Or show a success notification.
} catch (error) {
console.error('Error deleting tarea:', error);
alert('Ocurrió un error al eliminar la tarea. Por favor, intente de nuevo.');
- // Potentially show a more user-friendly notification.
}
};
+
+const getStatusClass = (status) => {
+ if (!status) return 'bg-gray-400';
+ const statusNormalized = status.toLowerCase().replace(/\s+/g, '-');
+ switch (statusNormalized) {
+ case 'pendiente': return 'bg-yellow-500 text-gray-800';
+ case 'realizada':
+ case 'completada':
+ case 'hecho': return 'bg-green-500';
+ case 'en-progreso': return 'bg-blue-500';
+ case 'anulada':
+ case 'cancelada': return 'bg-red-500';
+ case 'archivada': return 'bg-gray-500';
+ default: return 'bg-gray-400';
+ }
+};
+
+const buttonHover = (event, isHovering) => {
+ if (isHovering) {
+ event.target.style.filter = 'brightness(90%)';
+ } else {
+ event.target.style.filter = 'brightness(100%)';
+ }
+};
+
diff --git a/ui/src/style.css b/ui/src/style.css
index 3cdb482..fb4740a 100644
--- a/ui/src/style.css
+++ b/ui/src/style.css
@@ -4,28 +4,39 @@
@tailwind utilities;
:root {
- --primary-color: #1976D2;
+ /* Colores base */
+ --primary-color: #1976D2;
--secondary-color: #424242;
- --warning-color: #FFC107;
- --background-color: #FFFFFF;
+ --warning-color: #dc3545; /* rojo para acciones peligrosas */
+ --background-color:#FFFFFF;
+
+ /* Tipografía */
--font-family: 'Roboto', sans-serif;
--font-size: 16px;
- /* Add other variables as needed, e.g., text colors for themes */
- --text-color: #212121; /* Default text color for light theme */
- /* Module-specific accent colors - Default Fallbacks */
- --accent-color-empleados: #2196F3;
- --accent-color-tareas: #4CAF50;
- --accent-color-planillas: #FF9800;
- --accent-color-asistencias: #E91E63;
+ /* Texto y bordes */
+ --text-color: #333333;
+ --muted-text-color: #555555;
+ --border-color: #e0e0e0;
- /* NEW: Module-specific table container background colors (Light Theme) */
+ /* Sombras */
+ --card-shadow: 0 2px 5px rgba(0,0,0,0.05);
+ --card-hover-shadow: 0 4px 10px rgba(0,0,0,0.10);
+
+ /* Colores de módulo */
+ --accent-color-asistencias: #4CAF50;
+ --accent-color-empleados: #2196F3;
+ --accent-color-planillas: #FF9800;
+ --accent-color-tareas: #9C27B0;
+
+ /* Fondos de tabla (light) */
--table-container-bg-color-asistencias: #fdebee;
- --table-container-bg-color-empleados: #e3f2fd;
- --table-container-bg-color-planillas: #fff8e1;
- --table-container-bg-color-tareas: #e6f4ea;
+ --table-container-bg-color-empleados: #e3f2fd;
+ --table-container-bg-color-planillas: #fff8e1;
+ --table-container-bg-color-tareas: #e6f4ea;
}
+
html.theme-dark {
--primary-color: #2196F3; /* Example dark theme primary */
--secondary-color: #757575; /* Example dark theme secondary */
diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js
new file mode 100644
index 0000000..bd3eb14
--- /dev/null
+++ b/ui/tailwind.config.js
@@ -0,0 +1,11 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{vue,js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
+
+ Tarea ID: {{ tarea.id }}+ + {{ tarea.estado || 'N/A' }} + +
+
+ Título: {{ tarea.titulo }} +Empleado ID: {{ tarea.empleado_id }} +Fecha: {{ formatDate(tarea.fecha) }} +Tipo: {{ tarea.tipo || 'N/A' }} +Precio: {{ formatCurrency(tarea.precio) }} ++ Observación: {{ tarea.observacion }} + +
+
+
|