actualizada API empleados, conectando UI con API
Some checks failed
build-and-deploy / filter (push) Successful in 3s
Sync to GitHub / sync (push) Failing after 1s
build-and-deploy / build (push) Successful in 13s
build-and-deploy / deploy (push) Successful in 14s

This commit is contained in:
2025-05-30 10:51:21 -06:00
parent 30f85bf602
commit 2a844d275d
4 changed files with 232 additions and 246 deletions

View File

@@ -1,142 +1,126 @@
import express from 'express'; import express from 'express'
const router = express.Router(); import { PrismaClient } from '../../prisma/generated/client/index.js'
import { PrismaClient } from '../../prisma/generated/client/index.js';
const prisma = new PrismaClient();
// GET all empleados const router = express.Router()
router.get('/', async (req, res) => { const prisma = new PrismaClient()
// ⚙️ helper: evita el crash al serializar BigInt
const fixBigInt = (data) =>
JSON.parse(JSON.stringify(data, (_, v) => (typeof v === 'bigint' ? v.toString() : v)))
// ───── GET todos los empleados ─────
router.get('/', async (_req, res) => {
try { try {
const empleados = await prisma.cliente.findMany({ const empleados = await prisma.cliente.findMany({ where: { empleado: true } })
where: { empleado: true }, res.json(fixBigInt(empleados))
}); } catch (e) {
res.json(empleados); console.error(e)
} catch (error) { res.status(500).json({ error: 'Error al obtener empleados.' })
console.error(error); // Log the error for debugging
res.status(500).json({ error: 'Error al obtener empleados.' });
} }
}); })
// GET empleado by ID // ───── GET empleado por ID ─────
router.get('/:id', async (req, res) => { router.get('/:id', async (req, res) => {
const { id } = req.params; const id = BigInt(req.params.id)
try { try {
const empleado = await prisma.cliente.findFirst({ const empleado = await prisma.cliente.findFirst({ where: { id, empleado: true } })
where: { if (!empleado) return res.status(404).json({ error: 'Empleado no encontrado.' })
id: parseInt(id), res.json(fixBigInt(empleado))
empleado: true } catch (e) {
}, console.error(e)
}); res.status(500).json({ error: 'Error al obtener empleado.' })
if (empleado) {
res.json(empleado);
} else {
res.status(404).json({ error: 'Empleado no encontrado.' });
}
} catch (error) {
console.error(error); // Log the error for debugging
res.status(500).json({ error: 'Error al obtener empleado.' });
} }
}); })
// POST create new empleado // ───── POST crear empleado ─────
router.post('/', async (req, res) => { router.post('/', async (req, res) => {
const { nombre, apellido, dni, telefono, direccion, email } = req.body; const {
try { name,
const nuevoEmpleado = await prisma.cliente.create({ cedula,
data: { telefono,
nombre, ubicacion = '.',
apellido, grupo_estudio,
dni, avatar_url,
telefono, idciat,
direccion, } = req.body
email,
empleado: true, // Ensure empleado is set to true
},
});
res.status(201).json(nuevoEmpleado);
} catch (error) {
console.error(error); // Log the error for debugging
if (error.code === 'P2002' && error.meta?.target?.includes('dni')) {
return res.status(400).json({ error: 'Ya existe un cliente con este DNI.' });
}
if (error.code === 'P2002' && error.meta?.target?.includes('email')) {
return res.status(400).json({ error: 'Ya existe un cliente con este Email.' });
}
res.status(500).json({ error: 'Error al crear empleado.' });
}
});
// PUT update empleado by ID try {
const nuevo = await prisma.cliente.create({
data: {
name,
cedula: BigInt(cedula),
telefono,
ubicacion,
grupo_estudio,
avatar_url,
idciat,
empleado: true,
},
})
res.status(201).json(fixBigInt(nuevo))
} catch (e) {
console.error(e)
if (e.code === 'P2002' && e.meta?.target?.includes('cedula'))
return res.status(400).json({ error: 'Ya existe un cliente con esa cédula.' })
res.status(500).json({ error: 'Error al crear empleado.' })
}
})
// ───── PUT actualizar empleado ─────
router.put('/:id', async (req, res) => { router.put('/:id', async (req, res) => {
const { id } = req.params; const id = BigInt(req.params.id)
const { nombre, apellido, dni, telefono, direccion, email } = req.body; const {
name,
cedula,
telefono,
ubicacion,
grupo_estudio,
avatar_url,
idciat,
} = req.body
try { try {
// First, check if the employee exists and is an employee const existe = await prisma.cliente.findFirst({ where: { id, empleado: true } })
const existingEmpleado = await prisma.cliente.findFirst({ if (!existe) return res.status(404).json({ error: 'Empleado no encontrado.' })
where: {
id: parseInt(id),
empleado: true,
},
});
if (!existingEmpleado) { const actualizado = await prisma.cliente.update({
return res.status(404).json({ error: 'Empleado no encontrado.' }); where: { id },
}
const empleadoActualizado = await prisma.cliente.update({
where: { id: parseInt(id) },
data: { data: {
nombre, name,
apellido, cedula: cedula !== undefined ? BigInt(cedula) : undefined,
dni,
telefono, telefono,
direccion, ubicacion,
email, grupo_estudio,
// empleado: true, // Keep it as an employee, or allow changing this? For now, keep as true. avatar_url,
idciat,
}, },
}); })
res.json(empleadoActualizado); res.json(fixBigInt(actualizado))
} catch (error) { } catch (e) {
console.error(error); // Log the error for debugging console.error(e)
if (error.code === 'P2002' && error.meta?.target?.includes('dni')) { if (e.code === 'P2002' && e.meta?.target?.includes('cedula'))
return res.status(400).json({ error: 'Ya existe un cliente con este DNI.' }); return res.status(400).json({ error: 'Ya existe un cliente con esa cédula.' })
} if (e.code === 'P2025')
if (error.code === 'P2002' && error.meta?.target?.includes('email')) { return res.status(404).json({ error: 'Empleado no encontrado para actualizar.' })
return res.status(400).json({ error: 'Ya existe un cliente con este Email.' }); res.status(500).json({ error: 'Error al actualizar empleado.' })
}
if (error.code === 'P2025') { // Record to update not found
return res.status(404).json({ error: 'Empleado no encontrado para actualizar.' });
}
res.status(500).json({ error: 'Error al actualizar empleado.' });
} }
}); })
// DELETE empleado by ID // ───── DELETE eliminar empleado ─────
router.delete('/:id', async (req, res) => { router.delete('/:id', async (req, res) => {
const { id } = req.params; const id = BigInt(req.params.id)
try { try {
// First, check if the employee exists and is an employee const existe = await prisma.cliente.findFirst({ where: { id, empleado: true } })
const existingEmpleado = await prisma.cliente.findFirst({ if (!existe) return res.status(404).json({ error: 'Empleado no encontrado.' })
where: {
id: parseInt(id),
empleado: true,
},
});
if (!existingEmpleado) { await prisma.cliente.delete({ where: { id } })
return res.status(404).json({ error: 'Empleado no encontrado para eliminar.' }); res.status(204).send()
} } catch (e) {
console.error(e)
await prisma.cliente.delete({ if (e.code === 'P2025')
where: { id: parseInt(id) }, return res.status(404).json({ error: 'Empleado no encontrado para eliminar.' })
}); res.status(500).json({ error: 'Error al eliminar empleado.' })
res.status(204).send(); // No content
} catch (error) {
console.error(error); // Log the error for debugging
if (error.code === 'P2025') { // Record to delete not found
return res.status(404).json({ error: 'Empleado no encontrado para eliminar.' });
}
res.status(500).json({ error: 'Error al eliminar empleado.' });
} }
}); })
export default router; export default router

View File

@@ -20,9 +20,9 @@
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg> <svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
<span>{{ employee.ubicacion }}</span> <span>{{ employee.ubicacion }}</span>
</div> </div>
<div v-if="employee.idciat" class="flex items-center text-gray-700"> <div v-if="employee.cedula" class="flex items-center text-gray-700">
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 012-2h2a2 2 0 012 2v1m-4 0h4m-6 10v-5m0 5v0z"></path></svg> <svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 012-2h2a2 2 0 012 2v1m-4 0h4m-6 10v-5m0 5v0z"></path></svg>
<span>ID CIAT: {{ employee.idciat }}</span> <span>ID CIAT: {{ employee.cedula }}</span>
</div> </div>
<div v-if="employee.grupo_estudio" class="flex items-center text-gray-700"> <div v-if="employee.grupo_estudio" class="flex items-center text-gray-700">
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 14l9-5-9-5-9 5 9 5z"></path><path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-5.998 12.083 12.083 0 01.665-6.479L12 14z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0v3.945m0-3.945L6.161 10.58M17.839 10.58L12 14m5.839-3.42L12 14m0 0l6.161 3.42m-6.161-3.42L5.839 14.002"></path></svg> <svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 14l9-5-9-5-9 5 9 5z"></path><path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-5.998 12.083 12.083 0 01.665-6.479L12 14z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0v3.945m0-3.945L6.161 10.58M17.839 10.58L12 14m5.839-3.42L12 14m0 0l6.161 3.42m-6.161-3.42L5.839 14.002"></path></svg>

View File

@@ -47,8 +47,9 @@ export const useEmpleadosStore = defineStore('empleados', {
async createEmpleado (empleadoData) { async createEmpleado (empleadoData) {
try { try {
await apiClient.post('/api/empleados', empleadoData); const {data} = await apiClient.post('/api/empleados/', empleadoData);
await this.fetchEmpleados(); await this.fetchEmpleados();
return data
} catch (err) { } catch (err) {
console.error('Error creando empleado:', err); console.error('Error creando empleado:', err);
throw err; // para que el form muestre feedback throw err; // para que el form muestre feedback
@@ -57,9 +58,10 @@ export const useEmpleadosStore = defineStore('empleados', {
async updateEmpleado (id, empleadoData) { async updateEmpleado (id, empleadoData) {
try { try {
await apiClient.put(`/api/empleados/${id}`, empleadoData); const {data} = await apiClient.put(`/api/empleados/${id}`, empleadoData);
await this.fetchEmpleados(); await this.fetchEmpleados();
this.clearCurrentEmpleado(); this.clearCurrentEmpleado();
return data
} catch (err) { } catch (err) {
console.error(`Error actualizando empleado ${id}:`, err); console.error(`Error actualizando empleado ${id}:`, err);
throw err; throw err;

View File

@@ -3,100 +3,140 @@
<h1 class="text-3xl font-bold mb-8 text-center text-gray-700"> <h1 class="text-3xl font-bold mb-8 text-center text-gray-700">
{{ isEditMode ? 'Editar Empleado' : 'Crear Empleado' }} {{ isEditMode ? 'Editar Empleado' : 'Crear Empleado' }}
</h1> </h1>
<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 bg-white p-8 rounded-lg shadow-lg"
> >
<!-- Nombre -->
<div class="mb-6"> <div class="mb-6">
<label for="name" class="block text-gray-700 font-semibold mb-2">Nombre Completo</label> <label
for="name"
class="block text-gray-700 font-semibold mb-2"
>Nombre Completo</label>
<input <input
id="name" id="name"
v-model="form.name" v-model="form.name"
type="text" type="text"
required required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ej: Juan Pérez" placeholder="Ej: Juan Pérez"
/> />
</div> </div>
<!-- Cédula -->
<div class="mb-6"> <div class="mb-6">
<label for="cedula" class="block text-gray-700 font-semibold mb-2">Cédula</label> <label
for="cedula"
class="block text-gray-700 font-semibold mb-2"
>Cédula</label>
<input <input
id="cedula" id="cedula"
v-model="form.cedula" v-model="form.cedula"
type="number" type="number"
required required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ej: 123456789" placeholder="Ej: 123456789"
/> />
</div> </div>
<!-- Ubicación -->
<div class="mb-6"> <div class="mb-6">
<label for="ubicacion" class="block text-gray-700 font-semibold mb-2">Ubicación</label> <label
for="ubicacion"
class="block text-gray-700 font-semibold mb-2"
>Ubicación</label>
<input <input
id="ubicacion" id="ubicacion"
v-model="form.ubicacion" v-model="form.ubicacion"
type="text" type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ej: Oficina Principal" placeholder="Ej: Oficina Principal"
/> />
</div> </div>
<!-- Grupo de estudio -->
<div class="mb-6"> <div class="mb-6">
<label for="grupo_estudio" class="block text-gray-700 font-semibold mb-2">Grupo de Estudio</label> <label
for="grupo_estudio"
class="block text-gray-700 font-semibold mb-2"
>Grupo de Estudio</label>
<input <input
id="grupo_estudio" id="grupo_estudio"
v-model="form.grupo_estudio" v-model="form.grupo_estudio"
type="text" type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ej: Grupo A" placeholder="Ej: Grupo A"
/> />
</div> </div>
<!-- Avatar -->
<div class="mb-6"> <div class="mb-6">
<label for="avatar_url" class="block text-gray-700 font-semibold mb-2">URL del Avatar</label> <label
for="avatar_url"
class="block text-gray-700 font-semibold mb-2"
>URL del Avatar</label>
<input <input
id="avatar_url" id="avatar_url"
v-model="form.avatar_url" v-model="form.avatar_url"
type="url" type="url"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ej: https://example.com/avatar.png" placeholder="Ej: https://example.com/avatar.png"
/> />
</div> </div>
<!-- Teléfono -->
<div class="mb-6"> <div class="mb-6">
<label for="telefono" class="block text-gray-700 font-semibold mb-2">Teléfono</label> <label
for="telefono"
class="block text-gray-700 font-semibold mb-2"
>Teléfono</label>
<input <input
id="telefono" id="telefono"
v-model="form.telefono" v-model="form.telefono"
type="tel" type="tel"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ej: 0991234567" placeholder="Ej: 0991234567"
/> />
</div> </div>
<!-- ID CIAT -->
<div class="mb-6"> <div class="mb-6">
<label for="idciat" class="block text-gray-700 font-semibold mb-2">ID CIAT</label> <label
for="idciat"
class="block text-gray-700 font-semibold mb-2"
>ID CIAT</label>
<input <input
id="idciat" id="idciat"
v-model="form.idciat" v-model="form.idciat"
type="text" type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" class="w-full px-4 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ej: CIAT123" placeholder="Ej: CIAT123"
/> />
</div> </div>
<!-- Botones -->
<div class="flex justify-end"> <div class="flex justify-end">
<button <button
type="button" type="button"
@click="handleCancel" @click="handleCancel"
class="mr-4 px-6 py-2 text-gray-700 border border-gray-300 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400" class="mr-4 px-6 py-2 text-gray-700 border border-gray-300 rounded-md
hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400"
> >
Cancelar Cancelar
</button> </button>
<button <button
type="submit" type="submit"
class="px-6 py-2 bg-blue-600 text-white font-semibold rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" class="px-6 py-2 bg-blue-600 text-white font-semibold rounded-md
hover:bg-blue-700 focus:outline-none focus:ring-2
focus:ring-blue-500 focus:ring-offset-2"
> >
{{ isEditMode ? 'Actualizar' : 'Crear' }} {{ isEditMode ? 'Actualizar' : 'Crear' }}
</button> </button>
@@ -108,7 +148,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useEmpleadosStore } from '@/stores/useEmpleados.js'
/* ───── Tipos ───── */
interface EmpleadoForm { interface EmpleadoForm {
name: string name: string
cedula: number | null cedula: number | null
@@ -119,154 +162,111 @@ interface EmpleadoForm {
idciat?: string idciat?: string
} }
const route = useRoute() /* ───── helpers ───── */
const router = useRouter() const defaultForm = (): EmpleadoForm => ({
const form = ref<EmpleadoForm>({
name: '', name: '',
cedula: null, cedula: null,
ubicacion: '.', // Default value as per schema ubicacion: '.', // default del schema
grupo_estudio: '', grupo_estudio: '',
avatar_url: '', avatar_url: '',
telefono: '', telefono: '',
idciat: '', idciat: '',
}) })
const employeeId = ref<string | null>(null) /* ───── Router ───── */
const route = useRoute()
const router = useRouter()
const isEditMode = computed(() => !!employeeId.value) /* ───── Store ───── */
const empleadosStore = useEmpleadosStore()
const { currentEmpleado } = storeToRefs(empleadosStore)
/* ───── State ───── */
const form = ref<EmpleadoForm>(defaultForm())
const employeeId = computed(() => route.params.id as string | undefined)
const isEditMode = computed(() => !!employeeId.value)
/* ───── Lifecycle ───── */
onMounted(async () => { onMounted(async () => {
if (route.params.id) { if (isEditMode.value && employeeId.value) {
employeeId.value = route.params.id as string try {
// In a real application, you would fetch the employee data here await empleadosStore.fetchEmpleadoById(employeeId.value)
// For example: // copiamos solo campos que el form necesita
// const response = await fetch(`/api/employees/${employeeId.value}`) form.value = {
// const data = await response.json() name : currentEmpleado.value.name ?? '',
// form.value = data // Populate form with fetched data cedula : currentEmpleado.value.cedula ?? null,
// For now, we'll simulate fetching data or indicate that it needs to be done ubicacion : currentEmpleado.value.ubicacion ?? '.',
if (employeeId.value) { grupo_estudio : currentEmpleado.value.grupo_estudio ?? '',
console.log(`Editing employee ID: ${employeeId.value}. Need to fetch data.`); avatar_url : currentEmpleado.value.avatar_url ?? '',
// Example: Pre-populate form for an existing employee telefono : currentEmpleado.value.telefono ?? '',
// form.value = { idciat : currentEmpleado.value.idciat ?? '',
// name: 'Juan Pérez Existente', }
// cedula: 123456789, } catch (err) {
// ubicacion: 'Oficina Antigua', console.error('Error cargando empleado:', err)
// grupo_estudio: 'Grupo Z',
// avatar_url: 'https://example.com/avatar_existente.png',
// telefono: '0987654321',
// idciat: 'CIATXYZ'
// };
} }
} }
}) })
/* ───── Submit ───── */
const handleSubmit = async () => { const handleSubmit = async () => {
// Ensure cedula is a number if provided, or null otherwise const payload = { ...form.value, empleado: true }
const cedulaValue = form.value.cedula ? Number(form.value.cedula) : null;
const payload = {
...form.value,
cedula: cedulaValue,
empleado: true, // This form is specifically for employees
}
try { try {
if (isEditMode.value) { if (isEditMode.value && employeeId.value) {
console.log('Actualizando empleado:', employeeId.value, payload) await empleadosStore.updateEmpleado(employeeId.value, payload)
// Replace with actual API call
// const response = await fetch(`/api/clientes/${employeeId.value}`, {
// method: 'PUT',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(payload),
// })
// if (!response.ok) throw new Error('Failed to update employee')
// const updatedEmployee = await response.json()
// console.log('Empleado actualizado:', updatedEmployee)
} else { } else {
console.log('Creando empleado:', payload) await empleadosStore.createEmpleado(payload)
// Replace with actual API call
// const response = await fetch('/api/clientes', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(payload),
// })
// if (!response.ok) throw new Error('Failed to create employee')
// const newEmployee = await response.json()
// console.log('Empleado creado:', newEmployee)
} }
router.push('/empleados') // Or wherever the employee list is router.push('/empleados')
} catch (error) { } catch (err) {
console.error('Error submitting form:', error) console.error('Error al guardar empleado:', err)
// Handle error (e.g., show a notification to the user) // aquí podrías disparar una notificación
} }
} }
/* ───── Cancel ───── */
const handleCancel = () => { const handleCancel = () => {
router.go(-1) // Go back to the previous page or to a default route empleadosStore.clearCurrentEmpleado()
router.go(-1)
} }
</script> </script>
<style scoped> <style scoped>
/* Custom styles for better visual appeal if needed */ /* --- Validación rápida de inputs requeridos --- */
input:required:invalid { input:required:invalid {
border-color: #e53e3e; /* Tailwind's red-600 */ border-color: #e53e3e; /* red-600 */
} }
input:focus, button:focus { /* --- Focus global para inputs y botones --- */
input:focus,
button:focus {
outline: none; outline: none;
box-shadow: 0 0 0 2px #3b82f6; /* Tailwind's blue-500 */ box-shadow: 0 0 0 2px #3b82f6; /* blue-500 */
} }
/* Styling for a more elegant look and feel */ /* --- Look & feel extra (opcional, podés ajustar) --- */
.form-container { .form-container { background-color: #f9fafb; } /* gray-50 */
background-color: #f9fafb; /* Tailwind's gray-50 */ .form-card { box-shadow: 0 10px 15px -3px rgba(0,0,0,.1),
} 0 4px 6px -2px rgba(0,0,0,.05); }
.form-card { .form-title { color: #1f2937; } /* gray-800 */
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */ .form-label { color: #374151; font-weight: 600; } /* gray-700 */
} .form-input { border-color: #d1d5db; transition: border-color .2s, box-shadow .2s; }
.form-title {
color: #1f2937; /* Tailwind's gray-800 */
}
.form-label {
color: #374151; /* Tailwind's gray-700 */
font-weight: 600; /* semibold */
}
.form-input {
border-color: #d1d5db; /* Tailwind's gray-300 */
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.form-input:focus { .form-input:focus {
border-color: #2563eb; /* Tailwind's blue-600 */ border-color: #2563eb; /* blue-600 */
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); /* Ring focus */ box-shadow: 0 0 0 3px rgba(59,130,246,.5);
} }
.btn-primary { .btn-primary {
background-color: #2563eb; /* Tailwind's blue-600 */ background-color: #2563eb; color: #fff; font-weight: 600;
color: white; padding: .75rem 1.5rem; border-radius: .375rem; transition: background-color .2s;
font-weight: 600; /* semibold */
padding: 0.75rem 1.5rem;
border-radius: 0.375rem; /* rounded-md */
transition: background-color 0.2s ease-in-out;
}
.btn-primary:hover {
background-color: #1d4ed8; /* Tailwind's blue-700 */
} }
.btn-primary:hover { background-color: #1d4ed8; }
.btn-secondary { .btn-secondary {
background-color: #e5e7eb; /* Tailwind's gray-200 */ background-color: #e5e7eb; color: #374151; font-weight: 600;
color: #374151; /* Tailwind's gray-700 */ padding: .75rem 1.5rem; border-radius: .375rem; border: 1px solid #d1d5db;
font-weight: 600; /* semibold */ transition: background-color .2s;
padding: 0.75rem 1.5rem;
border-radius: 0.375rem; /* rounded-md */
border: 1px solid #d1d5db; /* Tailwind's gray-300 */
transition: background-color 0.2s ease-in-out;
}
.btn-secondary:hover {
background-color: #d1d5db; /* Tailwind's gray-300 */
} }
.btn-secondary:hover { background-color: #d1d5db; }
</style> </style>