mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 17:33:16 +00:00
110 lines
5.4 KiB
Vue
110 lines
5.4 KiB
Vue
<template>
|
|
<Dialog v-model:visible="visibleModel" header="Importar padrón desde Excel" modal :style="{ width: '820px' }">
|
|
<div class="import-layout">
|
|
<Message severity="info" :closable="false">Descargue la plantilla, complete la hoja PERSONAL y súbala. CURP, RFC y NSS se validan antes de guardar.</Message>
|
|
<div class="import-actions">
|
|
<Button label="Descargar plantilla" icon="pi pi-download" outlined :loading="templateLoading" @click="downloadTemplate" />
|
|
<FormField label="Proyecto activo opcional">
|
|
<Select v-model="projectId" :options="projects" option-label="name" option-value="id" placeholder="Sin asignación inicial" show-clear class="w-full" />
|
|
</FormField>
|
|
<FileUpload mode="basic" accept=".xlsx,.xls" :auto="true" choose-label="Seleccionar Excel" custom-upload :disabled="importing" @uploader="importFile" />
|
|
<div class="muted">Al elegir el Excel se importa solo.</div>
|
|
</div>
|
|
<ProgressBar v-if="importing" mode="indeterminate" style="height: 4px" />
|
|
|
|
<template v-if="report">
|
|
<div class="import-summary">
|
|
<div><strong>{{ report.inserted }}</strong><span>Insertados</span></div>
|
|
<div><strong>{{ report.existed }}</strong><span>Actualizados</span></div>
|
|
<div><strong>{{ report.errors }}</strong><span>Con error</span></div>
|
|
<div><strong>{{ report.photos }}</strong><span>Fotos</span></div>
|
|
</div>
|
|
<Panel v-if="report.existed_rows.length" header="Registros que ya existían" toggleable collapsed>
|
|
<DeskTableBar v-model="existedQ" placeholder="Buscar nombre o CURP">
|
|
<Chip :label="`${listedExisted.length} coincidencias`" icon="pi pi-users" />
|
|
</DeskTableBar>
|
|
<DataTable :value="listedExisted" size="small" class="mt-2">
|
|
<Column field="row" header="Fila" /><Column field="nombre" header="Nombre" /><Column field="curp" header="CURP" /><Column field="matched" header="Coincidencia" />
|
|
</DataTable>
|
|
</Panel>
|
|
<Panel v-if="report.error_rows.length" header="Errores de validación" toggleable>
|
|
<DeskTableBar v-model="errorQ" placeholder="Buscar nombre o CURP">
|
|
<Chip :label="`${listedErrors.length} errores`" icon="pi pi-exclamation-triangle" />
|
|
</DeskTableBar>
|
|
<DataTable :value="listedErrors" size="small" class="mt-2">
|
|
<Column field="row" header="Fila" /><Column field="nombre" header="Nombre" /><Column field="curp" header="CURP" />
|
|
<Column header="Detalle"><template #body="{ data }"><ul class="validation-list"><li v-for="message in data.messages" :key="message">{{ message }}</li></ul></template></Column>
|
|
</DataTable>
|
|
</Panel>
|
|
</template>
|
|
</div>
|
|
<template #footer><Button label="Cerrar" severity="secondary" text @click="visibleModel = false" /></template>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ImportReport, ProjectOption } from "~/types/padron";
|
|
import FormField from "./FormField.vue";
|
|
|
|
const props = defineProps<{ visible: boolean; projects: ProjectOption[] }>();
|
|
const emit = defineEmits<{ "update:visible": [value: boolean]; imported: [] }>();
|
|
const visibleModel = computed({ get: () => props.visible, set: (value) => emit("update:visible", value) });
|
|
const { api, download } = useApi();
|
|
const toast = useToast();
|
|
const projectId = ref<number | null>(null);
|
|
const report = ref<ImportReport | null>(null);
|
|
const templateLoading = ref(false);
|
|
const importing = ref(false);
|
|
const existedQ = ref("");
|
|
const errorQ = ref("");
|
|
const listedExisted = computed(() => {
|
|
const rows = report.value?.existed_rows || [];
|
|
const q = existedQ.value.trim().toLowerCase();
|
|
if (!q) return rows;
|
|
return rows.filter((row) => [row.row, row.nombre, row.curp, row.matched].some((part) => String(part ?? "").toLowerCase().includes(q)));
|
|
});
|
|
const listedErrors = computed(() => {
|
|
const rows = report.value?.error_rows || [];
|
|
const q = errorQ.value.trim().toLowerCase();
|
|
if (!q) return rows;
|
|
return rows.filter((row) =>
|
|
[row.row, row.nombre, row.curp, ...(row.messages || [])].some((part) => String(part ?? "").toLowerCase().includes(q)),
|
|
);
|
|
});
|
|
|
|
async function downloadTemplate() {
|
|
templateLoading.value = true;
|
|
try {
|
|
await download("/v1/workers/import/template", "plantilla-padron-arctec.xlsx");
|
|
} catch (error) {
|
|
toast.add({ severity: "error", summary: error instanceof Error ? error.message : "No se pudo descargar", life: 3000 });
|
|
} finally {
|
|
templateLoading.value = false;
|
|
}
|
|
}
|
|
async function importFile(event: { files: File[] }) {
|
|
const file = event.files?.[0];
|
|
if (!file) return;
|
|
importing.value = true;
|
|
report.value = null;
|
|
try {
|
|
const body = new FormData();
|
|
body.append("file", file);
|
|
if (projectId.value) body.append("project_id", String(projectId.value));
|
|
report.value = await api<ImportReport>("/v1/workers/import", { method: "POST", body });
|
|
toast.add({
|
|
severity: report.value.errors ? "warn" : "success",
|
|
summary: `Insertados ${report.value.inserted}, actualizados ${report.value.existed}, errores ${report.value.errors}`,
|
|
life: 4000,
|
|
});
|
|
emit("imported");
|
|
} catch (error) {
|
|
toast.add({ severity: "error", summary: error instanceof Error ? error.message : "Falló la importación", life: 3500 });
|
|
} finally {
|
|
importing.value = false;
|
|
}
|
|
}
|
|
watch(() => props.visible, (visible) => {
|
|
if (visible) { report.value = null; projectId.value = null; }
|
|
});
|
|
</script>
|