feat: add modal for batch inventory item addition with responsive design and Tailwind CSS
This commit is contained in:
parent
19753a0f48
commit
9661275bc5
@ -34,6 +34,14 @@ const menuItems = ref<MenuItem[]>([
|
||||
{ label: 'Administrar Clasificaciones', icon: 'pi pi-sitemap', to: '/warehouse/classifications' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Recursos humanos',
|
||||
icon: 'pi pi-users',
|
||||
items: [
|
||||
{ label: 'Puestos laborales', icon: 'pi pi-user', to: '/rh/positions' },
|
||||
{ label: 'Departamentos', icon: 'pi pi-briefcase', to: '/rh/departments' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Productos',
|
||||
icon: 'pi pi-shopping-cart',
|
||||
|
||||
467
src/modules/rh/components/Departments.vue
Normal file
467
src/modules/rh/components/Departments.vue
Normal file
@ -0,0 +1,467 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Toast Notifications -->
|
||||
<Toast position="bottom-right" />
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<Breadcrumb :home="breadcrumbHome" :model="breadcrumbItems" />
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-wrap items-baseline justify-between gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-gray-900 dark:text-white text-3xl md:text-4xl font-black leading-tight tracking-tight">
|
||||
Gestión de Departamentos
|
||||
</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm">
|
||||
Total: {{ departments.length }} unidades operativas
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
label="Nuevo Departamento"
|
||||
icon="pi pi-plus"
|
||||
@click="openCreateDialog"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-primary-50 dark:bg-primary-900/20 text-primary flex items-center justify-center">
|
||||
<i class="pi pi-users text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-xs font-medium uppercase tracking-wider">
|
||||
Plantilla Total
|
||||
</p>
|
||||
<p class="text-2xl font-black text-gray-900 dark:text-white">{{ totalEmployees }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-green-50 dark:bg-green-900/20 text-green-600 flex items-center justify-center">
|
||||
<i class="pi pi-chart-line text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-xs font-medium uppercase tracking-wider">
|
||||
Nuevas Vacantes
|
||||
</p>
|
||||
<p class="text-2xl font-black text-gray-900 dark:text-white">5</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-purple-50 dark:bg-purple-900/20 text-purple-600 flex items-center justify-center">
|
||||
<i class="pi pi-calendar text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-xs font-medium uppercase tracking-wider">
|
||||
Próximas Evaluaciones
|
||||
</p>
|
||||
<p class="text-2xl font-black text-gray-900 dark:text-white">12</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Departments Table -->
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<DataTable
|
||||
:value="departments"
|
||||
:paginator="true"
|
||||
:rows="10"
|
||||
:rowsPerPageOptions="[10, 25, 50]"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
||||
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} departamentos"
|
||||
:loading="loading"
|
||||
stripedRows
|
||||
responsiveLayout="scroll"
|
||||
class="text-sm"
|
||||
>
|
||||
<Column field="name" header="Nombre del Departamento" sortable>
|
||||
<template #body="slotProps">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="size-10 rounded-full flex items-center justify-center text-white font-bold"
|
||||
:style="{ backgroundColor: slotProps.data.color }"
|
||||
>
|
||||
<i :class="slotProps.data.icon"></i>
|
||||
</div>
|
||||
<span class="font-semibold text-gray-900 dark:text-white">
|
||||
{{ slotProps.data.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="description" header="Descripción" sortable>
|
||||
<template #body="slotProps">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ slotProps.data.description }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="employeeCount" header="Número de Empleados" sortable class="text-center">
|
||||
<template #body="slotProps">
|
||||
<Tag
|
||||
:value="`${slotProps.data.employeeCount} empleados`"
|
||||
severity="secondary"
|
||||
/>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Acciones" :exportable="false">
|
||||
<template #body="slotProps">
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
icon="pi pi-eye"
|
||||
outlined
|
||||
rounded
|
||||
size="small"
|
||||
severity="secondary"
|
||||
@click="viewDepartment(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-pencil"
|
||||
outlined
|
||||
rounded
|
||||
size="small"
|
||||
@click="editDepartment(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
outlined
|
||||
rounded
|
||||
size="small"
|
||||
severity="danger"
|
||||
@click="confirmDelete(slotProps.data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<!-- Create/Edit Dialog -->
|
||||
<Dialog
|
||||
v-model:visible="showDialog"
|
||||
:modal="true"
|
||||
:style="{ width: '800px' }"
|
||||
:closable="true"
|
||||
class="p-0"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<h2 class="text-gray-900 dark:text-white text-2xl font-bold tracking-tight">
|
||||
{{ dialogMode === 'create' ? 'Crear Nuevo Departamento' : 'Editar Departamento' }}
|
||||
</h2>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-base font-normal leading-normal">
|
||||
Configure los detalles básicos para habilitar el nuevo departamento en la estructura organizacional.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="p-6 pt-0 space-y-6">
|
||||
<!-- Department Name Field -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-gray-900 dark:text-gray-200 text-base font-semibold leading-normal">
|
||||
Nombre del Departamento
|
||||
</label>
|
||||
<InputText
|
||||
v-model="formData.name"
|
||||
placeholder="Ej. Logística, Producción o Calidad"
|
||||
class="w-full h-14"
|
||||
/>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-xs font-normal">
|
||||
Este nombre será visible en todos los reportes de RH.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Department Description Field -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-gray-900 dark:text-gray-200 text-base font-semibold leading-normal">
|
||||
Descripción del Departamento
|
||||
</label>
|
||||
<Textarea
|
||||
v-model="formData.description"
|
||||
rows="5"
|
||||
placeholder="Describa las funciones, responsabilidades y objetivos principales de este departamento..."
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-end gap-4 pt-4 border-t border-gray-200 dark:border-gray-800">
|
||||
<Button
|
||||
label="Cancelar"
|
||||
severity="secondary"
|
||||
text
|
||||
@click="showDialog = false"
|
||||
class="px-6"
|
||||
/>
|
||||
<Button
|
||||
:label="dialogMode === 'create' ? 'Guardar Departamento' : 'Actualizar Departamento'"
|
||||
icon="pi pi-save"
|
||||
@click="saveDepartment"
|
||||
class="px-8"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- Delete Confirmation Dialog -->
|
||||
<Dialog
|
||||
v-model:visible="showDeleteDialog"
|
||||
header="Confirmar Eliminación"
|
||||
:modal="true"
|
||||
:style="{ width: '450px' }"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<i class="pi pi-exclamation-triangle text-3xl text-red-500"></i>
|
||||
<div>
|
||||
<p class="text-gray-900 dark:text-white mb-2">
|
||||
¿Estás seguro de que deseas eliminar el departamento <strong>{{ departmentToDelete?.name }}</strong>?
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Esta acción no se puede deshacer.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button
|
||||
label="Cancelar"
|
||||
severity="secondary"
|
||||
@click="showDeleteDialog = false"
|
||||
/>
|
||||
<Button
|
||||
label="Eliminar"
|
||||
severity="danger"
|
||||
icon="pi pi-trash"
|
||||
@click="deleteDepartment"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
// PrimeVue Components
|
||||
import Toast from 'primevue/toast';
|
||||
import Breadcrumb from 'primevue/breadcrumb';
|
||||
import Card from 'primevue/card';
|
||||
import Button from 'primevue/button';
|
||||
import DataTable from 'primevue/datatable';
|
||||
import Column from 'primevue/column';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Textarea from 'primevue/textarea';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import Select from 'primevue/select';
|
||||
import Tag from 'primevue/tag';
|
||||
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
||||
// State
|
||||
const loading = ref(false);
|
||||
const showDialog = ref(false);
|
||||
const showDeleteDialog = ref(false);
|
||||
const dialogMode = ref<'create' | 'edit'>('create');
|
||||
const departmentToDelete = ref<any>(null);
|
||||
|
||||
// Breadcrumb
|
||||
const breadcrumbHome = ref({ icon: 'pi pi-home', to: '/' });
|
||||
const breadcrumbItems = ref([
|
||||
{ label: 'RH', to: '/rh' },
|
||||
{ label: 'Departamentos' }
|
||||
]);
|
||||
|
||||
// Form Data
|
||||
const formData = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
employeeCount: 0,
|
||||
color: '#3b82f6',
|
||||
icon: 'pi pi-building'
|
||||
});
|
||||
|
||||
// Color Options
|
||||
const colorOptions = [
|
||||
{ label: 'Azul', value: '#3b82f6' },
|
||||
{ label: 'Verde', value: '#10b981' },
|
||||
{ label: 'Púrpura', value: '#8b5cf6' },
|
||||
{ label: 'Naranja', value: '#f97316' },
|
||||
{ label: 'Rosa', value: '#ec4899' },
|
||||
{ label: 'Rojo', value: '#ef4444' },
|
||||
{ label: 'Amarillo', value: '#f59e0b' },
|
||||
{ label: 'Índigo', value: '#6366f1' },
|
||||
];
|
||||
|
||||
// Icon Options
|
||||
const iconOptions = [
|
||||
{ label: 'Edificio', value: 'pi pi-building' },
|
||||
{ label: 'Usuarios', value: 'pi pi-users' },
|
||||
{ label: 'Cog', value: 'pi pi-cog' },
|
||||
{ label: 'Herramientas', value: 'pi pi-wrench' },
|
||||
{ label: 'Camión', value: 'pi pi-truck' },
|
||||
{ label: 'Gráfico', value: 'pi pi-chart-line' },
|
||||
{ label: 'Escudo', value: 'pi pi-shield' },
|
||||
{ label: 'Estrella', value: 'pi pi-star' },
|
||||
];
|
||||
|
||||
// Mock Data - Departments
|
||||
const departments = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Producción',
|
||||
description: 'Línea de ensamblaje y manufactura de productos terminados.',
|
||||
employeeCount: 45,
|
||||
color: '#3b82f6',
|
||||
icon: 'pi pi-building'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Logística',
|
||||
description: 'Gestión de envíos nacionales, recepción y almacenamiento.',
|
||||
employeeCount: 22,
|
||||
color: '#10b981',
|
||||
icon: 'pi pi-truck'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Control de Calidad',
|
||||
description: 'Inspección de estándares de calidad y auditoría de procesos.',
|
||||
employeeCount: 12,
|
||||
color: '#8b5cf6',
|
||||
icon: 'pi pi-shield'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Mantenimiento',
|
||||
description: 'Soporte técnico, reparaciones y mantenimiento preventivo.',
|
||||
employeeCount: 8,
|
||||
color: '#f97316',
|
||||
icon: 'pi pi-wrench'
|
||||
},
|
||||
]);
|
||||
|
||||
// Computed
|
||||
const totalEmployees = computed(() => {
|
||||
return departments.value.reduce((sum, dept) => sum + dept.employeeCount, 0);
|
||||
});
|
||||
|
||||
// Methods
|
||||
const openCreateDialog = () => {
|
||||
dialogMode.value = 'create';
|
||||
formData.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
employeeCount: 0,
|
||||
color: '#3b82f6',
|
||||
icon: 'pi pi-building'
|
||||
};
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const viewDepartment = (department: any) => {
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Ver Departamento',
|
||||
detail: `Visualizando: ${department.name}`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const editDepartment = (department: any) => {
|
||||
dialogMode.value = 'edit';
|
||||
formData.value = { ...department };
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const confirmDelete = (department: any) => {
|
||||
departmentToDelete.value = department;
|
||||
showDeleteDialog.value = true;
|
||||
};
|
||||
|
||||
const deleteDepartment = () => {
|
||||
if (departmentToDelete.value) {
|
||||
const index = departments.value.findIndex(d => d.id === departmentToDelete.value.id);
|
||||
if (index > -1) {
|
||||
departments.value.splice(index, 1);
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Departamento Eliminado',
|
||||
detail: `${departmentToDelete.value.name} ha sido eliminado`,
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
showDeleteDialog.value = false;
|
||||
departmentToDelete.value = null;
|
||||
};
|
||||
|
||||
const saveDepartment = () => {
|
||||
if (!formData.value.name) {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Validación',
|
||||
detail: 'El nombre del departamento es requerido',
|
||||
life: 3000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogMode.value === 'create') {
|
||||
// Create new department
|
||||
const newDepartment = {
|
||||
id: Math.max(...departments.value.map(d => d.id)) + 1,
|
||||
...formData.value
|
||||
};
|
||||
departments.value.push(newDepartment);
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Departamento Creado',
|
||||
detail: `${formData.value.name} ha sido creado exitosamente`,
|
||||
life: 3000
|
||||
});
|
||||
} else {
|
||||
// Update existing department
|
||||
const index = departments.value.findIndex(d => d.id === formData.value.id);
|
||||
if (index > -1) {
|
||||
departments.value[index] = { ...formData.value };
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Departamento Actualizado',
|
||||
detail: `${formData.value.name} ha sido actualizado`,
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showDialog.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Estilos adicionales si son necesarios */
|
||||
</style>
|
||||
521
src/modules/rh/components/Positions.vue
Normal file
521
src/modules/rh/components/Positions.vue
Normal file
@ -0,0 +1,521 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Toast Notifications -->
|
||||
<Toast position="bottom-right" />
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<Breadcrumb :home="breadcrumbHome" :model="breadcrumbItems" />
|
||||
|
||||
<!-- Page Heading -->
|
||||
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-3xl font-black text-gray-900 dark:text-white tracking-tight">
|
||||
Puestos de Trabajo
|
||||
</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm">
|
||||
Administre y organice los roles operativos y administrativos de la planta.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
label="Nuevo Puesto"
|
||||
icon="pi pi-plus-circle"
|
||||
@click="openCreateDialog"
|
||||
class="shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Bar -->
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="flex-1 relative">
|
||||
<i class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<InputText
|
||||
v-model="searchQuery"
|
||||
placeholder="Buscar por nombre o departamento..."
|
||||
class="w-full h-11 pl-10"
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
v-model="selectedDepartment"
|
||||
:options="departmentOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="Todos los departamentos"
|
||||
class="w-full md:w-64"
|
||||
/>
|
||||
<Button
|
||||
label="Filtros"
|
||||
icon="pi pi-filter"
|
||||
severity="secondary"
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<!-- Data Table Card -->
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<DataTable
|
||||
:value="filteredPositions"
|
||||
:paginator="true"
|
||||
:rows="10"
|
||||
:rowsPerPageOptions="[10, 25, 50]"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
||||
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} puestos"
|
||||
:loading="loading"
|
||||
stripedRows
|
||||
responsiveLayout="scroll"
|
||||
class="text-sm"
|
||||
>
|
||||
<Column field="name" header="Nombre del Puesto" sortable>
|
||||
<template #body="slotProps">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="size-8 rounded flex items-center justify-center"
|
||||
:class="slotProps.data.iconBg"
|
||||
>
|
||||
<i :class="slotProps.data.icon"></i>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ slotProps.data.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="department" header="Departamento" sortable>
|
||||
<template #body="slotProps">
|
||||
<span
|
||||
class="px-2.5 py-1 rounded-full text-[11px] font-bold"
|
||||
:class="getDepartmentBadge(slotProps.data.department)"
|
||||
>
|
||||
{{ slotProps.data.department.toUpperCase() }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="description" header="Descripción / Actividades" sortable>
|
||||
<template #body="slotProps">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 line-clamp-1">
|
||||
{{ slotProps.data.description }}
|
||||
</p>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Acciones" :exportable="false">
|
||||
<template #body="slotProps">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<Button
|
||||
icon="pi pi-eye"
|
||||
text
|
||||
rounded
|
||||
severity="secondary"
|
||||
@click="viewPosition(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-pencil"
|
||||
text
|
||||
rounded
|
||||
@click="editPosition(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
text
|
||||
rounded
|
||||
severity="danger"
|
||||
@click="confirmDelete(slotProps.data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<!-- Summary Info Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-primary-50 dark:bg-primary-900/20 text-primary flex items-center justify-center">
|
||||
<i class="pi pi-briefcase text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 font-medium">Total de Puestos</p>
|
||||
<p class="text-xl font-black text-gray-900 dark:text-white">{{ positions.length }} Roles</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-green-50 dark:bg-green-900/20 text-green-600 flex items-center justify-center">
|
||||
<i class="pi pi-building text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 font-medium">Departamentos Activos</p>
|
||||
<p class="text-xl font-black text-gray-900 dark:text-white">{{ activeDepartments }} Áreas</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-amber-50 dark:bg-amber-900/20 text-amber-600 flex items-center justify-center">
|
||||
<i class="pi pi-history text-xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 font-medium">Última Actualización</p>
|
||||
<p class="text-xl font-black text-gray-900 dark:text-white">Hoy, 09:12 AM</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Create/Edit Dialog -->
|
||||
<Dialog
|
||||
v-model:visible="showDialog"
|
||||
:modal="true"
|
||||
:style="{ width: '720px' }"
|
||||
:closable="true"
|
||||
class="p-0"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<h2 class="text-gray-900 dark:text-white text-2xl font-bold tracking-tight">
|
||||
{{ dialogMode === 'create' ? 'Crear Nuevo Puesto' : 'Editar Puesto' }}
|
||||
</h2>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-base font-normal leading-normal">
|
||||
Configure los detalles básicos para habilitar el nuevo puesto en la estructura organizacional.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="p-6 pt-0 space-y-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-gray-900 dark:text-gray-200 text-base font-semibold leading-normal">
|
||||
Nombre del Puesto
|
||||
</label>
|
||||
<InputText
|
||||
v-model="formData.name"
|
||||
placeholder="Ej. Supervisor de Almacén"
|
||||
class="w-full h-14"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-gray-900 dark:text-gray-200 text-base font-semibold leading-normal">
|
||||
Departamento
|
||||
</label>
|
||||
<Select
|
||||
v-model="formData.department"
|
||||
:options="departmentOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="Selecciona un departamento"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-gray-900 dark:text-gray-200 text-base font-semibold leading-normal">
|
||||
Descripción / Actividades
|
||||
</label>
|
||||
<Textarea
|
||||
v-model="formData.description"
|
||||
rows="5"
|
||||
placeholder="Describa las funciones y actividades principales del puesto..."
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 p-4 bg-primary-50 dark:bg-primary-900/10 rounded-lg border border-primary-200 dark:border-primary-800 flex gap-4">
|
||||
<div class="text-primary pt-1">
|
||||
<i class="pi pi-info-circle text-xl"></i>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-gray-900 dark:text-white text-sm font-bold">Importante</p>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-sm leading-relaxed">
|
||||
Al crear un nuevo puesto, este podrá asignarse a empleados y centros de costo. Puede editar estos detalles en cualquier momento.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-end gap-4 pt-4 border-t border-gray-200 dark:border-gray-800">
|
||||
<Button
|
||||
label="Cancelar"
|
||||
severity="secondary"
|
||||
text
|
||||
@click="showDialog = false"
|
||||
class="px-6"
|
||||
/>
|
||||
<Button
|
||||
:label="dialogMode === 'create' ? 'Guardar Puesto' : 'Actualizar Puesto'"
|
||||
icon="pi pi-save"
|
||||
@click="savePosition"
|
||||
class="px-8"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- Delete Confirmation Dialog -->
|
||||
<Dialog
|
||||
v-model:visible="showDeleteDialog"
|
||||
header="Confirmar Eliminación"
|
||||
:modal="true"
|
||||
:style="{ width: '450px' }"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<i class="pi pi-exclamation-triangle text-3xl text-red-500"></i>
|
||||
<div>
|
||||
<p class="text-gray-900 dark:text-white mb-2">
|
||||
¿Estás seguro de que deseas eliminar el puesto <strong>{{ positionToDelete?.name }}</strong>?
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Esta acción no se puede deshacer.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button
|
||||
label="Cancelar"
|
||||
severity="secondary"
|
||||
@click="showDeleteDialog = false"
|
||||
/>
|
||||
<Button
|
||||
label="Eliminar"
|
||||
severity="danger"
|
||||
icon="pi pi-trash"
|
||||
@click="deletePosition"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
// PrimeVue Components
|
||||
import Toast from 'primevue/toast';
|
||||
import Breadcrumb from 'primevue/breadcrumb';
|
||||
import Card from 'primevue/card';
|
||||
import Button from 'primevue/button';
|
||||
import DataTable from 'primevue/datatable';
|
||||
import Column from 'primevue/column';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Textarea from 'primevue/textarea';
|
||||
import Select from 'primevue/select';
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
// State
|
||||
const loading = ref(false);
|
||||
const showDialog = ref(false);
|
||||
const showDeleteDialog = ref(false);
|
||||
const dialogMode = ref<'create' | 'edit'>('create');
|
||||
const positionToDelete = ref<any>(null);
|
||||
const searchQuery = ref('');
|
||||
const selectedDepartment = ref('all');
|
||||
|
||||
// Breadcrumb
|
||||
const breadcrumbHome = ref({ icon: 'pi pi-home', to: '/' });
|
||||
const breadcrumbItems = ref([
|
||||
{ label: 'RH', to: '/rh' },
|
||||
{ label: 'Puestos' }
|
||||
]);
|
||||
|
||||
// Options
|
||||
const departmentOptions = [
|
||||
{ label: 'Todos los departamentos', value: 'all' },
|
||||
{ label: 'Logística', value: 'logistica' },
|
||||
{ label: 'Producción', value: 'produccion' },
|
||||
{ label: 'Almacén', value: 'almacen' },
|
||||
{ label: 'EHS', value: 'ehs' },
|
||||
];
|
||||
|
||||
// Form Data
|
||||
const formData = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
department: 'logistica',
|
||||
description: ''
|
||||
});
|
||||
|
||||
// Mock Data - Positions
|
||||
const positions = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Supervisor de Almacén',
|
||||
department: 'logistica',
|
||||
description: 'Coordinar la recepción de mercancías, gestión de inventarios y despacho...',
|
||||
icon: 'pi pi-users',
|
||||
iconBg: 'bg-blue-50 dark:bg-blue-900/20 text-blue-600'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Operario de Línea A',
|
||||
department: 'produccion',
|
||||
description: 'Operación de maquinaria de ensamblado y control de calidad primario...',
|
||||
icon: 'pi pi-cog',
|
||||
iconBg: 'bg-green-50 dark:bg-green-900/20 text-green-600'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Montacarguista',
|
||||
department: 'almacen',
|
||||
description: 'Traslado de pallets a zona de racks y carga de camiones de distribución...',
|
||||
icon: 'pi pi-truck',
|
||||
iconBg: 'bg-orange-50 dark:bg-orange-900/20 text-orange-600'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Analista de Seguridad',
|
||||
department: 'ehs',
|
||||
description: 'Monitoreo de protocolos de seguridad e higiene industrial en planta...',
|
||||
icon: 'pi pi-shield',
|
||||
iconBg: 'bg-purple-50 dark:bg-purple-900/20 text-purple-600'
|
||||
},
|
||||
]);
|
||||
|
||||
const departmentLabelMap: Record<string, string> = {
|
||||
logistica: 'Logística',
|
||||
produccion: 'Producción',
|
||||
almacen: 'Almacén',
|
||||
ehs: 'EHS',
|
||||
};
|
||||
|
||||
// Computed
|
||||
const filteredPositions = computed(() => {
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
return positions.value.filter((pos) => {
|
||||
const matchesQuery =
|
||||
pos.name.toLowerCase().includes(query) ||
|
||||
departmentLabelMap[pos.department]?.toLowerCase().includes(query);
|
||||
const matchesDept =
|
||||
selectedDepartment.value === 'all' || pos.department === selectedDepartment.value;
|
||||
return matchesQuery && matchesDept;
|
||||
});
|
||||
});
|
||||
|
||||
const activeDepartments = computed(() => {
|
||||
const unique = new Set(positions.value.map((pos) => pos.department));
|
||||
return unique.size;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const getDepartmentBadge = (dept: string) => {
|
||||
const map: Record<string, string> = {
|
||||
logistica: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
|
||||
produccion: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300',
|
||||
almacen: 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300',
|
||||
ehs: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
};
|
||||
return map[dept] || 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300';
|
||||
};
|
||||
|
||||
const openCreateDialog = () => {
|
||||
dialogMode.value = 'create';
|
||||
formData.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
department: 'logistica',
|
||||
description: ''
|
||||
};
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const viewPosition = (position: any) => {
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Ver Puesto',
|
||||
detail: `Visualizando: ${position.name}`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const editPosition = (position: any) => {
|
||||
dialogMode.value = 'edit';
|
||||
formData.value = { ...position };
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const confirmDelete = (position: any) => {
|
||||
positionToDelete.value = position;
|
||||
showDeleteDialog.value = true;
|
||||
};
|
||||
|
||||
const deletePosition = () => {
|
||||
if (positionToDelete.value) {
|
||||
const index = positions.value.findIndex(p => p.id === positionToDelete.value.id);
|
||||
if (index > -1) {
|
||||
positions.value.splice(index, 1);
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Puesto Eliminado',
|
||||
detail: `${positionToDelete.value.name} ha sido eliminado`,
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
showDeleteDialog.value = false;
|
||||
positionToDelete.value = null;
|
||||
};
|
||||
|
||||
const savePosition = () => {
|
||||
if (!formData.value.name || !formData.value.department) {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Validación',
|
||||
detail: 'Nombre y departamento son requeridos',
|
||||
life: 3000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogMode.value === 'create') {
|
||||
const newPosition = {
|
||||
id: Math.max(...positions.value.map(p => p.id)) + 1,
|
||||
name: formData.value.name,
|
||||
department: formData.value.department,
|
||||
description: formData.value.description,
|
||||
icon: 'pi pi-users',
|
||||
iconBg: 'bg-blue-50 dark:bg-blue-900/20 text-blue-600'
|
||||
};
|
||||
positions.value.push(newPosition);
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Puesto Creado',
|
||||
detail: `${formData.value.name} ha sido creado exitosamente`,
|
||||
life: 3000
|
||||
});
|
||||
} else {
|
||||
const index = positions.value.findIndex(p => p.id === formData.value.id);
|
||||
if (index > -1) {
|
||||
positions.value[index] = { ...positions.value[index], ...formData.value };
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Puesto Actualizado',
|
||||
detail: `${formData.value.name} ha sido actualizado`,
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showDialog.value = false;
|
||||
};
|
||||
</script>
|
||||
323
src/modules/rh/components/index.html
Normal file
323
src/modules/rh/components/index.html
Normal file
@ -0,0 +1,323 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="light" lang="es"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Gestión de Puestos de Trabajo | RH Portal</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"primary": "#137fec",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: {
|
||||
"display": ["Inter"]
|
||||
},
|
||||
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background-light dark:bg-background-dark text-[#111418] dark:text-white font-display">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col shrink-0">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="size-10 rounded-lg bg-primary flex items-center justify-center text-white">
|
||||
<span class="material-symbols-outlined">factory</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-sm font-bold leading-none">Warehouse System</h1>
|
||||
<p class="text-xs text-gray-500 mt-1">Admin Portal</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 space-y-1 overflow-y-auto">
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors" href="#">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<span class="text-sm font-medium">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors" href="#">
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
<span class="text-sm font-medium">Inventario</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors" href="#">
|
||||
<span class="material-symbols-outlined">precision_manufacturing</span>
|
||||
<span class="text-sm font-medium">Producción</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 bg-primary/10 text-primary rounded-lg transition-colors" href="#">
|
||||
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">groups</span>
|
||||
<span class="text-sm font-medium">Recursos Humanos</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors" href="#">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
<span class="text-sm font-medium">Configuración</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-gray-200 dark:border-gray-800">
|
||||
<div class="flex items-center gap-3 p-2">
|
||||
<div class="size-8 rounded-full bg-cover bg-center" data-alt="User profile avatar of admin" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAZE4yg7YhxPnYbTfdwrgMaXbIwT17qinrNA2sczwJTMCecZuuRxWI9crz6h75iNsn4VKTmHSNkkwtpFOx2I72wWVgtbhDUeUX8ngUzF0A_22GqWF9F46TErldseaBQgTwjXDP5wWLyOU8S8Mf64JntceSLjq2U9exTu9Pkhgo6OPG1GkinNVD3rBNsVjwN-xNbtWLt6jYnwVcAkrCqawJsiK_Pf8R3chrYkUJNmzJgn9XSTOtBLUDT6A7K1V_G1jXX6k1jzSdWnFdg')"></div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs font-semibold truncate">Marcos Pérez</p>
|
||||
<p class="text-[10px] text-gray-500 truncate">HR Manager</p>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-gray-400 text-sm">logout</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<!-- Top Navigation Bar -->
|
||||
<header class="h-16 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 flex items-center justify-between px-8 shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<a class="text-xs font-medium text-gray-500 hover:text-primary transition-colors" href="#">INICIO</a>
|
||||
<span class="text-xs text-gray-400">/</span>
|
||||
<a class="text-xs font-medium text-gray-500 hover:text-primary transition-colors" href="#">RH</a>
|
||||
<span class="text-xs text-gray-400">/</span>
|
||||
<span class="text-xs font-bold text-gray-900 dark:text-white uppercase tracking-wider">Puestos</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm">search</span>
|
||||
<input class="h-9 w-64 bg-gray-100 dark:bg-gray-800 border-none rounded-lg pl-9 text-xs focus:ring-2 focus:ring-primary/50 transition-all" placeholder="Buscar..." type="text"/>
|
||||
</div>
|
||||
<button class="size-9 flex items-center justify-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 relative">
|
||||
<span class="material-symbols-outlined text-gray-600 dark:text-gray-400">notifications</span>
|
||||
<span class="absolute top-2 right-2 size-2 bg-red-500 rounded-full border-2 border-white dark:border-gray-900"></span>
|
||||
</button>
|
||||
<div class="h-8 w-px bg-gray-200 dark:bg-gray-800 mx-2"></div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="size-8 rounded-full bg-primary/20 text-primary flex items-center justify-center font-bold text-xs">AD</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<!-- Page Content -->
|
||||
<div class="flex-1 overflow-y-auto p-8 bg-background-light dark:bg-background-dark">
|
||||
<div class="max-w-6xl mx-auto space-y-6">
|
||||
<!-- Page Heading -->
|
||||
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-3xl font-black text-gray-900 dark:text-white tracking-tight">Puestos de Trabajo</h2>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm">Administre y organice los roles operativos y administrativos de la planta.</p>
|
||||
</div>
|
||||
<button class="bg-primary hover:bg-primary/90 text-white px-5 py-2.5 rounded-lg flex items-center gap-2 text-sm font-bold shadow-lg shadow-primary/20 transition-all">
|
||||
<span class="material-symbols-outlined text-lg">add_circle</span>
|
||||
Nuevo Puesto
|
||||
</button>
|
||||
</div>
|
||||
<!-- Search and Filter Bar -->
|
||||
<div class="bg-white dark:bg-gray-900 p-4 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 flex gap-4">
|
||||
<div class="flex-1 relative">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">search</span>
|
||||
<input class="w-full h-11 bg-gray-50 dark:bg-gray-800 border-none rounded-lg pl-10 text-sm focus:ring-2 focus:ring-primary/50" placeholder="Buscar por nombre o departamento..." type="text"/>
|
||||
</div>
|
||||
<button class="flex items-center gap-2 px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 transition-colors">
|
||||
<span class="material-symbols-outlined">filter_list</span>
|
||||
<span class="text-sm font-medium">Filtros</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Data Table Card -->
|
||||
<div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-800">
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-wider w-1/4">Nombre del Puesto</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-wider">Departamento</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-wider w-1/3">Descripción / Actividades</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-wider text-right">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<!-- Row 1 -->
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors group">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="size-8 rounded bg-blue-50 dark:bg-blue-900/20 flex items-center justify-center text-blue-600">
|
||||
<span class="material-symbols-outlined text-base">supervisor_account</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">Supervisor de Almacén</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
<span class="px-2.5 py-1 rounded-full text-[11px] font-bold bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300">LOGÍSTICA</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 line-clamp-1">Coordinar la recepción de mercancías, gestión de inventarios y despacho...</p>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors" title="Ver Detalle">
|
||||
<span class="material-symbols-outlined text-lg">visibility</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors" title="Editar">
|
||||
<span class="material-symbols-outlined text-lg">edit</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-red-500 transition-colors" title="Eliminar">
|
||||
<span class="material-symbols-outlined text-lg">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Row 2 -->
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors group">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="size-8 rounded bg-green-50 dark:bg-green-900/20 flex items-center justify-center text-green-600">
|
||||
<span class="material-symbols-outlined text-base">precision_manufacturing</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">Operario de Línea A</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
<span class="px-2.5 py-1 rounded-full text-[11px] font-bold bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300">PRODUCCIÓN</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 line-clamp-1">Operación de maquinaria de ensamblado y control de calidad primario...</p>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">visibility</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">edit</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Row 3 -->
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors group">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="size-8 rounded bg-orange-50 dark:bg-orange-900/20 flex items-center justify-center text-orange-600">
|
||||
<span class="material-symbols-outlined text-base">forklift</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">Montacarguista</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
<span class="px-2.5 py-1 rounded-full text-[11px] font-bold bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300">ALMACÉN</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 line-clamp-1">Traslado de pallets a zona de racks y carga de camiones de distribución...</p>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">visibility</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">edit</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Row 4 -->
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors group">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="size-8 rounded bg-purple-50 dark:bg-purple-900/20 flex items-center justify-center text-purple-600">
|
||||
<span class="material-symbols-outlined text-base">security</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">Analista de Seguridad</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
<span class="px-2.5 py-1 rounded-full text-[11px] font-bold bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300">EHS</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 line-clamp-1">Monitoreo de protocolos de seguridad e higiene industrial en planta...</p>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">visibility</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-primary transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">edit</span>
|
||||
</button>
|
||||
<button class="p-2 text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Pagination Footer -->
|
||||
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-800/50 border-t border-gray-200 dark:border-gray-800 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">Mostrando 1 a 4 de 12 puestos</span>
|
||||
<div class="flex gap-2">
|
||||
<button class="size-8 flex items-center justify-center rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-600 dark:text-gray-400 disabled:opacity-50" disabled="">
|
||||
<span class="material-symbols-outlined text-sm">chevron_left</span>
|
||||
</button>
|
||||
<button class="size-8 flex items-center justify-center rounded bg-primary text-white font-bold text-xs">1</button>
|
||||
<button class="size-8 flex items-center justify-center rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:bg-gray-50 transition-colors text-xs font-bold">2</button>
|
||||
<button class="size-8 flex items-center justify-center rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:bg-gray-50 transition-colors text-xs font-bold">3</button>
|
||||
<button class="size-8 flex items-center justify-center rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-600 dark:text-gray-400 hover:bg-gray-50 transition-colors">
|
||||
<span class="material-symbols-outlined text-sm">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Summary Info Cards (Subtle Details) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="bg-white dark:bg-gray-900 p-6 rounded-xl border border-gray-200 dark:border-gray-800 shadow-sm flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-primary/10 flex items-center justify-center text-primary">
|
||||
<span class="material-symbols-outlined">work</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 font-medium">Total de Puestos</p>
|
||||
<p class="text-xl font-black">12 Roles</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-900 p-6 rounded-xl border border-gray-200 dark:border-gray-800 shadow-sm flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-green-500/10 flex items-center justify-center text-green-600">
|
||||
<span class="material-symbols-outlined">domain</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 font-medium">Departamentos Activos</p>
|
||||
<p class="text-xl font-black">5 Áreas</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-900 p-6 rounded-xl border border-gray-200 dark:border-gray-800 shadow-sm flex items-center gap-4">
|
||||
<div class="size-12 rounded-full bg-amber-500/10 flex items-center justify-center text-amber-600">
|
||||
<span class="material-symbols-outlined">history</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 font-medium">Última Actualización</p>
|
||||
<p class="text-xl font-black">Hoy, 09:12 AM</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body></html>
|
||||
@ -20,6 +20,8 @@ import RolesIndex from '../modules/users/components/RoleIndex.vue';
|
||||
import RoleForm from '../modules/users/components/RoleForm.vue';
|
||||
import UserIndex from '../modules/users/components/UserIndex.vue';
|
||||
import StoreDetails from '../modules/stores/components/StoreDetails.vue';
|
||||
import Positions from '../modules/rh/components/Positions.vue';
|
||||
import Departments from '../modules/rh/components/Departments.vue';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
@ -230,6 +232,34 @@ const routes: RouteRecordRaw[] = [
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'rh',
|
||||
name: 'RH',
|
||||
meta: {
|
||||
title: 'Recursos Humanos',
|
||||
requiresAuth: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'departments',
|
||||
name: 'Departments',
|
||||
component: Departments,
|
||||
meta: {
|
||||
title: 'Gestión de Departamentos',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'positions',
|
||||
name: 'Positions',
|
||||
component: Positions,
|
||||
meta: {
|
||||
title: 'Gestión de Puestos',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user