mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 10:43:18 +00:00
152 lines
5.5 KiB
Vue
152 lines
5.5 KiB
Vue
<template>
|
|
<div class="detail-section">
|
|
<div class="document-progress">
|
|
<div>
|
|
<div class="section-title">Expediente de empresa</div>
|
|
<div class="muted">{{ currentDocuments.length }} documentos guardados. Nada de esto es obligatorio.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Fieldset legend="Subir o reemplazar documento" class="mt-3">
|
|
<FormField label="Tipo de documento">
|
|
<Select
|
|
v-model="documentType"
|
|
:options="groupedTypes"
|
|
option-label="label"
|
|
option-value="code"
|
|
option-group-label="label"
|
|
option-group-children="items"
|
|
class="w-full"
|
|
/>
|
|
</FormField>
|
|
<FileUpload
|
|
mode="basic"
|
|
:auto="true"
|
|
choose-label="Seleccionar archivo"
|
|
accept=".pdf,.xlsx,.xls,.doc,.docx,.jpg,.jpeg,.png"
|
|
custom-upload
|
|
class="mt-2"
|
|
:disabled="uploading"
|
|
@uploader="uploadDocument"
|
|
/>
|
|
<div class="muted mt-2">REPSE, opiniones SAT/IMSS o actas se guardan aquí si la empresa los tiene.</div>
|
|
</Fieldset>
|
|
|
|
<Tabs v-model:value="docsTab" class="mt-3 document-list-tabs">
|
|
<TabList>
|
|
<Tab value="current">Vigentes ({{ currentDocuments.length }})</Tab>
|
|
<Tab value="history">Histórico ({{ historyDocuments.length }})</Tab>
|
|
</TabList>
|
|
<TabPanels>
|
|
<TabPanel value="current">
|
|
<DataTable :value="currentDocuments" size="small" empty-message="Aún no hay documentos">
|
|
<Column header="Tipo">
|
|
<template #body="{ data }">{{ typeLabel(data.type_code) }}</template>
|
|
</Column>
|
|
<Column field="original_name" header="Archivo" />
|
|
<Column header="">
|
|
<template #body="{ data }">
|
|
<Button icon="pi pi-eye" text rounded size="small" aria-label="Ver documento" @click="openDocument(data)" />
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</TabPanel>
|
|
<TabPanel value="history">
|
|
<DataTable :value="historyDocuments" size="small" empty-message="Sin versiones anteriores">
|
|
<Column header="Tipo">
|
|
<template #body="{ data }">{{ typeLabel(data.type_code) }}</template>
|
|
</Column>
|
|
<Column field="original_name" header="Archivo" />
|
|
<Column header="Fecha">
|
|
<template #body="{ data }">{{ formatDate(data.uploaded_at) }}</template>
|
|
</Column>
|
|
<Column header="">
|
|
<template #body="{ data }">
|
|
<Button icon="pi pi-eye" text rounded size="small" aria-label="Ver documento" @click="openDocument(data)" />
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</TabPanel>
|
|
</TabPanels>
|
|
</Tabs>
|
|
|
|
<DocumentPreviewDialog
|
|
v-model="previewOpen"
|
|
:path="previewPath"
|
|
:name="previewName"
|
|
:mime="previewMime"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import FormField from "~/components/padron/FormField.vue";
|
|
import DocumentPreviewDialog from "~/components/DocumentPreviewDialog.vue";
|
|
import { groupedDocumentOptions } from "~/types/padron";
|
|
|
|
type ChecklistItem = { code: string; label: string; required: boolean; present: boolean; category?: string };
|
|
type DocumentRow = {
|
|
id: number;
|
|
type_code: string;
|
|
original_name: string;
|
|
uploaded_at: string;
|
|
is_current: number;
|
|
mime?: string;
|
|
};
|
|
const props = defineProps<{ companyId: number; documents: DocumentRow[]; checklist: ChecklistItem[] }>();
|
|
const emit = defineEmits<{ changed: [] }>();
|
|
const { api } = useApi();
|
|
const toast = useToast();
|
|
const documentType = ref(props.checklist[0]?.code || "repse_federal");
|
|
const uploading = ref(false);
|
|
const docsTab = ref("current");
|
|
const previewOpen = ref(false);
|
|
const previewPath = ref("");
|
|
const previewName = ref("");
|
|
const previewMime = ref<string | null>(null);
|
|
const groupedTypes = computed(() => groupedDocumentOptions(props.checklist));
|
|
const currentDocuments = computed(() => props.documents.filter((doc) => doc.is_current));
|
|
const historyDocuments = computed(() => props.documents.filter((doc) => !doc.is_current));
|
|
|
|
function typeLabel(code: string) {
|
|
return props.checklist.find((item) => item.code === code)?.label || code;
|
|
}
|
|
function formatDate(value: string) {
|
|
if (!value) return "—";
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return value;
|
|
return date.toLocaleDateString("es-MX", { day: "2-digit", month: "short", year: "numeric" });
|
|
}
|
|
|
|
async function uploadDocument(event: { files: File[] }) {
|
|
const file = event.files?.[0];
|
|
if (!file || uploading.value) return;
|
|
uploading.value = true;
|
|
try {
|
|
const body = new FormData();
|
|
body.append("type", documentType.value);
|
|
body.append("file", file);
|
|
await api(`/v1/companies/${props.companyId}/documents`, { method: "POST", body });
|
|
toast.add({ severity: "success", summary: "Documento cifrado y guardado", life: 2200 });
|
|
docsTab.value = "current";
|
|
emit("changed");
|
|
} catch (error: unknown) {
|
|
toast.add({
|
|
severity: "error",
|
|
summary: error instanceof Error ? error.message : "No se pudo subir el archivo",
|
|
life: 3500,
|
|
});
|
|
} finally {
|
|
uploading.value = false;
|
|
}
|
|
}
|
|
function openDocument(row: DocumentRow) {
|
|
previewPath.value = `/v1/companies/${props.companyId}/documents/${row.id}`;
|
|
previewName.value = row.original_name;
|
|
previewMime.value = row.mime || null;
|
|
previewOpen.value = true;
|
|
}
|
|
watch(() => props.checklist, (items) => {
|
|
if (!items.some((item) => item.code === documentType.value) && items[0]) documentType.value = items[0].code;
|
|
}, { immediate: true });
|
|
</script>
|