changes
This commit is contained in:
@@ -10,6 +10,10 @@
|
||||
--bulma-hr-background-color: var(--bulma-grey-90);
|
||||
}
|
||||
|
||||
.textarea,
|
||||
.input {
|
||||
box-shadow: none;
|
||||
}
|
||||
// might break lots of stuff
|
||||
// .skeleton-block:not(:last-child), .media:not(:last-child), .level:not(:last-child), .fixed-grid:not(:last-child), .grid:not(:last-child), .tabs:not(:last-child), .pagination:not(:last-child), .message:not(:last-child), .card:not(:last-child), .breadcrumb:not(:last-child), .field:not(:last-child), .file:not(:last-child), .title:not(:last-child), .subtitle:not(:last-child), .tags:not(:last-child), .table:not(:last-child), .table-container:not(:last-child), .progress:not(:last-child), .notification:not(:last-child), .content:not(:last-child), .buttons:not(:last-child), .box:not(:last-child), .block:not(:last-child) {
|
||||
// margin-bottom: inherit;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<!-- Viewer: display when click tem from another dealer -->
|
||||
<template>
|
||||
<p>Rất tiếc, bạn hiện chưa có quyền xem thông tin sản phẩm này.</p>
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<template>Inventory Count</template>
|
||||
@@ -58,7 +58,7 @@ const emit = defineEmits(["selectOrder", "unselect"]);
|
||||
<div class="is-flex is-gap-0.5">
|
||||
<Icon
|
||||
:size="18"
|
||||
name="material-symbols:calendar-month-outline-rounded"
|
||||
name="material-symbols:calendar-today-outline-rounded"
|
||||
/>
|
||||
<span>{{ $dayjs(order.create_time).format("L") }}</span>
|
||||
</div>
|
||||
|
||||
@@ -484,7 +484,7 @@ function toggleStatus(id) {
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="card is-clipped">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="is-flex is-gap-2 is-align-items-center">
|
||||
<div class="field is-flex-grow-1 m-0">
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
carts: Array,
|
||||
filters: Object,
|
||||
filterTabs: Object,
|
||||
productCount: Number,
|
||||
});
|
||||
|
||||
const store = useStore();
|
||||
const { $getdata } = useNuxtApp();
|
||||
const emit = defineEmits(["modalevent", "close"]);
|
||||
const dealers = ref([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const dealersData = await $getdata("cart", undefined, {
|
||||
values: "dealer,dealer__name",
|
||||
filter: {
|
||||
dealer__name__icontains: "",
|
||||
},
|
||||
distinct_values: {
|
||||
product_count_by_dealer: { type: "Count", field: "prdcart" },
|
||||
},
|
||||
sort: "dealer__name",
|
||||
summary: "annotate",
|
||||
});
|
||||
|
||||
dealers.value = dealersData;
|
||||
});
|
||||
|
||||
const activeFilterTab = ref(store.lastlegendfiltertab);
|
||||
|
||||
const localFilterStates = ref({
|
||||
cartIds: props.filters.cartIds,
|
||||
dealerIds: props.filters.dealerIds,
|
||||
});
|
||||
|
||||
function filterTabChange(newTab) {
|
||||
activeFilterTab.value = newTab;
|
||||
store.commit("lastlegendfiltertab", newTab);
|
||||
}
|
||||
|
||||
function updateFilters() {
|
||||
emit("close");
|
||||
emit("modalevent", { name: "updateFilters", data: localFilterStates });
|
||||
}
|
||||
|
||||
function resetCartDealerFilters() {
|
||||
emit("close");
|
||||
emit("modalevent", { name: "resetCartDealerFilters" });
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.filters,
|
||||
() => {
|
||||
localFilterStates.value = {
|
||||
cartIds: props.filters.cartIds,
|
||||
dealerIds: props.filters.dealerIds,
|
||||
};
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="fs-14">
|
||||
<div class="tabs is-boxed is-fullwidth">
|
||||
<ul class="mx-0">
|
||||
<li
|
||||
v-for="tab in filterTabs"
|
||||
:class="{ 'is-active': tab === activeFilterTab }"
|
||||
@click="
|
||||
() => {
|
||||
if (tab !== activeFilterTab) filterTabChange(tab);
|
||||
}
|
||||
"
|
||||
>
|
||||
<a
|
||||
:class="[
|
||||
{
|
||||
'has-text-weight-bold has-text-secondary': tab === activeFilterTab,
|
||||
},
|
||||
]"
|
||||
>Lọc theo {{ tab }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="activeFilterTab === filterTabs.CARTS">
|
||||
<li
|
||||
class="is-light p-2 is-flex is-align-items-center is-justify-content-space-between is-gap-2 is-clickable"
|
||||
@click="
|
||||
() => {
|
||||
// toggle all
|
||||
if (localFilterStates.cartIds.length === carts.length) {
|
||||
localFilterStates.cartIds = [];
|
||||
} else {
|
||||
localFilterStates.cartIds = carts.map((c) => c.id);
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<span class="has-text-weight-bold">Tất cả</span>
|
||||
<div class="is-flex is-gap-2 is-flex-shrink-0">
|
||||
<span class="has-text-weight-bold">{{ productCount }}</span>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name:
|
||||
localFilterStates.cartIds.length === carts.length
|
||||
? 'checked.svg'
|
||||
: localFilterStates.cartIds.length === 0
|
||||
? 'uncheck.svg'
|
||||
: 'indeterminate.svg',
|
||||
type: localFilterStates.cartIds.length > 0 ? 'primary' : 'twitter',
|
||||
size: 22,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<ul style="max-height: 420px; overflow-y: scroll">
|
||||
<li
|
||||
v-for="cart in carts"
|
||||
class="border-bottom p-2 is-flex is-align-items-center is-justify-content-space-between is-gap-2 is-clickable"
|
||||
@click="
|
||||
() => {
|
||||
const selected = localFilterStates.cartIds.includes(cart.id);
|
||||
if (selected) {
|
||||
localFilterStates.cartIds = localFilterStates.cartIds.filter((id) => id !== cart.id);
|
||||
} else {
|
||||
localFilterStates.cartIds = [...localFilterStates.cartIds, cart.id];
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<span>{{ cart.name }}</span>
|
||||
<div class="is-flex is-gap-2 is-flex-shrink-0">
|
||||
<span>{{ cart.product_count }}</span>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: localFilterStates.cartIds.includes(cart.id) ? 'checked.svg' : 'uncheck.svg',
|
||||
type: localFilterStates.cartIds.includes(cart.id) ? 'primary' : 'twitter',
|
||||
size: 22,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>
|
||||
<li
|
||||
class="is-light p-2 is-flex is-align-items-center is-justify-content-space-between is-gap-2 is-clickable"
|
||||
@click="
|
||||
() => {
|
||||
// toggle all
|
||||
if (localFilterStates.dealerIds.length === dealers.length) {
|
||||
localFilterStates.dealerIds = [];
|
||||
} else {
|
||||
localFilterStates.dealerIds = dealers.map((c) => c.id);
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<span class="has-text-weight-bold">Tất cả</span>
|
||||
<div class="is-flex is-gap-2 is-flex-shrink-0">
|
||||
<span class="has-text-weight-bold">{{ productCount }}</span>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name:
|
||||
localFilterStates.dealerIds.length === dealers.length
|
||||
? 'checked.svg'
|
||||
: localFilterStates.dealerIds.length === 0
|
||||
? 'uncheck.svg'
|
||||
: 'indeterminate.svg',
|
||||
type: localFilterStates.dealerIds.length > 0 ? 'primary' : 'twitter',
|
||||
size: 22,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<ul>
|
||||
<li
|
||||
v-for="dealer in dealers"
|
||||
class="border-bottom p-2 is-flex is-align-items-center is-justify-content-space-between is-gap-2 is-clickable"
|
||||
@click="
|
||||
() => {
|
||||
const selected = localFilterStates.dealerIds.includes(dealer.dealer);
|
||||
if (selected) {
|
||||
localFilterStates.dealerIds = localFilterStates.dealerIds.filter((id) => id !== dealer.dealer);
|
||||
} else {
|
||||
localFilterStates.dealerIds = [...localFilterStates.dealerIds, dealer.dealer];
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<span>{{ dealer.dealer__name }}</span>
|
||||
<div class="is-flex is-gap-2 is-flex-shrink-0">
|
||||
<span>{{ dealer.product_count_by_dealer }}</span>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: localFilterStates.dealerIds.includes(dealer.dealer) ? 'checked.svg' : 'uncheck.svg',
|
||||
type: localFilterStates.dealerIds.includes(dealer.dealer) ? 'primary' : 'twitter',
|
||||
size: 22,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-5 is-flex is-gap-2">
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="updateFilters"
|
||||
>
|
||||
Áp dụng
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
@click="resetCartDealerFilters"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
li:hover {
|
||||
background-color: var(--bulma-primary-95);
|
||||
}
|
||||
</style>
|
||||
@@ -1,65 +0,0 @@
|
||||
<script setup>
|
||||
import Modal from "@/components/Modal.vue";
|
||||
const showLayerSettingListModal = ref(null);
|
||||
const showLayerSettingSaveModal = ref(null);
|
||||
|
||||
function openLayerSettingList() {
|
||||
showLayerSettingListModal.value = {
|
||||
component: "viewer/LayerSettingList",
|
||||
title: "Chọn thiết lập layer",
|
||||
width: "400px",
|
||||
height: "auto",
|
||||
};
|
||||
}
|
||||
|
||||
function openLayerSettingSave() {
|
||||
showLayerSettingSaveModal.value = {
|
||||
component: "viewer/LayerSettingSave",
|
||||
title: "Lưu thiết lập layer",
|
||||
width: "400px",
|
||||
height: "auto",
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="is-flex is-gap-1">
|
||||
<button
|
||||
class="button is-small p-2 is-white"
|
||||
title="Mở thiết lập"
|
||||
@click="openLayerSettingList"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'folder.svg', type: 'primary', size: 20 }" />
|
||||
</button>
|
||||
<button
|
||||
class="button is-small p-2 is-white"
|
||||
title="Lưu thiết lập"
|
||||
@click="openLayerSettingSave"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'save.svg', type: 'primary', size: 20 }" />
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
v-bind="showLayerSettingListModal"
|
||||
v-if="showLayerSettingListModal"
|
||||
@close="showLayerSettingListModal = null"
|
||||
/>
|
||||
<Modal
|
||||
v-bind="showLayerSettingSaveModal"
|
||||
v-if="showLayerSettingSaveModal"
|
||||
@close="showLayerSettingSaveModal = null"
|
||||
/>
|
||||
</template>
|
||||
<style>
|
||||
#layerSettingSFCContainer {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 75px;
|
||||
height: 50px;
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
padding: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,69 +0,0 @@
|
||||
<script setup>
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
import { applyLayerSetting } from "@/components/viewer/utils/aps-viewer";
|
||||
|
||||
const store = useStore();
|
||||
const { $getdata, $deleteapi } = useNuxtApp();
|
||||
const layerSettings = ref([]);
|
||||
|
||||
async function fetchLayerSettings() {
|
||||
layerSettings.value = await $getdata("layersetting", {
|
||||
user: store.login.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteLayerSetting(id) {
|
||||
try {
|
||||
const result = await $deleteapi("layersetting", id);
|
||||
if (result && !result.error) {
|
||||
if (store.layersetting?.id === id) {
|
||||
store.commit("layersetting", undefined);
|
||||
}
|
||||
fetchLayerSettings();
|
||||
} else {
|
||||
throw new Error(result.error || "Xóa thiết lập layer không thành công.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Lỗi khi xóa thiết lập layer:", error);
|
||||
alert("Đã xảy ra lỗi khi xóa thiết lập layer. Vui lòng thử lại.");
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchLayerSettings);
|
||||
</script>
|
||||
<template>
|
||||
<ul v-if="layerSettings.length > 0">
|
||||
<li
|
||||
v-for="layersetting in layerSettings"
|
||||
:key="layersetting.id"
|
||||
:class="[
|
||||
'border-bottom px-2 py-0 is-flex is-justify-content-space-between is-align-items-center is-clickable hoverable',
|
||||
store.layersetting?.id === layersetting.id && 'has-text-weight-bold has-background-white-ter',
|
||||
]"
|
||||
@click="applyLayerSetting(layersetting, store)"
|
||||
>
|
||||
<span>{{ layersetting.name }}</span>
|
||||
<button
|
||||
class="delBtn"
|
||||
@click.stop="deleteLayerSetting(layersetting.id)"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'primary', size: 18 }" />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>Bạn chưa tạo thiết lập layer nào.</p>
|
||||
</template>
|
||||
<style scoped>
|
||||
.hoverable:hover {
|
||||
background-color: var(--bulma-primary-95);
|
||||
}
|
||||
|
||||
.delBtn {
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: hsla(0, 0%, 0%, 0.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,127 +0,0 @@
|
||||
<script setup>
|
||||
import { getLayerNames } from "@/components/viewer/utils/aps-viewer";
|
||||
|
||||
const store = useStore();
|
||||
const { layersetting } = store;
|
||||
const emit = defineEmits("close");
|
||||
const { $insertapi, $patchapi, $snackbar } = useNuxtApp();
|
||||
const radioSave = ref(layersetting ? "overwrite" : "new");
|
||||
const name = ref(radioSave === "new" ? "" : layersetting ? layersetting.name : "");
|
||||
const errors = ref([]);
|
||||
|
||||
watch(radioSave, (val) => {
|
||||
name.value = val === "new" ? "" : layersetting.name;
|
||||
errors.value = [];
|
||||
});
|
||||
|
||||
function switchRadioSave(newVal) {
|
||||
if (newVal === "overwrite" && !layersetting) return;
|
||||
|
||||
radioSave.value = newVal;
|
||||
}
|
||||
|
||||
async function saveLayerSetting() {
|
||||
const layerNames = getLayerNames();
|
||||
const user = store.login.id;
|
||||
if (!name.value) {
|
||||
errors.value.push({
|
||||
name: "name",
|
||||
msg: "Tên thiết lập không được bỏ trống",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
user,
|
||||
name: name.value,
|
||||
detail: layerNames,
|
||||
};
|
||||
|
||||
if (radioSave.value === "overwrite") {
|
||||
payload.id = layersetting.id;
|
||||
}
|
||||
|
||||
const res =
|
||||
radioSave.value === "new" ? await $insertapi("layersetting", payload) : await $patchapi("layersetting", payload);
|
||||
if (res === "error") {
|
||||
$snackbar(
|
||||
isVietnamese.value
|
||||
? `Có lỗi xảy ra khi ${radioSave.value === "new" ? "tạo" : "cập nhật"} thiết lập`
|
||||
: `Error ${radioSave.value === "new" ? "creating" : "updating"} layer setting`,
|
||||
"Lỗi",
|
||||
"Error",
|
||||
);
|
||||
} else {
|
||||
store.commit("layersetting", res);
|
||||
emit("close");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="field">
|
||||
<label class="label fs-14">Chọn chế độ lưu</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-white fs-14 px-2 py-1"
|
||||
@click="switchRadioSave('overwrite')"
|
||||
:disabled="layersetting === undefined"
|
||||
>
|
||||
<span class="icon-text">
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: `radio-${radioSave === 'new' ? 'unchecked' : 'checked'}.svg`,
|
||||
type: 'gray',
|
||||
size: 22,
|
||||
}"
|
||||
/>
|
||||
Ghi đè
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="button is-white fs-14 px-2 py-1"
|
||||
@click="switchRadioSave('new')"
|
||||
>
|
||||
<span class="icon-text">
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: `radio-${radioSave === 'new' ? 'checked' : 'unchecked'}.svg`,
|
||||
type: 'gray',
|
||||
size: 22,
|
||||
}"
|
||||
/>
|
||||
Tạo mới
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14">Tên thiết lập</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
style="box-sizing: border-box"
|
||||
type="text"
|
||||
placeholder="e.g. Đường, Mặt hồ"
|
||||
v-model="name"
|
||||
@keydown.enter="saveLayerSetting"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="help has-text-danger"
|
||||
v-if="errors.find((v) => v.name === 'name')"
|
||||
>
|
||||
{{ errors.find((v) => v.name === "name").msg }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="saveLayerSetting()"
|
||||
>
|
||||
Lưu lại
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<style></style>
|
||||
@@ -1,368 +0,0 @@
|
||||
<script setup>
|
||||
import { countBy, difference, isEqual, pick } from "es-toolkit";
|
||||
import Search from "@/components/viewer/Search.vue";
|
||||
|
||||
const props = defineProps({
|
||||
products: Object,
|
||||
carts: Object,
|
||||
dealers: Object,
|
||||
defaultStatusIds: Object,
|
||||
defaultCartIds: Object,
|
||||
defaultDealerIds: Object,
|
||||
filters: Object,
|
||||
statuses: Object,
|
||||
statusModes: Object,
|
||||
statusMode: Object,
|
||||
});
|
||||
|
||||
const { products, carts, dealers, defaultCartIds, defaultDealerIds, filters, statuses, statusModes, statusMode } =
|
||||
toRefs(props);
|
||||
|
||||
const emit = defineEmits(["switchStatusMode", "updateFilters", "resetCartDealerFilters", "resetFilters"]);
|
||||
const store = useStore();
|
||||
|
||||
const showFilterModal = ref(null);
|
||||
const { dealer } = store;
|
||||
const filterTabs = dealer ? { CARTS: "Giỏ hàng" } : { CARTS: "Giỏ hàng", DEALERS: "Đại lý" };
|
||||
|
||||
function openFiltersModal() {
|
||||
showFilterModal.value = {
|
||||
component: "viewer/Filters",
|
||||
title: "Tuỳ chọn",
|
||||
width: "40%",
|
||||
height: "20%",
|
||||
vbind: {
|
||||
carts,
|
||||
dealers,
|
||||
filters,
|
||||
filterTabs,
|
||||
productCount: products.value.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function updateFilters(updatedFilters) {
|
||||
if (isEqual(pick(filters.value, ["cartIds", "dealerIds"]), updatedFilters.value)) return;
|
||||
|
||||
let payload;
|
||||
const { lastlegendfiltertab } = store;
|
||||
|
||||
if (lastlegendfiltertab === filterTabs.CARTS) {
|
||||
payload = {
|
||||
cartIds: toRaw(updatedFilters.value.cartIds),
|
||||
dealerIds: defaultDealerIds.value,
|
||||
};
|
||||
} else if (lastlegendfiltertab === filterTabs.DEALERS) {
|
||||
payload = {
|
||||
cartIds: defaultCartIds.value,
|
||||
dealerIds: toRaw(updatedFilters.value.dealerIds),
|
||||
};
|
||||
}
|
||||
emit("updateFilters", payload);
|
||||
}
|
||||
|
||||
const cartOrDealerFilterInfo = computed(() => {
|
||||
// dealer or cart filter is modified
|
||||
if (filters.value.cartIds.length < carts.value.length) {
|
||||
const filteredCarts = carts.value
|
||||
.filter((c) => filters.value.cartIds.includes(c.id))
|
||||
.map((c) => c.name.replace("Giỏ hàng ", ""));
|
||||
return {
|
||||
name: filterTabs.CARTS,
|
||||
values: filteredCarts.join(", "),
|
||||
};
|
||||
}
|
||||
if (filters.value.dealerIds.length < dealers.value.length) {
|
||||
const filteredDealers = dealers.value.filter((d) => filters.value.dealerIds.includes(d.id)).map((c) => c.code);
|
||||
return {
|
||||
name: filterTabs.DEALERS,
|
||||
values: filteredDealers.join(", "),
|
||||
};
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const productCountByStatus = computed(() => {
|
||||
const productsFilteredBeforeStatus = products.value.filter((product) => {
|
||||
/*
|
||||
cart__dealer: null means cart doesnt belong to any dealer
|
||||
- when no filter: count null
|
||||
- when filter: dont count null
|
||||
*/
|
||||
const { cartIds, dealerIds } = filters.value;
|
||||
const { cart, cart__dealer } = product;
|
||||
|
||||
const diffCartFilter = difference(defaultCartIds.value, cartIds);
|
||||
const diffDealerFilter = difference(defaultDealerIds.value, dealerIds);
|
||||
const noCartFilter = diffCartFilter.length === 0;
|
||||
const noDealerFilter = diffDealerFilter.length === 0;
|
||||
const cartCondition = noCartFilter ? cartIds.includes(cart) || cart === null : cartIds.includes(cart);
|
||||
const dealerCondition = noDealerFilter
|
||||
? dealerIds.includes(cart__dealer) || cart__dealer === null
|
||||
: dealerIds.includes(cart__dealer);
|
||||
|
||||
return cartCondition && dealerCondition;
|
||||
});
|
||||
|
||||
const result = countBy(productsFilteredBeforeStatus, (product) =>
|
||||
statusMode.value === statusModes.value.SIMPLE ? product.status__sale_status : product.status,
|
||||
);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const visibilityAll = computed(() => {
|
||||
return filters.value.statusIds.length > 0;
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="#legend.docking-panel.docking-panel-container-solid-color-a">
|
||||
<div id="customBtns">
|
||||
<div class="is-sr-only">
|
||||
<!-- hack to speed up first eye-off icon appearance -->
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: 'eye-autodesk-off.svg',
|
||||
type: 'black',
|
||||
size: 15,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
title="Show/hide all legends"
|
||||
@click="
|
||||
() =>
|
||||
emit('updateFilters', {
|
||||
statusIds: visibilityAll ? [] : statuses.map((s) => s.id),
|
||||
})
|
||||
"
|
||||
>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: `eye-autodesk-${visibilityAll ? 'on' : 'off'}.svg`,
|
||||
type: 'black',
|
||||
size: 17,
|
||||
}"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</Teleport>
|
||||
<Search :products="products" />
|
||||
<div
|
||||
class="tabs mb-0 has-background-white is-fullwidth is-flex-shrink-0"
|
||||
style="position: sticky; top: 32.5px; z-index: -1"
|
||||
>
|
||||
<ul class="mx-0">
|
||||
<li
|
||||
v-for="mode in Object.values(statusModes)"
|
||||
:class="{ 'is-active': statusMode === mode }"
|
||||
@click="
|
||||
() => {
|
||||
if (statusMode !== mode) emit('switchStatusMode', mode);
|
||||
}
|
||||
"
|
||||
>
|
||||
<a :class="['is-size-7', statusMode === mode && 'has-text-weight-bold has-text-secondary']">{{ mode }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="fs-13 has-background-white"
|
||||
style="z-index: -2"
|
||||
>
|
||||
<div
|
||||
v-for="{ id, name, color } in statuses.filter((x) => {
|
||||
// hide Chưa bán in dealer
|
||||
return dealer ? x.code !== 'not-sold' : true;
|
||||
})"
|
||||
class="status is-flex is-gap-1 is-justify-content-space-between is-clickable"
|
||||
@click="() => emit('updateFilters', { statusIds: [id] })"
|
||||
>
|
||||
<div class="statusInfo">
|
||||
<div
|
||||
class="swatch"
|
||||
:style="{ backgroundColor: color }"
|
||||
></div>
|
||||
<p>
|
||||
<span>{{ name }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="mr-2 has-text-right is-inline-block"
|
||||
style="line-height: 1"
|
||||
>
|
||||
{{ productCountByStatus[id] || 0 }}
|
||||
</span>
|
||||
<button
|
||||
class="eyeBtn"
|
||||
@click.stop="
|
||||
() => {
|
||||
const selected = filters.statusIds.includes(id);
|
||||
const payload = {
|
||||
statusIds: selected
|
||||
? filters.statusIds.filter((statusId) => statusId !== id)
|
||||
: [...filters.statusIds, id],
|
||||
};
|
||||
emit('updateFilters', payload);
|
||||
}
|
||||
"
|
||||
>
|
||||
<SvgIcon
|
||||
v-bind="{
|
||||
name: `eye-autodesk-${filters.statusIds.includes(id) ? 'on' : 'off'}.svg`,
|
||||
type: 'black',
|
||||
size: 17,
|
||||
}"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
:class="[
|
||||
'button is-fullwidth is-radiusless is-gap-0.5 is-small is-flex-direction-column is-align-items-stretch fs-13',
|
||||
cartOrDealerFilterInfo && 'is-light',
|
||||
]"
|
||||
style="position: sticky; bottom: 0; box-sizing: border-box; border-inline-width: 0; border-bottom-width: 0"
|
||||
@click="openFiltersModal"
|
||||
>
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<div class="is-flex is-gap-1">
|
||||
<SvgIcon v-bind="{ name: 'filter.svg', type: 'primary', size: 18 }" />
|
||||
<span>Lọc</span>
|
||||
</div>
|
||||
<div style="margin-right: 5px">
|
||||
<span class="has-text-weight-bold">{{ Object.values(productCountByStatus).reduce((p, c) => p + c, 0) }}</span>
|
||||
<span style="margin-left: 9px">SP</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="cartOrDealerFilterInfo"
|
||||
class="is-fullwidth is-flex is-flex-direction-column is-align-items-flex-start has-text-left"
|
||||
>
|
||||
<p
|
||||
class="is-fullwidth"
|
||||
style="text-wrap: wrap"
|
||||
>
|
||||
{{ cartOrDealerFilterInfo.name }}:
|
||||
{{ cartOrDealerFilterInfo.values }}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
<Teleport to="#legend.docking-panel.docking-panel-container-solid-color-a">
|
||||
<Modal
|
||||
v-if="showFilterModal"
|
||||
v-bind="showFilterModal"
|
||||
@close="showFilterModal = null"
|
||||
@updateFilters="updateFilters"
|
||||
@resetCartDealerFilters="emit('resetCartDealerFilters')"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
<style>
|
||||
#legend.docking-panel {
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
min-width: 150px;
|
||||
width: 275px;
|
||||
height: fit-content;
|
||||
|
||||
--hover-bg: rgba(0, 0, 0, 0.1);
|
||||
|
||||
&.top-initial-important {
|
||||
top: initial !important; /* fixed top until meshes & colors are added */
|
||||
}
|
||||
|
||||
.docking-panel-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-bottom: none;
|
||||
text-transform: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.docking-panel-close {
|
||||
width: 40px;
|
||||
background-position: 14px 19px;
|
||||
|
||||
&:hover {
|
||||
/* make it uniform with our buttons */
|
||||
background-color: var(--hover-bg);
|
||||
transition-duration: var(--bulma-duration);
|
||||
transition-property: background-color, border-color, color;
|
||||
}
|
||||
}
|
||||
|
||||
.docking-panel-footer {
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
.content.docking-panel-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
z-index: -1;
|
||||
overflow: scroll;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
.status {
|
||||
&:nth-child(even) {
|
||||
background-color: #f2f2f280;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 191, 255, 0.2);
|
||||
}
|
||||
|
||||
.statusInfo {
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#customBtns {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 40px;
|
||||
width: 40px;
|
||||
height: 50px;
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
|
||||
& > * {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--hover-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eyeBtn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,166 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
const store = useStore();
|
||||
const { $connectWebSocket, $subscribe } = useNuxtApp();
|
||||
const { dealer } = store;
|
||||
const notes = ref([]);
|
||||
const isLoading = ref(true);
|
||||
|
||||
function navigateTo(productId) {
|
||||
if (!productId) return;
|
||||
window.dispatchEvent(new CustomEvent("navigateToProduct", { detail: { productId } }));
|
||||
}
|
||||
|
||||
function handleWsMessageForNotes({ detail: response }) {
|
||||
if (response.type !== "realtime_update") return;
|
||||
const { name, change_type, record } = response.payload;
|
||||
if (name.toLowerCase() !== "product_note") return;
|
||||
|
||||
if (dealer?.id && record.ref__cart__dealer !== dealer.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newNotes = [...notes.value];
|
||||
const existingNoteIndex = newNotes.findIndex((n) => n.id === record.id);
|
||||
|
||||
if (change_type === "created") {
|
||||
if (existingNoteIndex === -1) {
|
||||
newNotes.push(record);
|
||||
}
|
||||
} else if (change_type === "updated") {
|
||||
if (existingNoteIndex > -1) {
|
||||
newNotes[existingNoteIndex] = {
|
||||
...newNotes[existingNoteIndex],
|
||||
...record,
|
||||
};
|
||||
}
|
||||
} else if (change_type === "deleted") {
|
||||
if (existingNoteIndex > -1) {
|
||||
newNotes.splice(existingNoteIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
notes.value = newNotes.sort((a, b) => new Date(b.create_time) - new Date(a.create_time));
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
$connectWebSocket();
|
||||
const filter = dealer?.id ? { ref__cart__dealer: dealer.id } : undefined;
|
||||
|
||||
$subscribe("productnote", filter, (initialData) => {
|
||||
if (initialData && initialData.rows) {
|
||||
notes.value = initialData.rows.sort((a, b) => new Date(b.create_time) - new Date(a.create_time));
|
||||
}
|
||||
isLoading.value = false;
|
||||
});
|
||||
|
||||
window.addEventListener("ws_message", handleWsMessageForNotes);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("ws_message", handleWsMessageForNotes);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="is-flex is-flex-direction-column has-background-white"
|
||||
style="height: 100%; overflow: hidden"
|
||||
>
|
||||
<!-- Loading -->
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="is-flex is-flex-grow-1 is-justify-content-center is-align-items-center p-5"
|
||||
>
|
||||
<div class="has-text-centered">
|
||||
<progress
|
||||
class="progress is-info"
|
||||
max="100"
|
||||
style="width: 100px"
|
||||
></progress>
|
||||
<p class="has-text-grey is-size-7 mt-2">Đang tải...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty -->
|
||||
<div
|
||||
v-else-if="notes.length === 0"
|
||||
class="is-flex is-flex-grow-1 is-justify-content-center is-align-items-center p-5"
|
||||
>
|
||||
<p class="has-text-grey-light is-size-7">Chưa có ghi chú</p>
|
||||
</div>
|
||||
|
||||
<!-- Notes Table -->
|
||||
<div
|
||||
v-else
|
||||
class="is-flex-grow-1"
|
||||
style="overflow-y: auto; overflow-x: hidden; min-height: 0"
|
||||
>
|
||||
<table class="table is-fullwidth is-hoverable is-narrow is-size-7 mb-0">
|
||||
<thead style="position: sticky; top: 0px">
|
||||
<tr>
|
||||
<th>Sản phẩm</th>
|
||||
<th>Nội dung</th>
|
||||
<th style="min-width: max-content; text-wrap: nowrap">Người tạo</th>
|
||||
<th class="has-text-right">Thời gian</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="note in notes"
|
||||
:key="note.id"
|
||||
class="is-clickable"
|
||||
@click="navigateTo(note.ref)"
|
||||
>
|
||||
<td>
|
||||
<span class="tag is-link is-light is-small">{{ note.ref__trade_code }}</span>
|
||||
</td>
|
||||
<td style="white-space: pre-wrap; word-break: break-word">
|
||||
{{ note.detail }}
|
||||
</td>
|
||||
<td>{{ note.username || note.user__username }}</td>
|
||||
<td
|
||||
class="has-text-right"
|
||||
style="white-space: nowrap"
|
||||
>
|
||||
{{ new Date(note.create_time).toLocaleString("vi-VN") }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
#noteHistory.docking-panel {
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
width: 450px;
|
||||
height: 400px;
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
|
||||
.docking-panel-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-bottom: none;
|
||||
text-transform: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.docking-panel-scroll {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 20px;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.table.is-narrow td,
|
||||
.table.is-narrow th {
|
||||
padding: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,205 +0,0 @@
|
||||
<script setup>
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
import { findLandByTradeCode, findTemInside, objIsTem } from "@/components/viewer/utils/aps-viewer";
|
||||
import { getBounds } from "@/components/viewer/utils/geometry";
|
||||
|
||||
const props = defineProps({
|
||||
products: Array,
|
||||
});
|
||||
|
||||
const productInput = ref("");
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
const productMatches = computed(() => {
|
||||
const val = productInput.value.trim().toUpperCase();
|
||||
if (!val) return;
|
||||
return props.products.filter((p) => p.trade_code.includes(val));
|
||||
});
|
||||
|
||||
function panToLand(trade_code) {
|
||||
const { viewer } = window;
|
||||
|
||||
viewer.getObjectTree(async (instanceTree) => {
|
||||
const dbIdMap = instanceTree.nodeAccess.dbIdToIndex;
|
||||
const allDbIds = Object.keys(dbIdMap).map((id) => parseInt(id));
|
||||
|
||||
viewer.model.getBulkProperties(allDbIds, {}, (results) => {
|
||||
const frags = viewer.model.getFragmentList();
|
||||
const tems = results.filter(objIsTem);
|
||||
const lands = results.filter((land) => {
|
||||
const layerProp = land.properties.find((prop) => prop.displayName === "Layer");
|
||||
if (!layerProp) return false;
|
||||
const globalWidthProp = land.properties.find((prop) => prop.displayName === "Global width");
|
||||
return layerProp.displayValue === "1-bodim" && globalWidthProp.displayValue === 0;
|
||||
});
|
||||
|
||||
const temsWithBounds = tems.map((tem) => ({
|
||||
...tem,
|
||||
bounds: getBounds(tem.dbId, frags),
|
||||
}));
|
||||
const landsWithBounds = lands.map((land) => ({
|
||||
...land,
|
||||
bounds: getBounds(land.dbId, frags),
|
||||
}));
|
||||
const temsInside = landsWithBounds.map((landWithBounds) => findTemInside(landWithBounds, temsWithBounds));
|
||||
|
||||
const [foundLand] = findLandByTradeCode(trade_code, landsWithBounds, temsInside);
|
||||
if (!foundLand) return;
|
||||
|
||||
viewer.fitToView([foundLand.dbId]);
|
||||
isSubmitting.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clickMatchBtn(product) {
|
||||
isSubmitting.value = true;
|
||||
panToLand(product.trade_code);
|
||||
}
|
||||
|
||||
function submit() {
|
||||
isSubmitting.value = true;
|
||||
const trade_code = productInput.value.toUpperCase();
|
||||
|
||||
const foundProduct = props.products.find((p) => {
|
||||
return p.trade_code === trade_code;
|
||||
});
|
||||
|
||||
if (!foundProduct) {
|
||||
isSubmitting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
panToLand(trade_code);
|
||||
}
|
||||
|
||||
const showMatches = ref(true);
|
||||
const input = useTemplateRef("input");
|
||||
const matches = useTemplateRef("matches");
|
||||
|
||||
function clickAwayListener(e) {
|
||||
if (input.value?.contains(e.target) || matches.value?.contains(e.target)) {
|
||||
showMatches.value = true;
|
||||
} else {
|
||||
showMatches.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("click", clickAwayListener);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", clickAwayListener);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="has-background-white"
|
||||
style="position: sticky; top: 0"
|
||||
>
|
||||
<form class="is-flex is-gap-1">
|
||||
<div class="field has-addons is-flex-grow-1">
|
||||
<p
|
||||
ref="input"
|
||||
class="control mb-0 is-flex-grow-1"
|
||||
>
|
||||
<input
|
||||
v-model="productInput"
|
||||
class="input is-radiusless has-icons-right fs-13"
|
||||
type="text"
|
||||
placeholder="Tìm sản phẩm"
|
||||
style="box-sizing: border-box; border-inline-width: 0; box-shadow: none"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="clearInputBtn"
|
||||
v-if="productInput"
|
||||
@click="productInput = ''"
|
||||
tabindex="-1"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'close.svg', type: 'primary', size: 14 }" />
|
||||
</button>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button
|
||||
:class="['button is-small is-primary is-radiusless', isSubmitting && 'is-loading']"
|
||||
:disabled="!productInput"
|
||||
tabindex="-1"
|
||||
style="box-sizing: border-box; width: 38px; height: 100%"
|
||||
@click.prevent="submit"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
v-if="!isSubmitting"
|
||||
class="icon"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'search.svg', type: 'white', size: 18 }" />
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
v-if="showMatches"
|
||||
ref="matches"
|
||||
tabindex="-1"
|
||||
style="
|
||||
max-height: 100px;
|
||||
overflow-y: scroll;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 4px 0 hsla(0, 0%, 0%, 0.1);
|
||||
"
|
||||
>
|
||||
<button
|
||||
v-for="product in productMatches"
|
||||
@click="clickMatchBtn(product)"
|
||||
class="button is-small is-fullwidth is-radiusless is-justify-content-start"
|
||||
style="box-sizing: border-box; border-inline-width: 0; border-bottom-width: 0"
|
||||
>
|
||||
<p>{{ product.trade_code }}</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.control.is-loading::after,
|
||||
.select.is-loading::after,
|
||||
.button.is-loading::after {
|
||||
/* overrides spinner color */
|
||||
border: 3px solid white;
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.button:focus-visible,
|
||||
.button.is-focused {
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
/* copy styles from is-hovered */
|
||||
--bulma-button-background-l-delta: var(--bulma-button-hover-background-l-delta);
|
||||
--bulma-button-border-l-delta: var(--bulma-button-hover-border-l-delta);
|
||||
}
|
||||
|
||||
.clearInputBtn {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 5;
|
||||
border-radius: 9999px;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
background-color: hsla(0, 0%, 0%, 0.05);
|
||||
|
||||
&:hover {
|
||||
background-color: hsla(0, 0%, 0%, 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,77 +0,0 @@
|
||||
export const utopiaUrn =
|
||||
"dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6cGtxYjhrbHBnZWtsZ2tlbHBzanBoYzljMm5neXhtbjY0cXZocHNhcXVodjQ2emVuLWJhc2ljLWFwcC8yNi4wMS4xNiUyMC0lMjBFeHBvcnQlMjBUTUIlMjAtJTIwUGhhbiUyMG1lbS5kd2c";
|
||||
export const blankUrn =
|
||||
"dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6cGtxYjhrbHBnZWtsZ2tlbHBzanBoYzljMm5neXhtbjY0cXZocHNhcXVodjQ2emVuLWJhc2ljLWFwcC9ibGFuay5kd2c";
|
||||
|
||||
export async function getAccessToken(callback) {
|
||||
try {
|
||||
const { access_token, expires_in } = await $fetch("/api/apsAuthToken");
|
||||
callback(access_token, expires_in);
|
||||
} catch (err) {
|
||||
console.error("Could not obtain access token. Error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
export function loadModel(viewer, urn, xform) {
|
||||
return new Promise((resolve, reject) => {
|
||||
function onDocumentLoadSuccess(doc) {
|
||||
const viewable = doc.getRoot().getDefaultGeometry();
|
||||
const options = {
|
||||
keepCurrentModels: true,
|
||||
};
|
||||
if (xform) {
|
||||
options.placementTransform = xform;
|
||||
}
|
||||
viewer.loadDocumentNode(doc, viewable, options).then(resolve).catch(reject);
|
||||
}
|
||||
function onDocumentLoadFailure(code, message, errors) {
|
||||
reject({ code, message, errors });
|
||||
}
|
||||
viewer.setLightPreset(0);
|
||||
Autodesk.Viewing.Document.load(`urn:${urn}`, onDocumentLoadSuccess, onDocumentLoadFailure);
|
||||
});
|
||||
}
|
||||
|
||||
function showNotification(message) {
|
||||
const overlay = document.getElementById("overlay");
|
||||
overlay.innerHTML = `<div class="notification">${message}</div>`;
|
||||
overlay.style.display = "flex";
|
||||
}
|
||||
|
||||
function clearNotification() {
|
||||
const overlay = document.getElementById("overlay");
|
||||
overlay.innerHTML = "";
|
||||
overlay.style.display = "none";
|
||||
}
|
||||
|
||||
export async function setupModelSelection(viewer) {
|
||||
if (window.onModelSelectedTimeout) {
|
||||
clearTimeout(window.onModelSelectedTimeout);
|
||||
delete window.onModelSelectedTimeout;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await $fetch(`/api/models/${utopiaUrn}/status`);
|
||||
const { status } = res;
|
||||
switch (status.status) {
|
||||
case "n/a":
|
||||
showNotification(`Model has not been translated.`);
|
||||
break;
|
||||
case "inprogress":
|
||||
showNotification(`Model is being translated (${status.progress})...`);
|
||||
window.onModelSelectedTimeout = setTimeout(onModelSelected, 5000, viewer, utopiaUrn);
|
||||
break;
|
||||
case "failed":
|
||||
showNotification(
|
||||
`Translation failed. <ul>${status.messages.map((msg) => `<li>${JSON.stringify(msg)}</li>`).join("")}</ul>`,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
clearNotification();
|
||||
loadModel(viewer, utopiaUrn);
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Could not load model. Error:", err);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import { blankUrn, utopiaUrn } from "@/components/viewer/utils/aps-init";
|
||||
import { isNotNil } from "es-toolkit";
|
||||
|
||||
function isTemInsideLand(landBounds, temBounds) {
|
||||
const tolerance = 0.007;
|
||||
return (
|
||||
landBounds.min.x - temBounds.min.x <= tolerance &&
|
||||
landBounds.min.y - temBounds.min.y <= tolerance &&
|
||||
landBounds.max.x - temBounds.max.x >= -tolerance &&
|
||||
landBounds.max.y - temBounds.max.y >= -tolerance
|
||||
);
|
||||
}
|
||||
|
||||
export function findTemInside(land, tems) {
|
||||
const temInside = tems.find((tem) => {
|
||||
const temBounds = tem.bounds[0];
|
||||
|
||||
for (const landBounds of land.bounds) {
|
||||
if (isTemInsideLand(landBounds, temBounds)) return true;
|
||||
}
|
||||
});
|
||||
|
||||
return temInside;
|
||||
}
|
||||
|
||||
export const getTradeCodeFromTem = (tem) => tem.properties.find((prop) => prop.displayName === "LO").displayValue;
|
||||
export const objIsTem = (obj) => obj.name && obj.name.startsWith("Blk003");
|
||||
export const objIsLand = (obj) => {
|
||||
const layerProp = obj.properties.find((prop) => prop.displayName === "Layer");
|
||||
if (!layerProp) return false;
|
||||
const globalWidthProp = obj.properties.find((prop) => prop.displayName === "Global width");
|
||||
return (
|
||||
(layerProp.displayValue === "1-bodim" || layerProp.displayValue === "0") /* special case - Z.E02.02A */ &&
|
||||
globalWidthProp.displayValue === 0
|
||||
);
|
||||
};
|
||||
|
||||
export function findLandByTradeCode(trade_code, lands, temsInside) {
|
||||
const foundTemIndex = temsInside.findIndex((tem) => tem && getTradeCodeFromTem(tem) === trade_code);
|
||||
return foundTemIndex >= 0 ? [lands[foundTemIndex], foundTemIndex] : undefined;
|
||||
}
|
||||
|
||||
export function pan(viewer) {
|
||||
const navigation = viewer.navigation;
|
||||
const position = navigation.getPosition();
|
||||
const target = navigation.getTarget();
|
||||
|
||||
// offset both target and position to maintain angle
|
||||
const panOffset = new THREE.Vector3(2, 0, 0);
|
||||
navigation.setPosition(position.clone().add(panOffset));
|
||||
navigation.setTarget(target.clone().add(panOffset));
|
||||
}
|
||||
|
||||
export function unloadUnusedExtensions(viewer) {
|
||||
viewer.addEventListener(Autodesk.Viewing.EXTENSION_LOADED_EVENT, (e) => {
|
||||
if (["Autodesk.Measure", "Autodesk.DocumentBrowser", "Autodesk.DefaultTools.NavTools"].includes(e.extensionId)) {
|
||||
viewer.unloadExtension(e.extensionId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function addTemSelectionListener(viewer, products, openProductViewModal, openNoPermissionModal) {
|
||||
viewer.addEventListener(
|
||||
Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT,
|
||||
(e) => {
|
||||
viewer.clearSelection();
|
||||
if (e.selections.length !== 1) return;
|
||||
const [selectedObj] = e.selections;
|
||||
const { model, dbIdArray } = selectedObj;
|
||||
if (dbIdArray.length !== 1) return;
|
||||
|
||||
if (model.loader.svfUrn === utopiaUrn) {
|
||||
const [dbId] = dbIdArray;
|
||||
viewer.getProperties(dbId, (obj) => {
|
||||
if (!objIsTem(obj)) return;
|
||||
|
||||
const trade_code = getTradeCodeFromTem(obj);
|
||||
const product = products.find((p) => p.trade_code === trade_code);
|
||||
product ? openProductViewModal(product) : openNoPermissionModal();
|
||||
});
|
||||
} else if (model.loader.svf.isSceneBuilder) {
|
||||
const [dbId] = dbIdArray;
|
||||
const product = products.find((p) => p.id === dbId);
|
||||
product ? openProductViewModal(product) : openNoPermissionModal();
|
||||
} else if (model.loader.svfUrn === blankUrn) {
|
||||
viewer.clearSelection(); // make unselectable
|
||||
}
|
||||
},
|
||||
(err) => console.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayerNames() {
|
||||
const { viewer } = window;
|
||||
if (!viewer) return;
|
||||
if (!viewer.impl) return;
|
||||
|
||||
const layerNames = viewer.impl.layers.indexToLayer
|
||||
.filter((obj) => isNotNil(obj)) // not counting root
|
||||
.filter((obj) => obj.visible)
|
||||
.map(({ layer }) => layer.name)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
return layerNames;
|
||||
}
|
||||
|
||||
export function applyLayerSetting(layersetting, store) {
|
||||
const { viewer } = window;
|
||||
viewer.setLayerVisible(null, false); // first, hide everything
|
||||
viewer.setLayerVisible(layersetting?.detail || null, true); // show specific ones, or all if there's no setting
|
||||
store.commit("layersetting", layersetting);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export async function renderSvg(relativePath) {
|
||||
const svgRes = await fetch(relativePath);
|
||||
const svgText = await svgRes.text();
|
||||
const parser = new DOMParser();
|
||||
const svgDoc = parser.parseFromString(svgText, "image/svg+xml");
|
||||
const svgElement = svgDoc.documentElement;
|
||||
|
||||
return svgElement;
|
||||
}
|
||||
|
||||
export function html(tag, props = {}, children = []) {
|
||||
const element = document.createElement(tag);
|
||||
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
if (key === "textContent" || key === "innerHTML") {
|
||||
element[key] = value;
|
||||
} else if (key === "style" && typeof value === "object") {
|
||||
Object.assign(element.style, value);
|
||||
} else if (key === "class") {
|
||||
element.className = value;
|
||||
} else if (key.startsWith("on")) {
|
||||
element.addEventListener(key.slice(2).toLowerCase(), value);
|
||||
} else {
|
||||
element.setAttribute(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
children.flat().forEach((child) => {
|
||||
if (typeof child === "string") {
|
||||
element.appendChild(document.createTextNode(child));
|
||||
} else if (child instanceof Node) {
|
||||
element.appendChild(child);
|
||||
}
|
||||
});
|
||||
|
||||
return element;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
export class GeometryCallback {
|
||||
constructor(viewer, vpXform) {
|
||||
this.viewer = viewer;
|
||||
this.vpXform = vpXform;
|
||||
this.lines = [];
|
||||
}
|
||||
|
||||
onLineSegment(x1, y1, x2, y2) {
|
||||
let pt1 = new THREE.Vector3().set(x1, y1, 0).applyMatrix4(this.vpXform);
|
||||
let pt2 = new THREE.Vector3().set(x2, y2, 0).applyMatrix4(this.vpXform);
|
||||
|
||||
this.lines.push({
|
||||
x1: pt1.x,
|
||||
y1: pt1.y,
|
||||
x2: pt2.x,
|
||||
y2: pt2.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getBounds(dbId, frags) {
|
||||
let fragIds = frags.fragments.dbId2fragId[dbId];
|
||||
if (typeof fragIds === "number") {
|
||||
fragIds = [fragIds];
|
||||
}
|
||||
|
||||
const bounds = fragIds.map((fId) => {
|
||||
const bound = new THREE.Box3();
|
||||
const boundsCallback = new Autodesk.Viewing.Private.BoundsCallback(bound);
|
||||
const mesh = frags.getVizmesh(fId);
|
||||
const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, viewer.impl.use2dInstancing);
|
||||
vbr.enumGeomsForObject(dbId, boundsCallback);
|
||||
return bound;
|
||||
});
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
export function extractPoints(lines) {
|
||||
const tolerance = 0.001;
|
||||
const allPoints = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const { x1, y1, x2, y2 } = lines[i];
|
||||
allPoints.push({ x: x1, y: y1 });
|
||||
allPoints.push({ x: x2, y: y2 });
|
||||
}
|
||||
|
||||
const pointsDeduped = [];
|
||||
|
||||
for (let i = 0; i < allPoints.length; i++) {
|
||||
const element = allPoints[i];
|
||||
|
||||
const notFound = !pointsDeduped.find(
|
||||
(pt) => Math.abs(pt.x - element.x) < tolerance && Math.abs(pt.y - element.y) < tolerance,
|
||||
);
|
||||
if (notFound) pointsDeduped.push(element);
|
||||
}
|
||||
|
||||
return pointsDeduped;
|
||||
}
|
||||
|
||||
export function sortByAngle(points) {
|
||||
// Calculate centroid
|
||||
const centroid = new THREE.Vector3();
|
||||
points.forEach((v) => centroid.add(v));
|
||||
centroid.divideScalar(points.length);
|
||||
|
||||
// Sort by angle around centroid
|
||||
return points.slice().sort((a, b) => {
|
||||
const angleA = Math.atan2(a.y - centroid.y, a.x - centroid.x);
|
||||
const angleB = Math.atan2(b.y - centroid.y, b.x - centroid.x);
|
||||
return angleA - angleB;
|
||||
});
|
||||
}
|
||||
|
||||
export function getColorByIndex(i) {
|
||||
const colors = ["magenta", "red", "orange", "yellow", "chartreuse", "green", "blue"];
|
||||
if (i === 0) return "black";
|
||||
return colors[i % 7];
|
||||
}
|
||||
|
||||
/**
|
||||
* - THREE.Color: `{ r: number, g: number, b: number }`
|
||||
* - THREE.Vector4: `{ x: number, y: number, z: number, w: number }`
|
||||
*
|
||||
* @param {string} colorStr e.g. `'#ff0000'`, `'white'`, `'hotpink'`
|
||||
* @returns A Vector4 object
|
||||
*/
|
||||
export function colorStrToVector4(colorStr) {
|
||||
const color = new THREE.Color().setStyle(colorStr);
|
||||
const vector4 = new THREE.Vector4(color.r, color.g, color.b, 1);
|
||||
return vector4;
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<span>{{ tab[$store.lang] }}</span>
|
||||
<Icon
|
||||
name="material-symbols:arrow-forward-ios-rounded"
|
||||
:size="16"
|
||||
:size="15"
|
||||
class="has-text-grey"
|
||||
/>
|
||||
<span>{{ subtab[$store.lang] }}</span>
|
||||
@@ -40,14 +40,17 @@
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div id="header-right-slot"></div>
|
||||
<div
|
||||
id="header-right-slot"
|
||||
:key="componentKey"
|
||||
></div>
|
||||
</div>
|
||||
<KeepAlive>
|
||||
<component
|
||||
:is="componentMap[vbind.component]"
|
||||
v-bind="vbind"
|
||||
:key="componentKey"
|
||||
v-if="componentKey"
|
||||
:is="componentMap[vbind.component]"
|
||||
:key="componentKey"
|
||||
v-bind="vbind"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</div>
|
||||
|
||||
@@ -421,6 +421,7 @@ export default defineNuxtPlugin(() => {
|
||||
|
||||
return {
|
||||
provide: {
|
||||
store,
|
||||
find,
|
||||
findIndex,
|
||||
filter,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// nuxt 3 - plugins/my-plugin.ts
|
||||
import { useRoute } from "vue-router";
|
||||
import { useStore } from "~/stores/index";
|
||||
import dayjs from "dayjs";
|
||||
import weekday from "dayjs/plugin/weekday";
|
||||
import weekOfYear from "dayjs/plugin/weekOfYear";
|
||||
@@ -17,10 +15,9 @@ dayjs.extend(isSameOrBefore);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.locale("vi");
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const route = useRoute();
|
||||
const { $id, $empty } = useNuxtApp();
|
||||
const store = useStore();
|
||||
const { $id, $empty, $store } = nuxtApp;
|
||||
const dialog = function (content, title, type, duration, width, height, vbind) {
|
||||
content = typeof content === "string" ? content : JSON.stringify(content);
|
||||
let vtitle = type === "Success" ? `<span class="has-text-primary">${title}</span>` : title;
|
||||
@@ -33,7 +30,7 @@ export default defineNuxtPlugin(() => {
|
||||
width: width || "600px",
|
||||
height: height || "100px",
|
||||
};
|
||||
store.commit("showmodal", data);
|
||||
$store.commit("showmodal", data);
|
||||
};
|
||||
|
||||
const snackbar = function (content, title, type, width, height) {
|
||||
@@ -48,7 +45,7 @@ export default defineNuxtPlugin(() => {
|
||||
width: width || "600px",
|
||||
height: height || "100px",
|
||||
};
|
||||
store.commit("snackbar", data);
|
||||
$store.commit("snackbar", data);
|
||||
};
|
||||
|
||||
const getLink = function (val) {
|
||||
@@ -379,8 +376,8 @@ export default defineNuxtPlugin(() => {
|
||||
};
|
||||
|
||||
const lang = function (code) {
|
||||
let field = store.common.find((v) => v.code === code);
|
||||
return field ? field[store.lang] : "";
|
||||
let field = $store.common.find((v) => v.code === code);
|
||||
return field ? field[$store.lang] : "";
|
||||
};
|
||||
|
||||
const createMeta = function (metainfo) {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
// nuxt 3 - plugins/my-plugin.ts
|
||||
import { useStore } from "~/stores/index";
|
||||
import axios from "axios";
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const module = "application";
|
||||
const mode = "dev";
|
||||
const paths = [
|
||||
@@ -1001,13 +997,12 @@ export default defineNuxtPlugin(() => {
|
||||
params: {},
|
||||
},
|
||||
];
|
||||
const store = useStore();
|
||||
const { $copy, $clone, $updateSeriesFields, $snackbar, $remove, $dialog } = useNuxtApp();
|
||||
const { $copy, $clone, $updateSeriesFields, $snackbar, $store, $remove, $dialog } = nuxtApp;
|
||||
|
||||
const requestLogin = function () {
|
||||
store.commit("login", undefined);
|
||||
store.commit("layersetting", undefined);
|
||||
store.commit("lastlegendfiltertab", "Giỏ hàng");
|
||||
$store.commit("login", undefined);
|
||||
$store.commit("layersetting", undefined);
|
||||
$store.commit("lastlegendfiltertab", "Giỏ hàng");
|
||||
window.location.href = `https://${mode === "dev" ? "dev." : ""}login.utopia.com.vn/signin?module=${module}&link=${window.location.origin}`;
|
||||
};
|
||||
|
||||
@@ -1022,17 +1017,19 @@ export default defineNuxtPlugin(() => {
|
||||
return $copy(result);
|
||||
};
|
||||
|
||||
const readyapi = function (list) {
|
||||
var array = [];
|
||||
list.forEach((element) => {
|
||||
let found = apis.find((v) => v.name === element);
|
||||
const readyapi = function (apiNames) {
|
||||
const apisWithReady = [];
|
||||
apiNames.forEach((apiName) => {
|
||||
const found = apis.find((v) => v.name === apiName);
|
||||
if (found) {
|
||||
let ele = JSON.parse(JSON.stringify(found));
|
||||
ele.ready = store[element] ? true : false;
|
||||
array.push(ele);
|
||||
const apiWithReady = {
|
||||
...$copy(found),
|
||||
ready: Boolean($store[apiName]),
|
||||
};
|
||||
apisWithReady.push(apiWithReady);
|
||||
}
|
||||
});
|
||||
return array;
|
||||
return apisWithReady;
|
||||
};
|
||||
|
||||
// get data
|
||||
@@ -1042,16 +1039,15 @@ export default defineNuxtPlugin(() => {
|
||||
let found = apis.find((api) => api.name === v.name);
|
||||
let url = (v.path ? paths.find((x) => x.name === v.path).url : path) + (v.url ? v.url : found.url);
|
||||
let params = v.params ? v.params : found.params === undefined ? {} : found.params;
|
||||
params.login = store.login ? store.login.id : undefined;
|
||||
return { url: url, params: params };
|
||||
params.login = $store.login ? $store.login.id : undefined;
|
||||
return { url, params };
|
||||
});
|
||||
//let data = await Promise.all(arr.map(v=>axios.get(v.url, {params: v.params})))
|
||||
let data = await Promise.all(arr.map((v) => $fetch(v.url, { params: v.params })));
|
||||
data.map((v, i) => {
|
||||
list[i].data = v;
|
||||
if (list[i].commit) {
|
||||
let data = v.rows ? v.rows : v;
|
||||
store.commit(list[i].commit, data);
|
||||
$store.commit(list[i].commit, data);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
@@ -1067,20 +1063,24 @@ export default defineNuxtPlugin(() => {
|
||||
let found = findapi(name);
|
||||
let curpath = found.path ? paths.find((x) => x.name === found.path).url : path;
|
||||
var rs;
|
||||
if (!Array.isArray(data))
|
||||
rs = await axios.post(`${curpath}${found.url}`, data, {
|
||||
params: { values: values },
|
||||
if (!Array.isArray(data)) {
|
||||
rs = await $fetch(`${curpath}${found.url}`, {
|
||||
method: "POST",
|
||||
body: data,
|
||||
params: { values },
|
||||
});
|
||||
else {
|
||||
let params = { action: "import", values: values };
|
||||
rs = await axios.post(`${curpath}import-data/${found.url.substring(5, found.url.length - 1)}/`, data, {
|
||||
params: params,
|
||||
} else {
|
||||
let params = { action: "import", values };
|
||||
rs = await $fetch(`${curpath}import-data/${found.url.substring(5, found.url.length - 1)}/`, {
|
||||
method: "POST",
|
||||
body: data,
|
||||
params,
|
||||
});
|
||||
}
|
||||
// update store
|
||||
if (found.commit) {
|
||||
if (store[found.commit]) {
|
||||
let copy = JSON.parse(JSON.stringify(store[found.commit]));
|
||||
if ($store[found.commit]) {
|
||||
let copy = JSON.parse(JSON.stringify($store[found.commit]));
|
||||
let rows = Array.isArray(rs.data) ? rs.data : [rs.data];
|
||||
rows.map((v) => {
|
||||
if (v.id && !v.error) {
|
||||
@@ -1089,11 +1089,11 @@ export default defineNuxtPlugin(() => {
|
||||
else copy.push(v);
|
||||
}
|
||||
});
|
||||
store.commit(found.commit, copy);
|
||||
$store.commit(found.commit, copy);
|
||||
}
|
||||
}
|
||||
if (notify !== false) {
|
||||
store.lang === "en"
|
||||
$store.lang === "en"
|
||||
? $snackbar("Data has been successfully saved to the system.", "Success", "Success")
|
||||
: $snackbar("Dữ liệu đã được lưu vào hệ thống", "Thành công", "Success");
|
||||
}
|
||||
@@ -1110,25 +1110,27 @@ export default defineNuxtPlugin(() => {
|
||||
let found = findapi(name);
|
||||
let curpath = found.path ? paths.find((x) => x.name === found.path).url : path;
|
||||
let updateUrl = found.url_detail ? found.url_detail : found.url;
|
||||
let rs = await axios.put(`${curpath}${updateUrl}${data.id}/`, data, {
|
||||
params: { values: values ? values : found.params.values },
|
||||
let rs = await $fetch(`${curpath}${updateUrl}${data.id}/`, {
|
||||
method: "PUT",
|
||||
body: data,
|
||||
params: { values: values || found.params.values },
|
||||
});
|
||||
if (found.commit) {
|
||||
let index = store[found.commit] ? store[found.commit].findIndex((v) => v.id === rs.data.id) : -1;
|
||||
let index = $store[found.commit] ? $store[found.commit].findIndex((v) => v.id === rs.id) : -1;
|
||||
if (index >= 0) {
|
||||
var copy = JSON.parse(JSON.stringify(store[found.commit]));
|
||||
if (Array.isArray(rs.data) === false) copy[index] = rs.data;
|
||||
var copy = $copy($store[found.commit]);
|
||||
if (Array.isArray(rs) === false) copy[index] = rs;
|
||||
else {
|
||||
rs.data.forEach((v) => {
|
||||
rs.forEach((v) => {
|
||||
let index = copy.findIndex((v) => v.id === v.id);
|
||||
if (index >= 0) copy[index] = v;
|
||||
});
|
||||
}
|
||||
store.commit(found.commit, copy);
|
||||
$store.commit(found.commit, copy);
|
||||
}
|
||||
}
|
||||
if (notify !== false) {
|
||||
store.lang === "en"
|
||||
$store.lang === "en"
|
||||
? $snackbar("Data has been successfully saved to the system.", "Success", "Success")
|
||||
: $snackbar("Dữ liệu đã được lưu vào hệ thống", "Thành công", "Success");
|
||||
}
|
||||
@@ -1145,15 +1147,19 @@ export default defineNuxtPlugin(() => {
|
||||
let found = findapi(name);
|
||||
let curpath = found.path ? paths.find((x) => x.name === found.path).url : path;
|
||||
let updateUrl = found.url_detail ? found.url_detail : found.url;
|
||||
let rs = await axios.patch(`${curpath}${updateUrl}${data.id}/`, data, {
|
||||
params: { values: values ? values : found.params?.values },
|
||||
|
||||
const rs = await $fetch(`${curpath}${updateUrl}${data.id}/`, {
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
params: { values: values || found.params?.values },
|
||||
});
|
||||
|
||||
if (notify !== false) {
|
||||
store.lang === "en"
|
||||
$store.lang === "en"
|
||||
? $snackbar("Data has been successfully saved to the system.", "Success", "Success")
|
||||
: $snackbar("Dữ liệu đã được lưu vào hệ thống", "Thành công", "Success");
|
||||
}
|
||||
return rs.data;
|
||||
return rs;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return "error";
|
||||
@@ -1161,7 +1167,7 @@ export default defineNuxtPlugin(() => {
|
||||
};
|
||||
|
||||
const findpage = function (arr) {
|
||||
var copy = $copy(store.pagetrack);
|
||||
var copy = $copy($store.pagetrack);
|
||||
var doFind = function () {
|
||||
let found = undefined;
|
||||
for (let i = 1; i <= 30; i++) {
|
||||
@@ -1184,7 +1190,7 @@ export default defineNuxtPlugin(() => {
|
||||
} else {
|
||||
result = doFind(copy);
|
||||
}
|
||||
store.commit("pagetrack", copy);
|
||||
$store.commit("pagetrack", copy);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -1209,7 +1215,7 @@ export default defineNuxtPlugin(() => {
|
||||
const setpage = function (pagename, row, api) {
|
||||
let json = row.detail;
|
||||
let fields = $updateSeriesFields(json.fields);
|
||||
let copy = store[pagename] || getpage();
|
||||
let copy = $store[pagename] || getpage();
|
||||
copy.fields = fields;
|
||||
copy.setting = $copy(row);
|
||||
if (json.filters) copy.filters = $copy(json.filters);
|
||||
@@ -1222,7 +1228,7 @@ export default defineNuxtPlugin(() => {
|
||||
copy.api = copyApi;
|
||||
copy.origin_api = copy;
|
||||
}
|
||||
store.commit(pagename, copy);
|
||||
$store.commit(pagename, copy);
|
||||
return copy;
|
||||
};
|
||||
|
||||
@@ -1289,30 +1295,30 @@ export default defineNuxtPlugin(() => {
|
||||
// insert row
|
||||
var insertrow = async function (name, data, values, pagename, notify) {
|
||||
let result = await insertapi(name, data, values, notify);
|
||||
if (result === "error" || !pagename || !store[pagename]) return result;
|
||||
let copy = $clone(store[pagename]);
|
||||
if (result === "error" || !pagename || !$store[pagename]) return result;
|
||||
let copy = $clone($store[pagename]);
|
||||
copy.update = { refresh: true };
|
||||
store.commit(pagename, copy);
|
||||
$store.commit(pagename, copy);
|
||||
return result;
|
||||
};
|
||||
|
||||
// update row
|
||||
const updaterow = async function (name, data, values, pagename, notify) {
|
||||
let result = await updateapi(name, data, values, notify);
|
||||
if (result === "error" || !pagename || !store[pagename]) return result;
|
||||
let copy = $clone(store[pagename]);
|
||||
if (result === "error" || !pagename || !$store[pagename]) return result;
|
||||
let copy = $clone($store[pagename]);
|
||||
copy.update = { refresh: true };
|
||||
store.commit(pagename, copy);
|
||||
$store.commit(pagename, copy);
|
||||
return result;
|
||||
};
|
||||
|
||||
// patch row
|
||||
const patchrow = async function (name, data, values, pagename, notify) {
|
||||
let result = await patchapi(name, data, values, notify);
|
||||
if (result === "error" || !pagename || !store[pagename]) return result;
|
||||
let copy = $clone(store[pagename]);
|
||||
if (result === "error" || !pagename || !$store[pagename]) return result;
|
||||
let copy = $clone($store[pagename]);
|
||||
copy.update = { refresh: true };
|
||||
store.commit(pagename, copy);
|
||||
$store.commit(pagename, copy);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -1328,11 +1334,11 @@ export default defineNuxtPlugin(() => {
|
||||
else {
|
||||
let params = { action: "delete" };
|
||||
rs = await $fetch(`${path}import-data/${found.url.substring(5, found.url.length - 1)}/`, id, {
|
||||
params: params,
|
||||
params,
|
||||
});
|
||||
}
|
||||
if (found.commit) {
|
||||
let copy = JSON.parse(JSON.stringify(store[found.commit]));
|
||||
let copy = JSON.parse(JSON.stringify($store[found.commit]));
|
||||
if (!Array.isArray(id)) {
|
||||
let index = copy.findIndex((v) => v.id === id);
|
||||
if (index >= 0) $remove(copy, index);
|
||||
@@ -1342,7 +1348,7 @@ export default defineNuxtPlugin(() => {
|
||||
if (index >= 0) $remove(copy, index);
|
||||
});
|
||||
}
|
||||
store.commit(found.name, copy);
|
||||
$store.commit(found.name, copy);
|
||||
console.log("copy", copy);
|
||||
}
|
||||
return id;
|
||||
@@ -1363,16 +1369,16 @@ export default defineNuxtPlugin(() => {
|
||||
// delete row
|
||||
const deleterow = async function (name, id, pagename) {
|
||||
let result = await deleteapi(name, id);
|
||||
if (result === "error" || !pagename || !store[pagename]) return result;
|
||||
let copy = $clone(store[pagename]);
|
||||
if (result === "error" || !pagename || !$store[pagename]) return result;
|
||||
let copy = $clone($store[pagename]);
|
||||
copy.update = { refresh: true };
|
||||
store.commit(pagename, copy);
|
||||
$store.commit(pagename, copy);
|
||||
return result;
|
||||
};
|
||||
|
||||
// update page
|
||||
const updatepage = function (pagename, row, action) {
|
||||
let copy = $clone(store[pagename]);
|
||||
let copy = $clone($store[pagename]);
|
||||
let rows = Array.isArray(row) ? row : [row];
|
||||
rows.map((x) => {
|
||||
let idx = copy.data.findIndex((v) => v.id === x.id);
|
||||
@@ -1383,7 +1389,7 @@ export default defineNuxtPlugin(() => {
|
||||
}
|
||||
});
|
||||
copy.update = { data: copy.data };
|
||||
store.commit(pagename, copy);
|
||||
$store.commit(pagename, copy);
|
||||
};
|
||||
|
||||
const buildFileUrl = (file) => {
|
||||
@@ -1472,25 +1478,25 @@ export default defineNuxtPlugin(() => {
|
||||
return right === "edit" ? rightObj && rightObj.is_edit : Boolean(rightObj);
|
||||
};
|
||||
|
||||
if (store.rights.length === 0) return true; // full rights
|
||||
if ($store.rights.length === 0) return true; // full rights
|
||||
|
||||
if (code && category) {
|
||||
// if passed, must pass both
|
||||
const foundRight = store.rights.find(
|
||||
const foundRight = $store.rights.find(
|
||||
({ setting__category, setting__code }) => setting__category === category && setting__code === code,
|
||||
);
|
||||
return getRight(foundRight);
|
||||
} else {
|
||||
const { tab, subtab } = store.tabinfo;
|
||||
const { tab, subtab } = $store.tabinfo;
|
||||
let isTabEdit;
|
||||
let isSubTabEdit;
|
||||
|
||||
const tabRight = store.rights.find((rights) => rights.setting === tab.id);
|
||||
const tabRight = $store.rights.find((rights) => rights.setting === tab.id);
|
||||
isTabEdit = getRight(tabRight);
|
||||
|
||||
if (!subtab) isSubTabEdit = false;
|
||||
else {
|
||||
const subTabRight = store.rights.find((rights) => rights.setting === subtab.id);
|
||||
const subTabRight = $store.rights.find((rights) => rights.setting === subtab.id);
|
||||
isSubTabEdit = getRight(subTabRight);
|
||||
}
|
||||
|
||||
@@ -1589,7 +1595,6 @@ export default defineNuxtPlugin(() => {
|
||||
deleteapi,
|
||||
deleterow,
|
||||
updatepage,
|
||||
store,
|
||||
requestLogin,
|
||||
buildFileUrl,
|
||||
generateDocument,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
const { $getapi, $readyapi } = useNuxtApp();
|
||||
const { $getapi, $readyapi } = nuxtApp;
|
||||
let connlist = $readyapi([
|
||||
"datatype",
|
||||
"common",
|
||||
"filterchoice",
|
||||
"colorchoice",
|
||||
"common",
|
||||
"textalign",
|
||||
"placement",
|
||||
"colorscheme",
|
||||
@@ -17,9 +17,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
"settingtype",
|
||||
"settingclass",
|
||||
"sex",
|
||||
"legaltype",
|
||||
"cart",
|
||||
]);
|
||||
let filter = connlist.filter((v) => !v.ready);
|
||||
if (filter.length > 0) await $getapi(filter);
|
||||
let notReadyConns = connlist.filter((v) => !v.ready);
|
||||
if (notReadyConns.length > 0) await $getapi(notReadyConns);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ import ExportsDamaged from "@/components/exports/ExportsDamaged.vue";
|
||||
import ExportsInternal from "@/components/exports/ExportsInternal.vue";
|
||||
import ExportsAssembled from "@/components/exports/ExportsAssembled.vue";
|
||||
import InventoryTransfer from "@/components/inventory-transfer/InventoryTransfer.vue";
|
||||
import InventoryCount from "@/components/inventory-count/InventoryCount.vue";
|
||||
import CashBook from "@/components/cash-book/CashBook.vue";
|
||||
import NCC from "@/components/report/NCC.vue";
|
||||
import Customers from "@/components/report/Customers.vue";
|
||||
@@ -58,7 +57,6 @@ import MenuCollab from "~/components/menu/MenuCollab.vue";
|
||||
import MenuNote from "~/components/menu/MenuNote.vue";
|
||||
import MenuPayment from "~/components/menu/MenuPayment.vue";
|
||||
import ScrollBox from "~/components/datatable/ScrollBox.vue";
|
||||
import Viewer from "~/components/viewer/Viewer.vue";
|
||||
import Product from "~/components/product/Product.vue";
|
||||
import Reservation from "~/components/modal/Reservation.vue";
|
||||
import UserMainTab from "~/components/modal/UserMainTab.vue";
|
||||
@@ -128,7 +126,6 @@ const components = {
|
||||
MenuAdd,
|
||||
MenuCollab,
|
||||
MenuNote,
|
||||
Viewer,
|
||||
Product,
|
||||
UserMainTab,
|
||||
InternalAccount,
|
||||
@@ -155,7 +152,6 @@ const components = {
|
||||
ExportsInternal,
|
||||
ExportsAssembled,
|
||||
InventoryTransfer,
|
||||
InventoryCount,
|
||||
CashBook,
|
||||
NCC,
|
||||
Customers,
|
||||
|
||||
Reference in New Issue
Block a user