1055 lines
30 KiB
Vue
1055 lines
30 KiB
Vue
<script setup>
|
|
import { ref, computed, onMounted, watch } from "vue";
|
|
import { useNuxtApp } from "#app";
|
|
import { useStore } from "@/stores/index";
|
|
|
|
const props = defineProps({
|
|
row: Object,
|
|
});
|
|
|
|
const {
|
|
$buildFileUrl,
|
|
$id,
|
|
$formatFileSize,
|
|
$getdata,
|
|
$getpath,
|
|
$getapi,
|
|
$findapi,
|
|
$snackbar,
|
|
$exportpdf,
|
|
} = useNuxtApp();
|
|
const store = useStore();
|
|
const product = props.row || {};
|
|
const tab = ref("info");
|
|
const documentSubTab = ref("product");
|
|
const imageSubTab = ref("product");
|
|
const record = ref(null);
|
|
const docid = $id();
|
|
const docid1 = $id();
|
|
const showmodal = ref();
|
|
|
|
const lang = computed(() => store.lang);
|
|
const isVietnamese = computed(() => lang.value === "vi");
|
|
|
|
const imageFiles = ref([]);
|
|
const projectImages = ref([]);
|
|
const documents = ref([]);
|
|
const projectDocuments = ref([]);
|
|
const viewingImage = ref(null);
|
|
const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"];
|
|
const documentExtensions = [
|
|
"pdf",
|
|
"doc",
|
|
"docx",
|
|
"xls",
|
|
"xlsx",
|
|
"ppt",
|
|
"pptx",
|
|
"txt",
|
|
"csv",
|
|
"zip",
|
|
"rar",
|
|
];
|
|
|
|
const policies = ref([]);
|
|
const selectedPolicy = ref(null);
|
|
const allPaymentPlans = ref([]);
|
|
const paymentPlans = ref([]);
|
|
const activeTab = ref(null);
|
|
const isLoadingPolicies = ref(false);
|
|
const associatedTransaction = ref(null); // New ref for associated transaction
|
|
|
|
const originPrice = computed(() => Number(record.value?.origin_price || 0));
|
|
const remainingPrice = computed(() => {
|
|
if (!selectedPolicy.value) return originPrice.value;
|
|
return originPrice.value - Number(selectedPolicy.value.deposit || 0);
|
|
});
|
|
|
|
async function loadAllPolicyData() {
|
|
if (!record.value || !record.value.id) {
|
|
policies.value = [];
|
|
selectedPolicy.value = null;
|
|
paymentPlans.value = [];
|
|
return;
|
|
}
|
|
|
|
isLoadingPolicies.value = true;
|
|
try {
|
|
const policiesData = await $getdata("salepolicy", {}, undefined, false);
|
|
const plansData = await $getdata("paymentplan", {}, undefined, false);
|
|
|
|
policies.value = policiesData || [];
|
|
allPaymentPlans.value = plansData
|
|
? plansData.sort((a, b) => a.cycle - b.cycle)
|
|
: [];
|
|
|
|
if (policies.value.length > 0) {
|
|
loadPlansForPolicy(policies.value[0].id);
|
|
} else {
|
|
selectedPolicy.value = null;
|
|
activeTab.value = null;
|
|
paymentPlans.value = [];
|
|
}
|
|
} catch (error) {
|
|
console.error("Error loading policy data:", error);
|
|
} finally {
|
|
isLoadingPolicies.value = false;
|
|
}
|
|
}
|
|
|
|
function loadPlansForPolicy(id) {
|
|
const policy = policies.value.find((pol) => pol.id === id);
|
|
if (!policy) return;
|
|
|
|
selectedPolicy.value = policy;
|
|
paymentPlans.value = allPaymentPlans.value.filter(
|
|
(plan) => plan.policy === id
|
|
);
|
|
activeTab.value = id;
|
|
}
|
|
|
|
function printContent() {
|
|
if (!record.value || !selectedPolicy.value) {
|
|
$snackbar(
|
|
isVietnamese.value
|
|
? "Vui lòng chọn chính sách"
|
|
: "Please select a policy",
|
|
{ type: "is-warning" }
|
|
);
|
|
return;
|
|
}
|
|
|
|
const docId = 'print-area';
|
|
const fileName = `${selectedPolicy.value?.name || "Payment Schedule"} - ${
|
|
record.value?.code
|
|
}`;
|
|
|
|
if (typeof $exportpdf === "function") {
|
|
$exportpdf(docId, fileName);
|
|
$snackbar(isVietnamese.value ? "Đang xuất PDF..." : "Exporting PDF...", {
|
|
type: "is-info",
|
|
});
|
|
} else {
|
|
console.warn("$exportpdf function is not available in nuxtApp.");
|
|
}
|
|
}
|
|
|
|
function getFileExtension(fileName) {
|
|
if (!fileName || typeof fileName !== "string") return "";
|
|
const parts = fileName.split(".");
|
|
return parts.length > 1 ? parts.pop().toLowerCase() : "";
|
|
}
|
|
|
|
function changeTab(tabCode) {
|
|
if (tab.value === tabCode) return;
|
|
tab.value = tabCode;
|
|
if (tabCode === "document") {
|
|
documentSubTab.value = "product";
|
|
}
|
|
if (tabCode === "image") {
|
|
imageSubTab.value = "product";
|
|
}
|
|
}
|
|
|
|
function openEditModal() {
|
|
if (!record.value?.id) return;
|
|
showmodal.value = {
|
|
component: "product/ProductEdit",
|
|
title: "Chỉnh sửa sản phẩm",
|
|
width: "60%",
|
|
height: "50%",
|
|
vbind: {
|
|
row: { ...record.value },
|
|
},
|
|
};
|
|
}
|
|
|
|
function viewImage(image) {
|
|
viewingImage.value = image;
|
|
}
|
|
|
|
function closeViewImage() {
|
|
viewingImage.value = null;
|
|
}
|
|
|
|
async function loadProductFiles() {
|
|
if (!record.value?.id) return;
|
|
try {
|
|
const links = await $getdata("productfile", { product: record.value.id });
|
|
if (!links || links.length === 0) {
|
|
imageFiles.value = [];
|
|
documents.value = [];
|
|
return;
|
|
}
|
|
|
|
const files = await Promise.all(
|
|
links.map(async (link) => {
|
|
const fileData = await $getdata(
|
|
"file",
|
|
{ id: link.file },
|
|
undefined,
|
|
true
|
|
).catch((error) => {
|
|
console.error(`Error loading file ${link.file}:`, error);
|
|
return null;
|
|
});
|
|
return {
|
|
id: link.id,
|
|
name: fileData?.name || fileData?.file || `Tệp ${link.file}`,
|
|
file: fileData?.file,
|
|
size: fileData?.size ? $formatFileSize(Number(fileData.size)) : "",
|
|
uploaded_by: fileData?.creator__fullname || "Admin",
|
|
doc_type: fileData?.doc_type || fileData?.doc_type__code,
|
|
caption: fileData?.caption || "",
|
|
create_time: link.create_time,
|
|
};
|
|
})
|
|
);
|
|
|
|
const imageList = [];
|
|
const docList = [];
|
|
|
|
files.forEach((file) => {
|
|
const extension = getFileExtension(file.file);
|
|
const fileEntry = {
|
|
id: file.id,
|
|
name: file.name,
|
|
caption: file.caption || "",
|
|
url: $buildFileUrl(file.file),
|
|
file: file.file,
|
|
size: file.size,
|
|
uploaded_by: file.uploaded_by,
|
|
uploaded_at: file.create_time || "",
|
|
};
|
|
|
|
if (imageExtensions.includes(extension)) {
|
|
imageList.push(fileEntry);
|
|
} else if (documentExtensions.includes(extension) || extension) {
|
|
docList.push(fileEntry);
|
|
} else {
|
|
docList.push(fileEntry);
|
|
}
|
|
});
|
|
|
|
imageFiles.value = imageList;
|
|
documents.value = docList;
|
|
} catch (err) {
|
|
console.error("Error loading product files:", err);
|
|
}
|
|
}
|
|
|
|
async function loadProjectFiles() {
|
|
if (!record.value?.project) {
|
|
projectImages.value = [];
|
|
projectDocuments.value = [];
|
|
return;
|
|
}
|
|
try {
|
|
const links = await $getdata("projectfile", {
|
|
project: record.value.project,
|
|
});
|
|
if (!links || links.length === 0) {
|
|
projectImages.value = [];
|
|
projectDocuments.value = [];
|
|
return;
|
|
}
|
|
|
|
const files = await Promise.all(
|
|
links.map(async (link) => {
|
|
const fileData = await $getdata(
|
|
"file",
|
|
{ id: link.file },
|
|
undefined,
|
|
true
|
|
).catch((error) => {
|
|
console.error(`Error loading project file ${link.file}:`, error);
|
|
return null;
|
|
});
|
|
return {
|
|
link,
|
|
fileData,
|
|
};
|
|
})
|
|
);
|
|
|
|
const validFiles = files.filter((entry) => entry.fileData);
|
|
|
|
const allowedDocExtensions = ["pdf", "doc", "docx"];
|
|
const imageList = [];
|
|
const docList = [];
|
|
|
|
validFiles.forEach((entry) => {
|
|
const ext = getFileExtension(entry.fileData?.file);
|
|
const fileEntry = {
|
|
id: entry.link.id,
|
|
name: entry.fileData.name || entry.fileData.file || "File",
|
|
caption: entry.fileData.caption || "",
|
|
url: $buildFileUrl(entry.fileData.file),
|
|
file: entry.fileData.file,
|
|
size: entry.fileData.size
|
|
? $formatFileSize(Number(entry.fileData.size))
|
|
: "",
|
|
uploaded_by: entry.fileData.creator__fullname || "Admin",
|
|
uploaded_at: entry.fileData.create_time || entry.link.create_time || "",
|
|
};
|
|
|
|
if (imageExtensions.includes(ext)) {
|
|
imageList.push(fileEntry);
|
|
} else if (allowedDocExtensions.includes(ext)) {
|
|
docList.push(fileEntry);
|
|
}
|
|
});
|
|
|
|
projectImages.value = imageList;
|
|
projectDocuments.value = docList;
|
|
} catch (err) {
|
|
console.error("Error loading project files:", err);
|
|
projectImages.value = [];
|
|
projectDocuments.value = [];
|
|
}
|
|
}
|
|
async function handleModalEvent(event) {
|
|
if (event.name === "dataevent" && event.data) {
|
|
const updatedData = event.data;
|
|
if (updatedData.id && updatedData.id === record.value?.id) {
|
|
record.value = { ...record.value, ...updatedData };
|
|
await loadFullData();
|
|
}
|
|
}
|
|
}
|
|
|
|
function findFieldName(code) {
|
|
const data = store.common;
|
|
const field = data.find((v) => v.code === code);
|
|
return field || {};
|
|
}
|
|
|
|
async function loadLookupNames() {
|
|
if (!record.value) return;
|
|
|
|
try {
|
|
const basePath = $getpath();
|
|
const promises = [];
|
|
|
|
if (record.value.status && !record.value.status__name) {
|
|
promises.push(
|
|
$getdata("productstatus", { id: record.value.status }, undefined, true)
|
|
.then((data) => {
|
|
if (data && data.name) {
|
|
record.value.status__name = data.name;
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
if (record.value.type && !record.value.type__name) {
|
|
promises.push(
|
|
$getdata("producttype", { id: record.value.type }, undefined, true)
|
|
.then((data) => {
|
|
if (data && data.name) {
|
|
record.value.type__name = data.name;
|
|
record.value.type__en = data.name_en || data.en || data.name;
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
if (record.value.direction && !record.value.direction__name) {
|
|
promises.push(
|
|
$getdata("direction", { id: record.value.direction }, undefined, true)
|
|
.then((data) => {
|
|
if (data && data.name) {
|
|
record.value.direction__name = data.name;
|
|
record.value.direction__en = data.name_en || data.en || data.name;
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
if (record.value.zone_type && !record.value.zone_type__name) {
|
|
promises.push(
|
|
$getdata("zonetype", { id: record.value.zone_type }, undefined, true)
|
|
.then((data) => {
|
|
if (data && data.name) {
|
|
record.value.zone_type__name = data.name;
|
|
record.value.zone_type__en = data.name_en || data.en || data.name;
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
if (record.value.project && !record.value.project__name) {
|
|
promises.push(
|
|
(async () => {
|
|
try {
|
|
const found = $findapi("project");
|
|
const projectApi = {
|
|
name: "project",
|
|
url: `${found.url_detail}${record.value.project}/`,
|
|
params: found.params || {},
|
|
};
|
|
const rs = await $getapi([projectApi]);
|
|
const data = rs[0]?.data;
|
|
if (data) {
|
|
const projectName =
|
|
data.name || data.fullname || data.code || data.title;
|
|
if (projectName) {
|
|
record.value.project__name = projectName;
|
|
record.value.project__fullname =
|
|
data.fullname || data.name || projectName;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(
|
|
`Failed to load project for id ${record.value.project}:`,
|
|
err
|
|
);
|
|
}
|
|
})()
|
|
);
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
} catch (error) {
|
|
console.error("Error loading lookup names:", error);
|
|
}
|
|
}
|
|
|
|
async function loadFullData() {
|
|
if (!props.row?.id) return;
|
|
try {
|
|
const fullData = await $getdata(
|
|
"product",
|
|
{ id: props.row.id },
|
|
undefined,
|
|
true
|
|
);
|
|
record.value = fullData;
|
|
|
|
await loadLookupNames();
|
|
await loadProductFiles();
|
|
await loadProjectFiles();
|
|
await loadAllPolicyData();
|
|
|
|
|
|
} catch (error) {
|
|
console.error("Error loading full product data:", error);
|
|
record.value = props.row ? { ...props.row } : null;
|
|
if (record.value) {
|
|
await loadLookupNames();
|
|
await loadAllPolicyData();
|
|
associatedTransaction.value = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.row?.id,
|
|
async (newId) => {
|
|
if (newId) {
|
|
await loadFullData();
|
|
}
|
|
},
|
|
{ immediate: false }
|
|
);
|
|
|
|
watch(
|
|
() => record.value?.project,
|
|
async (newProjectId) => {
|
|
if (newProjectId) {
|
|
await loadProjectFiles();
|
|
} else {
|
|
projectImages.value = [];
|
|
projectDocuments.value = [];
|
|
}
|
|
},
|
|
{ immediate: false }
|
|
);
|
|
|
|
onMounted(async () => {
|
|
await loadFullData();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="record" class="px-5">
|
|
<div class="tabs is-toggle is-fullwidth mb-4">
|
|
<ul>
|
|
<li :class="{ 'is-active': tab === 'info' }">
|
|
<a @click="changeTab('info')">
|
|
<span class="has-text-weight-bold">Thông tin</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li :class="{ 'is-active': tab === 'image' }">
|
|
<a @click="changeTab('image')">
|
|
<span class="has-text-weight-bold">Hình ảnh</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li :class="{ 'is-active': tab === 'document' }">
|
|
<a @click="changeTab('document')">
|
|
<span class="has-text-weight-bold">Tài liệu</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li :class="{ 'is-active': tab === 'financial-policy' }">
|
|
<a @click="changeTab('financial-policy')">
|
|
<span class="has-text-weight-bold">Chính sách tài chính</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="">
|
|
<div v-if="tab === 'info' && record" :id="docid">
|
|
<TransactionView
|
|
pagename="ProductView"
|
|
:transactionId="product.txnprd"
|
|
:productId="product.id"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Tab Hình ảnh -->
|
|
<div v-if="tab === 'image' && record">
|
|
<div class="columns is-gapless">
|
|
<!-- Tab con bên trái -->
|
|
<div class="column is-2">
|
|
<div class="vertical-tabs">
|
|
<ul>
|
|
<li
|
|
:class="{ 'is-active': imageSubTab === 'product' }"
|
|
@click="imageSubTab = 'product'"
|
|
>
|
|
<a>
|
|
<span class="has-text-weight-bold">Hình ảnh sản phẩm</span>
|
|
</a>
|
|
</li>
|
|
<li
|
|
:class="{ 'is-active': imageSubTab === 'project' }"
|
|
@click="imageSubTab = 'project'"
|
|
>
|
|
<a>
|
|
<span class="has-text-weight-bold">Hình ảnh dự án</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nội dung bên phải -->
|
|
<div class="column pl-4">
|
|
<!-- Hình ảnh sản phẩm -->
|
|
<div v-if="imageSubTab === 'product'">
|
|
<div
|
|
v-if="imageFiles.length > 0"
|
|
class="columns is-multiline px-2 mt-0"
|
|
>
|
|
<div
|
|
class="column is-one-quarter p-2"
|
|
v-for="image in imageFiles"
|
|
:key="image.id"
|
|
>
|
|
<div class="card image-card">
|
|
<div
|
|
class="card-image"
|
|
style="cursor: pointer"
|
|
@click="viewImage(image)"
|
|
>
|
|
<figure class="image is-4by3">
|
|
<img
|
|
:src="image.url"
|
|
:alt="image.caption || image.name"
|
|
/>
|
|
</figure>
|
|
</div>
|
|
<div class="card-content p-2">
|
|
<div class="image-meta">
|
|
<p
|
|
class="has-text-weight-semibold is-size-7 is-clipped"
|
|
>
|
|
{{ image.name }}
|
|
</p>
|
|
<p class="is-size-7 is-clipped">
|
|
{{ image.caption }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="has-text-centered py-4">
|
|
<p class="">Chưa có hình ảnh nào được tải lên.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hình ảnh dự án -->
|
|
<div v-if="imageSubTab === 'project'">
|
|
<div
|
|
v-if="projectImages.length > 0"
|
|
class="columns is-multiline px-2 mt-0"
|
|
>
|
|
<div
|
|
class="column is-one-quarter p-2"
|
|
v-for="image in projectImages"
|
|
:key="image.id"
|
|
>
|
|
<div class="card image-card">
|
|
<div
|
|
class="card-image"
|
|
style="cursor: pointer"
|
|
@click="viewImage(image)"
|
|
>
|
|
<figure class="image is-4by3">
|
|
<img
|
|
:src="image.url"
|
|
:alt="image.caption || image.name"
|
|
/>
|
|
</figure>
|
|
</div>
|
|
<div class="card-content p-2">
|
|
<div class="image-meta">
|
|
<p
|
|
class="has-text-weight-semibold is-size-7 is-clipped"
|
|
>
|
|
{{ image.name }}
|
|
</p>
|
|
<p class="is-size-7 is-clipped">
|
|
{{ image.caption }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="has-text-centered py-4">
|
|
<p class="">Chưa có hình ảnh dự án nào được tải lên.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal xem ảnh lớn -->
|
|
<div
|
|
v-if="viewingImage"
|
|
class="modal is-active"
|
|
@click.self="closeViewImage"
|
|
>
|
|
<div class="modal-background"></div>
|
|
<div class="modal-content" style="width: 90vw; height: 90vh">
|
|
<figure
|
|
class="image"
|
|
style="
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
>
|
|
<img
|
|
:src="viewingImage.url"
|
|
:alt="viewingImage.caption || viewingImage.name || 'Hình ảnh'"
|
|
style="max-width: 100%; max-height: 90vh; object-fit: contain"
|
|
/>
|
|
</figure>
|
|
</div>
|
|
<button
|
|
class="modal-close is-large"
|
|
aria-label="close"
|
|
@click="closeViewImage"
|
|
></button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Tài liệu -->
|
|
<div v-if="tab === 'document' && record">
|
|
<div class="columns is-gapless">
|
|
<!-- Tab con bên trái -->
|
|
<div class="column is-2">
|
|
<div class="vertical-tabs">
|
|
<ul>
|
|
<li
|
|
:class="{ 'is-active': documentSubTab === 'product' }"
|
|
@click="documentSubTab = 'product'"
|
|
>
|
|
<a>
|
|
<span class="has-text-weight-bold">Tài liệu sản phẩm</span>
|
|
</a>
|
|
</li>
|
|
<li
|
|
:class="{ 'is-active': documentSubTab === 'project' }"
|
|
@click="documentSubTab = 'project'"
|
|
>
|
|
<a>
|
|
<span class="has-text-weight-bold">Tài liệu dự án</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nội dung bên phải -->
|
|
<div class="column pl-4">
|
|
<!-- Tài liệu sản phẩm -->
|
|
<div v-if="documentSubTab === 'product'">
|
|
<div class="table-container">
|
|
<table class="table is-fullwidth is-striped">
|
|
<thead>
|
|
<tr>
|
|
<th class="">File</th>
|
|
<th class="">Mô tả</th>
|
|
<th class="">Người tải lên</th>
|
|
<th class="">Tải xuống</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="doc in documents" :key="doc.id">
|
|
<td>
|
|
<div class="is-flex is-align-items-center">
|
|
<span class="icon is-medium has-text-info mr-2">
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: 'attach-file.svg',
|
|
type: 'primary',
|
|
size: 20,
|
|
}"
|
|
></SvgIcon>
|
|
</span>
|
|
<div>
|
|
<p class="has-text-weight-semibold">
|
|
{{ doc.name }}
|
|
</p>
|
|
<p class="is-size-7 has-text-grey">
|
|
{{ doc.size }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="is-size-7">{{ doc.caption || "-" }}</span>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<p class="is-size-7 has-text-weight-semibold">
|
|
{{ doc.uploaded_by }}
|
|
</p>
|
|
<p class="is-size-7 has-text-grey">
|
|
{{
|
|
doc.uploaded_at
|
|
? new Date(doc.uploaded_at).toLocaleString(
|
|
"vi-VN"
|
|
)
|
|
: "-"
|
|
}}
|
|
</p>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="buttons is-justify-content-center">
|
|
<a
|
|
class="button is-small is-info is-outlined"
|
|
:href="doc.url"
|
|
target="_blank"
|
|
title="Tải xuống"
|
|
>
|
|
<span class="icon is-small"
|
|
><SvgIcon
|
|
v-bind="{
|
|
name: 'download.svg',
|
|
type: 'primary',
|
|
size: 14,
|
|
}"
|
|
></SvgIcon
|
|
></span>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="documents.length === 0">
|
|
<td colspan="4" class="has-text-centered py-4">
|
|
<p class="has-text-grey">
|
|
Chưa có tài liệu nào được đính kèm.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tài liệu dự án -->
|
|
<div v-if="documentSubTab === 'project'">
|
|
<div class="table-container">
|
|
<table class="table is-fullwidth is-striped">
|
|
<thead>
|
|
<tr>
|
|
<th class="">File</th>
|
|
<th class="">Mô tả</th>
|
|
<th class="">Người tải lên</th>
|
|
<th class="">Chức năng</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="doc in projectDocuments" :key="doc.id">
|
|
<td>
|
|
<div class="is-flex is-align-items-center">
|
|
<span class="icon is-medium has-text-info mr-2">
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: 'attach-file.svg',
|
|
type: 'primary',
|
|
size: 20,
|
|
}"
|
|
></SvgIcon>
|
|
</span>
|
|
<div>
|
|
<p class="has-text-weight-semibold">
|
|
{{ doc.name }}
|
|
</p>
|
|
<p class="is-size-7 has-text-grey">
|
|
{{ doc.size }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="is-size-7">{{ doc.caption || "-" }}</span>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<p class="is-size-7 has-text-weight-semibold">
|
|
{{ doc.uploaded_by }}
|
|
</p>
|
|
<p class="is-size-7 has-text-grey">
|
|
{{
|
|
doc.uploaded_at
|
|
? new Date(doc.uploaded_at).toLocaleString(
|
|
"vi-VN"
|
|
)
|
|
: "-"
|
|
}}
|
|
</p>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="buttons is-justify-content-center">
|
|
<a
|
|
class="button is-small is-info is-outlined"
|
|
:href="doc.url"
|
|
target="_blank"
|
|
title="Tải xuống"
|
|
>
|
|
<span class="icon is-small"
|
|
><SvgIcon
|
|
v-bind="{
|
|
name: 'download.svg',
|
|
type: 'primary',
|
|
size: 14,
|
|
}"
|
|
></SvgIcon
|
|
></span>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="projectDocuments.length === 0">
|
|
<td colspan="4" class="has-text-centered py-4">
|
|
<p class="has-text-grey">
|
|
Chưa có tài liệu dự án nào được đính kèm.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Chính sách tài chính -->
|
|
<div v-if="tab === 'financial-policy' && record" class="py-3">
|
|
<div v-if="isLoadingPolicies" class="has-text-centered">
|
|
<p class="has-text-info is-italic">Đang tải chính sách...</p>
|
|
</div>
|
|
<PaymentSchedule v-else
|
|
:productData="record"
|
|
:policies="policies"
|
|
:activeTab="activeTab"
|
|
:selectedPolicy="selectedPolicy"
|
|
:isVietnamese="isVietnamese"
|
|
:originPrice="originPrice"
|
|
:discountValueDisplay="0"
|
|
:priceAfterDiscount="remainingPrice"
|
|
:selectedCustomer="null"
|
|
:detailedDiscounts="[]"
|
|
:paymentPlans="paymentPlans"
|
|
@policy-selected="loadPlansForPolicy"
|
|
@print="printContent"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Modal
|
|
@close="showmodal = undefined"
|
|
@modalevent="handleModalEvent"
|
|
v-bind="showmodal"
|
|
v-if="showmodal"
|
|
></Modal>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.table-container {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.table-container table {
|
|
table-layout: fixed;
|
|
}
|
|
|
|
.table th {
|
|
background-color: #f5f5f5;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.table td {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.table th:nth-child(1),
|
|
.table td:nth-child(1) {
|
|
width: 30%;
|
|
min-width: 200px;
|
|
}
|
|
|
|
.table th:nth-child(2),
|
|
.table td:nth-child(2) {
|
|
width: 25%;
|
|
min-width: 150px;
|
|
}
|
|
|
|
.table th:nth-child(3),
|
|
.table td:nth-child(3) {
|
|
width: 25%;
|
|
min-width: 180px;
|
|
}
|
|
|
|
.table th:nth-child(4),
|
|
.table td:nth-child(4) {
|
|
width: 20%;
|
|
min-width: 150px;
|
|
text-align: center;
|
|
}
|
|
|
|
.table td:nth-child(4) .buttons {
|
|
justify-content: center;
|
|
display: flex;
|
|
}
|
|
|
|
.vertical-tabs {
|
|
border-right: 1px solid #dbdbdb;
|
|
padding-right: 5px;
|
|
margin-right: 1rem;
|
|
min-height: 200px;
|
|
}
|
|
|
|
.vertical-tabs ul {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.vertical-tabs li {
|
|
margin-bottom: 0.5rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.vertical-tabs li a {
|
|
display: block;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 4px;
|
|
transition: all 0.3s ease;
|
|
color: #4a4a4a;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.vertical-tabs li a:hover {
|
|
background-color: #f5f5f5;
|
|
color: #204853;
|
|
}
|
|
|
|
.vertical-tabs li.is-active a {
|
|
background-color: #e8f4fd;
|
|
color: #204853;
|
|
border-left-color: #204853;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.image-card {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.image-card .card-image {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.image-card .card-content {
|
|
flex-grow: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.image-card .image-meta {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.document-card {
|
|
border-bottom: 1px solid #e0e0e0;
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
.tabs .icon:first-child {
|
|
margin-right: 0.2rem;
|
|
margin-bottom: 0.1rem;
|
|
}
|
|
|
|
li.is-active a,
|
|
li a:hover {
|
|
color: white !important;
|
|
background-color: #204853 !important;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
li.is-active a .icon :deep(img),
|
|
li a:hover .icon :deep(img) {
|
|
filter: brightness(0) invert(1) !important;
|
|
}
|
|
|
|
.image-card {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.image-card .card-image {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.image-card .card-content {
|
|
flex-grow: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.image-card .image-meta {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.document-card {
|
|
border-bottom: 1px solid #e0e0e0;
|
|
padding-bottom: 1rem;
|
|
}
|
|
</style> |