mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 21:43:17 +00:00
128 lines
4.7 KiB
Vue
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>
|