Merge pull request #17 from josedario87/feat/standardize-ui-cards
Feat/standardize UI cards
This commit is contained in:
117
ui/README.md
117
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:** `<div class="card ...">`
|
||||
- This is the main container for the card.
|
||||
- Styling is primarily applied using Tailwind CSS utility classes. Common classes include `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`.
|
||||
- **Card Header:** `<div class="card-header ...">`
|
||||
- Typically contains the title or ID of the item and a status badge.
|
||||
- Styled with Tailwind: `flex justify-between items-center mb-3 md:mb-4 pb-2 md:pb-3 border-b border-gray-100`.
|
||||
- The title element (e.g., `<h4>`) uses module-specific accent colors.
|
||||
- **Card Body:** `<div class="card-body ...">`
|
||||
- Displays the main content fields of the item.
|
||||
- Styled with Tailwind: `text-sm text-gray-700 space-y-2`.
|
||||
- **Card Actions:** `<div class="card-actions ...">`
|
||||
- Contains action buttons like "Editar" and "Eliminar".
|
||||
- Styled with Tailwind: `mt-auto pt-3 md:pt-4 flex justify-end space-x-2 md:space-x-3`.
|
||||
|
||||
**Key Features & Standards:**
|
||||
### Styling with Tailwind CSS
|
||||
|
||||
* **Styling:** Tables are styled using **Tailwind CSS**. This utility-first CSS framework allows for flexible and consistent styling.
|
||||
* **Structure:**
|
||||
* **Container:** Tables are wrapped in a `div` with classes `p-4 sm:p-6 bg-white shadow-md rounded-lg overflow-x-auto` for padding, background, shadow, rounded corners, and horizontal scrolling on smaller screens.
|
||||
* **Table:** The `<table>` element uses `min-w-full divide-y divide-gray-200`.
|
||||
* **Header (`<thead>`):** Styled with `bg-gray-50`. Header cells (`<th>`) use `px-4 py-3 sm:px-6 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider`.
|
||||
* **Body (`<tbody>`):** Uses `bg-white divide-y divide-gray-200`. Data rows (`<tr>`) have a hover effect (`hover:bg-gray-100`). Data cells (`<td>`) 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 (`<template>` 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
|
||||
<h4 :style="{ color: 'var(--accent-color-empleados)' }">Employee Name</h4>
|
||||
|
||||
<button :style="{ backgroundColor: 'var(--accent-color-empleados)' }">Edit Employee</button>
|
||||
```
|
||||
Alternatively, Tailwind's arbitrary value support can be used with CSS variables:
|
||||
```html
|
||||
<h4 class="text-[var(--accent-color-empleados)]">Employee Name</h4>
|
||||
<button class="bg-[var(--accent-color-empleados)] hover:bg-[var(--accent-color-empleados)]/90 ...">Edit</button>
|
||||
```
|
||||
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
|
||||
<template>
|
||||
<cardAsistencia :asistencia="asistenciaData" @edit="handleEditAsistencia" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// ...
|
||||
const asistenciaData = { id: 1, /* ... other properties */ };
|
||||
const handleEditAsistencia = (asistenciaId) => {
|
||||
// Navigate to edit page or open modal
|
||||
};
|
||||
</script>
|
||||
```
|
||||
The specific prop name matches the module (e.g., `asistencia` for `cardAsistencia`, `employee` for `cardEmpleado`, `planilla` for `cardPlanilla`, `tarea` for `cardTarea`).
|
||||
|
||||
This standardization aims to improve code maintainability, reduce redundancy, and provide a consistent and predictable user interface.
|
||||
|
||||
6
ui/postcss.config.js
Normal file
6
ui/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
// Tailwind CSS is handled by the @tailwindcss/vite plugin in vite.config.js
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -1,24 +1,32 @@
|
||||
<template>
|
||||
<div class="asistencia-card">
|
||||
<div class="card-header">
|
||||
<h4>Asistencia ID: {{ asistencia.id }}</h4>
|
||||
<span :class="`estado-asistencia estado-${asistencia.estado?.toLowerCase().replace(/\s+/g, '-')}`">
|
||||
<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 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)]">
|
||||
{{ asistencia.estado || 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Empleado ID:</strong> {{ asistencia.empleado_id }}</p>
|
||||
<p><strong>Entrada:</strong> {{ formatDateTime(asistencia.entrada) }}</p>
|
||||
<p><strong>Salida:</strong> {{ asistencia.salida ? formatDateTime(asistencia.salida) : 'No registrada' }}</p>
|
||||
<p v-if="asistencia.observacion" class="observacion">
|
||||
<strong>Observación:</strong> {{ asistencia.observacion }}
|
||||
<div class="text-sm text-gray-700 space-y-2">
|
||||
<p><strong class="font-medium text-gray-900">Empleado ID:</strong> {{ asistencia.empleado_id }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Entrada:</strong> {{ formatDateTime(asistencia.entrada) }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Salida:</strong> {{ asistencia.salida ? formatDateTime(asistencia.salida) : 'No registrada' }}</p>
|
||||
<p v-if="asistencia.observacion" class="italic text-gray-600 bg-gray-50 p-2 border-l-3 rounded" :style="{ borderColor: 'var(--accent-color-asistencias)' }">
|
||||
<strong class="font-medium text-gray-900">Observación:</strong> {{ asistencia.observacion }}
|
||||
</p>
|
||||
<!-- Historial might be too complex for a card, but can be added if needed -->
|
||||
<!-- <p v-if="asistencia.historial"><strong>Historial:</strong> {{ asistencia.historial }}</p> -->
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button @click="editAsistencia" class="action-button edit-button">Editar</button>
|
||||
<button @click="confirmDeleteAsistencia" class="action-button delete-button">Eliminar</button>
|
||||
<div class="mt-auto pt-3 md:pt-4 flex justify-end space-x-2 md:space-x-3">
|
||||
<button
|
||||
@click="editAsistencia"
|
||||
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 text-white"
|
||||
:style="{ backgroundColor: 'var(--accent-color-asistencias)', borderColor: 'var(--accent-color-asistencias)' }"
|
||||
@mouseover="buttonHover($event, true, 'var(--accent-color-asistencias)')"
|
||||
@mouseleave="buttonHover($event, false, 'var(--accent-color-asistencias)')"
|
||||
:class="`focus:ring-[var(--accent-color-asistencias)]`"
|
||||
>Editar</button>
|
||||
<button
|
||||
@click="confirmDeleteAsistencia"
|
||||
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>
|
||||
@@ -26,6 +34,7 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useAsistenciasStore } from '../../stores/useAsistencias';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
asistencia: {
|
||||
@@ -41,14 +50,7 @@ const asistenciasStore = useAsistenciasStore();
|
||||
const formatDateTime = (dateTimeString) => {
|
||||
if (!dateTimeString) return 'N/A';
|
||||
const date = new Date(dateTimeString);
|
||||
return date.toLocaleString('es-HN', { timeZone: 'America/Tegucigalpa', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); // Spanish locale
|
||||
// year: 'numeric', // Already present in the new options
|
||||
// month: '2-digit', // Using 2-digit for month for brevity in card // Already present in the new options
|
||||
// day: '2-digit', // Using 2-digit for day // Already present in the new options
|
||||
// hour: '2-digit', // Already present in the new options
|
||||
// minute: '2-digit', // Already present in the new options
|
||||
// second: '2-digit', // Optional: include seconds
|
||||
// }); // Removed this line as options are now in a single line
|
||||
return date.toLocaleString('es-HN', { timeZone: 'America/Tegucigalpa', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
|
||||
const editAsistencia = () => {
|
||||
@@ -56,7 +58,7 @@ const editAsistencia = () => {
|
||||
};
|
||||
|
||||
const confirmDeleteAsistencia = () => {
|
||||
if (confirm(`¿Está seguro de que desea eliminar esta asistencia (ID: ${props.asistencia.id})?`)) {
|
||||
if (confirm(`¿Está seguro de que desea eliminar la asistencia "${props.asistencia.id}" (ID: ${props.asistencia.id})?`)) {
|
||||
deleteAsistenciaInternal();
|
||||
}
|
||||
};
|
||||
@@ -64,126 +66,48 @@ const confirmDeleteAsistencia = () => {
|
||||
const deleteAsistenciaInternal = async () => {
|
||||
try {
|
||||
await asistenciasStore.deleteAsistencia(props.asistencia.id);
|
||||
// Optionally, emit 'deleted' or show notification
|
||||
} catch (error) {
|
||||
console.error('Error deleting asistencia:', error);
|
||||
alert('Ocurrió un error al eliminar la asistencia.');
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusClass = (status) => {
|
||||
if (!status) return 'bg-gray-400'; // Default for N/A
|
||||
const statusNormalized = status.toLowerCase().replace(/\s+/g, '-');
|
||||
switch (statusNormalized) {
|
||||
case 'pendiente': return 'bg-yellow-500 text-gray-800';
|
||||
case 'presente':
|
||||
case 'confirmada': return 'bg-green-500';
|
||||
case 'ausente': return 'bg-red-500';
|
||||
case 'justificada': return 'bg-blue-500';
|
||||
case 'cancelada':
|
||||
case 'anulada': return 'bg-gray-500';
|
||||
default: return 'bg-gray-400';
|
||||
}
|
||||
};
|
||||
|
||||
const buttonHover = (event, isHovering, colorVar) => {
|
||||
if (isHovering) {
|
||||
event.target.style.filter = 'brightness(90%)';
|
||||
} else {
|
||||
event.target.style.filter = 'brightness(100%)';
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.asistencia-card {
|
||||
border: 1px solid #e0e0e0; /* Lighter border for a softer look */
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05); /* Softer shadow */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: box-shadow 0.3s ease-in-out;
|
||||
}
|
||||
.asistencia-card:hover {
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1); /* Enhanced shadow on hover */
|
||||
}
|
||||
/* Minimal scoped styles, mainly for complex status colors if not handled by Tailwind directly */
|
||||
/* Or for :style bindings if preferred for dynamic properties like accent colors */
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px; /* Increased padding */
|
||||
border-bottom: 1px solid #f0f0f0; /* Even lighter border */
|
||||
/* The border-l-3 for observation uses a utility-like class,
|
||||
but Tailwind doesn't have border-left-width: 3px by default.
|
||||
You could add this to your tailwind.config.js if used often:
|
||||
theme: { extend: { borderWidth: { '3': '3px' } } }
|
||||
Then use class `border-l-3`. For now, inline style is fine.
|
||||
*/
|
||||
.border-l-3 {
|
||||
border-left-width: 3px;
|
||||
}
|
||||
|
||||
.card-header h4 { /* Style for the new Asistencia ID header */
|
||||
font-weight: bold;
|
||||
color: var(--accent-color-asistencias); /* Accent color */
|
||||
font-size: 1.15em; /* Slightly larger for main header element */
|
||||
margin: 0; /* Remove default margin */
|
||||
}
|
||||
|
||||
.empleado-id { /* This class is no longer used in the header, but could be reused elsewhere if needed */
|
||||
font-weight: bold;
|
||||
color: var(--accent-color-asistencias); /* Accent color */
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.estado-asistencia {
|
||||
padding: 5px 10px; /* Slightly more padding */
|
||||
border-radius: 15px; /* Pill shape */
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
text-transform: uppercase; /* Uppercase status */
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.card-body p {
|
||||
margin: 8px 0;
|
||||
color: #555;
|
||||
font-size: 0.95em;
|
||||
line-height: 1.6; /* Improved line spacing */
|
||||
}
|
||||
.card-body p strong {
|
||||
color: #333;
|
||||
}
|
||||
.observacion {
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
background-color: #f8f9fa; /* Very light grey background */
|
||||
padding: 10px; /* More padding */
|
||||
border-left: 3px solid var(--accent-color-asistencias); /* Accent color for border */
|
||||
border-radius: 4px;
|
||||
margin-top:10px;
|
||||
font-size: 0.9em; /* Slightly smaller for observation */
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
margin-top: auto;
|
||||
padding-top: 16px; /* More space before actions */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500; /* Slightly bolder text on buttons */
|
||||
transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.action-button:hover{
|
||||
transform: translateY(-2px); /* More pronounced lift */
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background-color: var(--accent-color-asistencias);
|
||||
color: white;
|
||||
}
|
||||
.edit-button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: var(--warning-color); /* Use warning color for delete */
|
||||
color: white;
|
||||
}
|
||||
.delete-button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
/* Estado specific styling */
|
||||
.estado-pendiente { background-color: #ffc107; color: #212529;} /* Amber, dark text */
|
||||
.estado-presente,
|
||||
.estado-confirmada { background-color: #28a745; color: white;} /* Green */
|
||||
.estado-ausente { background-color: #dc3545; color: white;} /* Red */
|
||||
.estado-justificada { background-color: #17a2b8; color: white;} /* Info Blue */
|
||||
.estado-cancelada,
|
||||
.estado-anulada { background-color: #6c757d; color: white;} /* Gray */
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,45 @@
|
||||
<template>
|
||||
<div class="bg-white shadow-lg rounded-lg p-6 m-4 w-full max-w-sm hover:shadow-xl transition-shadow duration-300 ease-in-out">
|
||||
<div class="flex items-center mb-4">
|
||||
<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 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'"
|
||||
alt="Avatar del empleado"
|
||||
class="w-20 h-20 rounded-full mr-6 border-2 object-cover employee-avatar-border"
|
||||
class="w-16 h-16 rounded-full mr-4 border-2 object-cover"
|
||||
:style="{ borderColor: 'var(--accent-color-empleados)' }"
|
||||
/>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-800">{{ employee.name }}</h2>
|
||||
<p class="text-gray-600 text-sm">ID: {{ employee.id }}</p>
|
||||
<h2 class="text-lg md:text-xl font-semibold" :style="{ color: 'var(--accent-color-empleados)' }">{{ employee.name }}</h2>
|
||||
<p class="text-xs text-gray-500">ID: {{ employee.id }}</p>
|
||||
</div>
|
||||
</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 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 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 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 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 class="text-sm text-gray-700 space-y-2">
|
||||
<p v-if="employee.telefono" class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 shrink-0" :style="{ color: 'var(--accent-color-empleados)' }" 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>
|
||||
<strong class="font-medium text-gray-900">Teléfono:</strong> <span>{{ employee.telefono }}</span>
|
||||
</p>
|
||||
<p v-if="employee.ubicacion" class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 shrink-0" :style="{ color: 'var(--accent-color-empleados)' }" 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>
|
||||
<strong class="font-medium text-gray-900">Ubicación:</strong> <span>{{ employee.ubicacion }}</span>
|
||||
</p>
|
||||
<p v-if="employee.cedula" class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 shrink-0" :style="{ color: 'var(--accent-color-empleados)' }" 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>
|
||||
<strong class="font-medium text-gray-900">Cédula:</strong> <span>{{ employee.cedula }}</span>
|
||||
</p>
|
||||
<p v-if="employee.grupo_estudio" class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 shrink-0" :style="{ color: 'var(--accent-color-empleados)' }" 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>
|
||||
<strong class="font-medium text-gray-900">Grupo Estudio:</strong> <span>{{ employee.grupo_estudio }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end space-x-3">
|
||||
<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">
|
||||
Ver Detalles
|
||||
</button>
|
||||
<div class="mt-auto pt-3 md:pt-4 flex justify-end space-x-2 md:space-x-3">
|
||||
<button
|
||||
@click="handleEdit"
|
||||
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 text-white"
|
||||
:style="{ backgroundColor: 'var(--accent-color-empleados)', borderColor: 'var(--accent-color-empleados)' }"
|
||||
@mouseover="buttonHover($event, true)"
|
||||
@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 -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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%)';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Scoped styles for the card ensure they don't leak */
|
||||
.max-w-sm {
|
||||
max-width: 24rem; /* Consistent with Tailwind's sm breakpoint for max-width */
|
||||
/* Minimal scoped styles */
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Adding some subtle enhancements for visual appeal */
|
||||
.text-2xl {
|
||||
line-height: 1.2; /* Adjust line height for the title for better readability */
|
||||
}
|
||||
|
||||
.flex svg {
|
||||
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, filter 0.2s ease-in-out;
|
||||
}
|
||||
button:hover {
|
||||
transform: translateY(-1px); /* Slight lift on hover */
|
||||
}
|
||||
button:active {
|
||||
transform: translateY(0px); /* Reset lift on click */
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
object-fit: cover; /* Ensures the avatar image covers the area without distortion */
|
||||
object-fit: cover; /* Ensures the avatar image covers the area without distortion */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
<template>
|
||||
<div class="planilla-card">
|
||||
<h4>Planilla ID: {{ planilla.id }}</h4>
|
||||
<p><strong>Título:</strong> {{ planilla.titulo }}</p>
|
||||
<p><strong>Empleado ID:</strong> {{ planilla.empleado_id }}</p>
|
||||
<p><strong>Desde:</strong> {{ formatDate(planilla.fecha_desde) }}</p>
|
||||
<p><strong>Hasta:</strong> {{ formatDate(planilla.fecha_hasta) }}</p>
|
||||
<p><strong>Total:</strong> {{ formatCurrency(planilla.total) }}</p>
|
||||
<p><strong>Estado:</strong> <span :class="`estado-${planilla.estado?.toLowerCase()}`">{{ planilla.estado }}</span></p>
|
||||
<div class="actions">
|
||||
<button @click="editPlanilla">Editar</button>
|
||||
<button @click="confirmDeletePlanilla">Eliminar</button>
|
||||
<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 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)]">
|
||||
{{ planilla.estado || 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-700 space-y-2">
|
||||
<p><strong class="font-medium text-gray-900">Título:</strong> {{ planilla.titulo }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Empleado ID:</strong> {{ planilla.empleado_id }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Desde:</strong> {{ formatDate(planilla.fecha_desde) }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Hasta:</strong> {{ formatDate(planilla.fecha_hasta) }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Total:</strong> {{ formatCurrency(planilla.total) }}</p>
|
||||
</div>
|
||||
<div class="mt-auto pt-3 md:pt-4 flex justify-end space-x-2 md:space-x-3">
|
||||
<button
|
||||
@click="editPlanilla"
|
||||
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 text-white"
|
||||
:style="{ backgroundColor: 'var(--accent-color-planillas)', borderColor: 'var(--accent-color-planillas)' }"
|
||||
@mouseover="buttonHover($event, true)"
|
||||
@mouseleave="buttonHover($event, false)"
|
||||
:class="`focus:ring-[var(--accent-color-planillas)]`"
|
||||
>Editar</button>
|
||||
<button
|
||||
@click="confirmDeletePlanilla"
|
||||
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>
|
||||
@@ -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%)';
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.planilla-card {
|
||||
border: 1px solid #ccc;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.planilla-card h4 { /* Updated selector from h3 to h4 */
|
||||
margin-top: 0;
|
||||
color: var(--accent-color-planillas); /* Accent color for title */
|
||||
font-size: 1.2em; /* Adjust size as needed, h4 is typically smaller than h3 */
|
||||
}
|
||||
|
||||
.planilla-card h3 { /* Keep if h3 is used elsewhere, or remove if h4 is the new standard */
|
||||
margin-top: 0;
|
||||
color: var(--accent-color-planillas); /* Accent color for title */
|
||||
}
|
||||
|
||||
.planilla-card p {
|
||||
margin: 8px 0;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.planilla-card .actions {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.planilla-card button {
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.planilla-card button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.actions button:first-child { /* Edit button */
|
||||
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: 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 {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
.estado-pendiente {
|
||||
color: orange;
|
||||
font-weight: bold;
|
||||
}
|
||||
.estado-anulado {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
/* All layout and most styling is now handled by Tailwind utility classes. */
|
||||
/* Scoped styles can be used for very specific cases not covered by Tailwind, if any. */
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
<template>
|
||||
<div class="tarea-card">
|
||||
<h4>Tarea ID: {{ tarea.id }}</h4>
|
||||
<p><strong>Título:</strong> {{ tarea.titulo }}</p>
|
||||
<p><strong>Empleado ID:</strong> {{ tarea.empleado_id }}</p>
|
||||
<p><strong>Fecha:</strong> {{ formatDate(tarea.fecha) }}</p>
|
||||
<p><strong>Estado:</strong> <span :class="`estado-${tarea.estado?.toLowerCase().replace(/\s+/g, '-')}`">{{ tarea.estado }}</span></p>
|
||||
<p><strong>Tipo:</strong> {{ tarea.tipo || 'N/A' }}</p>
|
||||
<p v-if="tarea.precio != null"><strong>Precio:</strong> {{ formatCurrency(tarea.precio) }}</p>
|
||||
<p v-if="tarea.observacion"><strong>Observación:</strong> {{ tarea.observacion }}</p>
|
||||
|
||||
<div class="actions">
|
||||
<button @click="editTarea" class="action-button edit-button">Editar</button>
|
||||
<button @click="confirmDeleteTarea" class="action-button delete-button">Eliminar</button>
|
||||
<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 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)]">
|
||||
{{ tarea.estado || 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-700 space-y-2">
|
||||
<p><strong class="font-medium text-gray-900">Título:</strong> {{ tarea.titulo }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Empleado ID:</strong> {{ tarea.empleado_id }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Fecha:</strong> {{ formatDate(tarea.fecha) }}</p>
|
||||
<p><strong class="font-medium text-gray-900">Tipo:</strong> {{ tarea.tipo || 'N/A' }}</p>
|
||||
<p v-if="tarea.precio != null"><strong class="font-medium text-gray-900">Precio:</strong> {{ formatCurrency(tarea.precio) }}</p>
|
||||
<p v-if="tarea.observacion" class="italic text-gray-600 bg-gray-50 p-2 border-l-3 rounded" :style="{ borderColor: 'var(--accent-color-tareas)' }">
|
||||
<strong class="font-medium text-gray-900">Observación:</strong> {{ tarea.observacion }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-auto pt-3 md:pt-4 flex justify-end space-x-2 md:space-x-3">
|
||||
<button
|
||||
@click="editTarea"
|
||||
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 text-white"
|
||||
:style="{ backgroundColor: 'var(--accent-color-tareas)', borderColor: 'var(--accent-color-tareas)' }"
|
||||
@mouseover="buttonHover($event, true)"
|
||||
@mouseleave="buttonHover($event, false)"
|
||||
:class="`focus:ring-[var(--accent-color-tareas)]`"
|
||||
>Editar</button>
|
||||
<button
|
||||
@click="confirmDeleteTarea"
|
||||
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>
|
||||
@@ -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%)';
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tarea-card {
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
transition: box-shadow 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.tarea-card:hover {
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.tarea-card h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
color: var(--accent-color-tareas); /* Accent color for title */
|
||||
font-size: 1.15em; /* Slightly smaller than a typical h3 */
|
||||
}
|
||||
|
||||
.tarea-card p {
|
||||
margin: 6px 0;
|
||||
color: #555;
|
||||
font-size: 0.95em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tarea-card p strong {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: background-color 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background-color: var(--accent-color-tareas);
|
||||
color: white;
|
||||
}
|
||||
.edit-button:hover {
|
||||
filter: brightness(0.9); /* Standard hover effect */
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: #dc3545; /* Red */
|
||||
color: white;
|
||||
}
|
||||
.delete-button:hover {
|
||||
background-color: #c82333; /* Darker red */
|
||||
}
|
||||
|
||||
/* Status styling: Added .replace(/\s+/g, '-') for multi-word statuses */
|
||||
.estado-pendiente {
|
||||
color: #ff9800; /* Orange */
|
||||
font-weight: bold;
|
||||
}
|
||||
.estado-realizada,
|
||||
.estado-completada, /* Common synonyms for 'done' */
|
||||
.estado-hecho {
|
||||
color: #4caf50; /* Green */
|
||||
font-weight: bold;
|
||||
}
|
||||
.estado-en-progreso { /* Example for 'in progress' */
|
||||
color: #2196f3; /* Blue */
|
||||
font-weight: bold;
|
||||
}
|
||||
.estado-anulada,
|
||||
.estado-cancelada {
|
||||
color: #f44336; /* Red */
|
||||
font-weight: bold;
|
||||
/* text-decoration: line-through; */ /* Optional */
|
||||
/* Minimal scoped styles, mainly for complex status colors if not handled by Tailwind directly */
|
||||
/* Or for :style bindings if preferred for dynamic properties like accent colors */
|
||||
.border-l-3 {
|
||||
border-left-width: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
11
ui/tailwind.config.js
Normal file
11
ui/tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Reference in New Issue
Block a user