mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 23:23:16 +00:00
142 lines
8.1 KiB
Vue
142 lines
8.1 KiB
Vue
<template>
|
|
<Dialog v-model:visible="visibleModel" :header="form.id ? 'Editar persona' : 'Alta de persona'" modal :style="{ width: '860px' }" class="worker-form-dialog">
|
|
<div class="dialog-intro">
|
|
<div><div class="section-title">{{ form.id ? displayName : "Nuevo registro de padrón" }}</div><div class="muted">La persona vive en el padrón; el proyecto es una asignación.</div></div>
|
|
<Tag v-if="form.id" value="Datos maestros" severity="secondary" />
|
|
</div>
|
|
|
|
<Tabs v-model:value="tab">
|
|
<TabList>
|
|
<Tab value="identidad"><i class="pi pi-user" /> Identidad</Tab>
|
|
<Tab value="laboral"><i class="pi pi-briefcase" /> Alta laboral</Tab>
|
|
<Tab v-if="!form.id" value="asignacion"><i class="pi pi-building" /> Asignación inicial</Tab>
|
|
</TabList>
|
|
<TabPanels>
|
|
<TabPanel value="identidad">
|
|
<Fluid>
|
|
<div class="form-grid form-grid-compact">
|
|
<FormField label="Nombre" required :error="fieldErr.first_name"><InputText v-model="form.first_name" :invalid="!!fieldErr.first_name" /></FormField>
|
|
<FormField label="Segundo nombre"><InputText v-model="form.middle_name" /></FormField>
|
|
<FormField label="Apellido paterno" required :error="fieldErr.last_name_p"><InputText v-model="form.last_name_p" :invalid="!!fieldErr.last_name_p" /></FormField>
|
|
<FormField label="Apellido materno" required :error="fieldErr.last_name_m"><InputText v-model="form.last_name_m" :invalid="!!fieldErr.last_name_m" /></FormField>
|
|
<FormField label="CURP" required :error="fieldErr.curp"><InputText v-model="form.curp" maxlength="18" class="identifier-input" :invalid="!!fieldErr.curp" @blur="validateLive" /></FormField>
|
|
<FormField label="RFC" required :error="fieldErr.rfc"><InputText v-model="form.rfc" class="identifier-input" :invalid="!!fieldErr.rfc" @blur="validateLive" /></FormField>
|
|
<FormField label="NSS" required :error="fieldErr.nss"><InputMask v-model="form.nss" mask="99999999999" :invalid="!!fieldErr.nss" @blur="validateLive" /></FormField>
|
|
<FormField label="Tipo de sangre"><Select v-model="form.blood_type" :options="bloodTypes" show-clear /></FormField>
|
|
<FormField label="Teléfono" required :error="fieldErr.phone"><InputText v-model="form.phone" :invalid="!!fieldErr.phone" /></FormField>
|
|
<FormField label="Correo" required :error="fieldErr.email"><InputText v-model="form.email" class="no-caps" :invalid="!!fieldErr.email" /></FormField>
|
|
<FormField label="Dirección" required :error="fieldErr.address" class="span-2"><InputText v-model="form.address" :invalid="!!fieldErr.address" /></FormField>
|
|
</div>
|
|
</Fluid>
|
|
</TabPanel>
|
|
|
|
<TabPanel value="laboral">
|
|
<Fluid>
|
|
<Message severity="info" :closable="false" class="mb-3">
|
|
La empresa patrón se asigna al registrar el alta IMSS. Sin alta, la persona no pertenece a un patrón.
|
|
</Message>
|
|
<div class="form-grid form-grid-compact">
|
|
<FormField label="Cargo" required :error="fieldErr.position"><InputText v-model="form.position" :invalid="!!fieldErr.position" /></FormField>
|
|
<FormField label="Riesgo / barra de gafete" required :error="fieldErr.risk_code">
|
|
<Select v-model="form.risk_code" :options="riskChoices" option-label="display" option-value="code">
|
|
<template #option="{ option }"><div class="risk-option"><span class="risk-swatch" :style="{ backgroundColor: option.color }" /><span>{{ option.display }}</span></div></template>
|
|
</Select>
|
|
</FormField>
|
|
<FormField label="Tipo de trabajo"><SelectButton v-model="form.work_type" :options="workTypes" option-label="label" option-value="value" :allow-empty="false" /></FormField>
|
|
<FormField label="Jornal diario" required :error="fieldErr.daily_wage"><InputNumber v-model="form.daily_wage" mode="currency" currency="MXN" locale="es-MX" :min="0" /></FormField>
|
|
<FormField label="Gafete">
|
|
<SelectButton
|
|
:model-value="form.needs_badge ? 'si' : 'no'"
|
|
:options="badgeOpts"
|
|
option-label="label"
|
|
option-value="value"
|
|
:allow-empty="false"
|
|
@update:model-value="(value: string) => form.needs_badge = value === 'si'"
|
|
/>
|
|
</FormField>
|
|
</div>
|
|
</Fluid>
|
|
</TabPanel>
|
|
|
|
<TabPanel v-if="!form.id" value="asignacion">
|
|
<Message severity="info" :closable="false">La asignación es opcional. Solo aparecen proyectos activos; también puede dejar a la persona en “Sin asignar”.</Message>
|
|
<FormField label="Proyecto activo" class="mt-3"><Select v-model="form.project_id" :options="projects" option-label="name" option-value="id" placeholder="Sin asignar" show-clear class="w-full" /></FormField>
|
|
</TabPanel>
|
|
</TabPanels>
|
|
</Tabs>
|
|
|
|
<Message v-if="formError" severity="error" class="mt-3" :closable="false">{{ formError }}</Message>
|
|
<template #footer>
|
|
<Button label="Cancelar" severity="secondary" text @click="visibleModel = false" />
|
|
<Button label="Guardar" icon="pi pi-check" :loading="saving" @click="submit" />
|
|
</template>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { CompanyOption, ProjectOption, RiskOption, Worker, WorkerForm } from "~/types/padron";
|
|
import { emptyWorkerForm } from "~/types/padron";
|
|
import FormField from "./FormField.vue";
|
|
|
|
const props = defineProps<{
|
|
visible: boolean;
|
|
worker: Worker | null;
|
|
projects: ProjectOption[];
|
|
risks: RiskOption[];
|
|
companies: CompanyOption[];
|
|
saving?: boolean;
|
|
error?: string;
|
|
}>();
|
|
const emit = defineEmits<{ "update:visible": [value: boolean]; save: [form: WorkerForm] }>();
|
|
const { api } = useApi();
|
|
const { riskLevelLabel } = useUi();
|
|
const visibleModel = computed({ get: () => props.visible, set: (value) => emit("update:visible", value) });
|
|
const form = reactive<WorkerForm>(emptyWorkerForm());
|
|
const fieldErr = ref<Record<string, string>>({});
|
|
const liveError = ref("");
|
|
const tab = ref("identidad");
|
|
const formError = computed(() => props.error || liveError.value);
|
|
const displayName = computed(() => [form.first_name, form.middle_name, form.last_name_p, form.last_name_m].filter(Boolean).join(" "));
|
|
|
|
const workTypes = [{ label: "Normal", value: "N" }, { label: "Destajo", value: "D" }];
|
|
const badgeOpts = [{ label: "Sí lleva", value: "si" }, { label: "No lleva", value: "no" }];
|
|
const bloodTypes = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"];
|
|
const riskChoices = computed(() => props.risks.map((risk) => ({ ...risk, display: `${riskLevelLabel(risk.code)} · ${risk.label}` })));
|
|
|
|
async function validateLive() {
|
|
try {
|
|
const result = await api<{ errors: Record<string, string> }>("/v1/workers/validate", { method: "POST", json: { ...form, id: form.id || undefined } });
|
|
fieldErr.value = result.errors || {};
|
|
liveError.value = "";
|
|
} catch (error) {
|
|
liveError.value = error instanceof Error ? error.message : "No se pudo validar";
|
|
}
|
|
}
|
|
async function submit() {
|
|
await validateLive();
|
|
if (Object.keys(fieldErr.value).length) {
|
|
tab.value = ["first_name", "last_name_p", "last_name_m", "curp", "rfc", "nss", "phone", "email", "address"].some((key) => fieldErr.value[key]) ? "identidad" : "laboral";
|
|
liveError.value = "Revise los campos marcados.";
|
|
return;
|
|
}
|
|
emit("save", { ...form });
|
|
}
|
|
function hydrate() {
|
|
Object.assign(form, emptyWorkerForm());
|
|
if (props.worker) {
|
|
Object.assign(form, props.worker, {
|
|
middle_name: props.worker.middle_name || "",
|
|
blood_type: props.worker.blood_type || "",
|
|
needs_badge: Boolean(props.worker.needs_badge),
|
|
project_id: null,
|
|
});
|
|
} else {
|
|
form.hire_type = "";
|
|
}
|
|
if (!props.risks.some((risk) => risk.code === form.risk_code) && props.risks[0]) form.risk_code = props.risks[0].code;
|
|
fieldErr.value = {};
|
|
liveError.value = "";
|
|
tab.value = "identidad";
|
|
}
|
|
watch(() => [props.visible, props.worker?.id, props.risks.length, props.companies.length], () => { if (props.visible) hydrate(); });
|
|
</script>
|