panels-origin/web-panel/components/ProjectFicha.vue
2026-08-21 16:55:01 -05:00

128 lines
4.7 KiB
Vue

<template>
<div class="ficha">
<Message v-if="!projectId" severity="secondary" :closable="false">Seleccione un proyecto</Message>
<div v-else-if="loading && !detail" class="detail-loading"><ProgressSpinner /></div>
<div v-else-if="view" class="ficha-body">
<div class="ficha-head">
<Avatar icon="pi pi-building" shape="circle" />
<div>
<div class="toolbar-title">{{ view.name }}</div>
<div class="muted">{{ view.code }}</div>
</div>
</div>
<div class="ficha-tags">
<Tag :value="projectStatusLabel(String(view.status))" :severity="projectStatusSeverity(String(view.status))" />
<Tag v-if="view.company_name || view.company_code" :value="String(view.company_name || view.company_code)" />
<Tag :value="docsReady ? 'Expediente completo' : 'Falta expediente'" :severity="docsReady ? 'success' : 'warn'" />
</div>
<Button label="Editar registro" icon="pi pi-pencil" outlined class="w-full mt-3" @click="emit('edit')" />
<Button label="Ver presupuesto" icon="pi pi-calculator" outlined class="w-full mt-2" @click="navigateTo(`/presupuesto?project=${projectId}`)" />
<Tabs v-model:value="tab" class="mt-3 detail-tabs">
<TabList>
<Tab value="datos">Datos</Tab>
<Tab value="docs">Expediente</Tab>
</TabList>
<TabPanels>
<TabPanel value="datos">
<DataTable :value="rows" size="small">
<Column field="label" header="Campo" />
<Column field="value" header="Dato" />
</DataTable>
</TabPanel>
<TabPanel value="docs">
<ProjectDocuments
:project-id="projectId"
:documents="detail?.documents || []"
:checklist="detail?.checklist || []"
:initial-type="docType"
@changed="reload"
/>
</TabPanel>
</TabPanels>
</Tabs>
</div>
</div>
</template>
<script setup lang="ts">
import ProjectDocuments from "~/components/ProjectDocuments.vue";
type ChecklistItem = { code: string; label: string; required: boolean; present: boolean };
type DocumentRow = { id: number; type_code: string; original_name: string; uploaded_at: string; is_current: number };
type ProjectRow = Record<string, unknown> & {
id: number;
code: string;
name: string;
status: string;
};
type DetailData = {
project: ProjectRow;
documents: DocumentRow[];
checklist: ChecklistItem[];
};
const props = defineProps<{ project?: Record<string, unknown> | null; doc?: string }>();
const emit = defineEmits<{ edit: [] }>();
const { api } = useApi();
const { projectStatusLabel, projectStatusSeverity } = useUi();
const desk = useDesktop();
const tab = ref("datos");
const loading = ref(false);
const detail = ref<DetailData | null>(null);
const projectId = computed(() => Number(props.project?.id || 0) || 0);
const view = computed(() => detail.value?.project || (props.project as ProjectRow | undefined));
const docsReady = computed(() => {
const required = detail.value?.checklist.filter((item) => item.required) || [];
return required.length > 0 && required.every((item) => item.present);
});
const docType = computed(() => props.doc || "contrato");
const rows = computed(() => {
const p = view.value;
if (!p) return [];
return [
{ label: "Código", value: p.code },
{ label: "Empresa", value: p.company_name || p.company_code || "—" },
{ label: "Nombre", value: p.name },
{ label: "Domicilio", value: p.address || "—" },
{ label: "Monto contrato", value: money(p.contract_amount) },
{ label: "Inicio", value: p.start_date || "—" },
{ label: "Término", value: p.end_date || "—" },
{ label: "Residente de obra", value: p.resident_name || "—" },
{ label: "SIROC", value: p.siroc || "—" },
{ label: "Sobre nómina", value: p.payroll_tax_pct == null || p.payroll_tax_pct === "" ? "—" : `${p.payroll_tax_pct}%` },
{ label: "Tema gafete", value: p.theme_id },
{ label: "Activos", value: p.active_count ?? 0 },
];
});
function money(value: unknown) {
if (value == null || value === "") return "—";
const amount = Number(value);
if (Number.isNaN(amount)) return "—";
return new Intl.NumberFormat("es-MX", { style: "currency", currency: "MXN" }).format(amount);
}
async function load() {
if (!projectId.value) {
detail.value = null;
return;
}
loading.value = true;
try {
detail.value = await api<DetailData>(`/v1/projects/${projectId.value}`);
} finally {
loading.value = false;
}
}
function reload() {
load();
desk.bumpDetail();
}
watch(() => [projectId.value, props.doc, desk.detailRev.value] as const, ([, doc]) => {
if (doc) tab.value = "docs";
load();
}, { immediate: true });
</script>