jws-frontend/src/pages/04_product-service/MainPage.vue

4049 lines
133 KiB
Vue
Raw Normal View History

2024-06-10 16:42:59 +07:00
<script setup lang="ts">
import { ref, watch, reactive } from 'vue';
2024-06-28 14:38:16 +07:00
import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue';
2024-07-10 13:21:32 +07:00
import { getUserId, getRole } from 'src/services/keycloak';
2024-06-28 14:38:16 +07:00
import { storeToRefs } from 'pinia';
2024-07-18 09:10:51 +00:00
import { useQuasar, type QTableProps } from 'quasar';
2024-06-28 14:38:16 +07:00
import ItemCard from 'components/ItemCard.vue';
2024-06-10 16:42:59 +07:00
import AppBox from 'components/app/AppBox.vue';
2024-06-12 15:37:17 +07:00
import AddButton from 'components/AddButton.vue';
2024-06-28 14:14:23 +07:00
import ProductCardComponent from 'components/04_product-service/ProductCardComponent.vue';
2024-06-10 16:42:59 +07:00
import StatCard from 'components/StatCardComponent.vue';
2024-06-28 14:14:23 +07:00
import DrawerInfo from 'components/DrawerInfo.vue';
import BasicInformation from 'components/04_product-service/BasicInformation.vue';
import FormDialog from 'components/FormDialog.vue';
2024-06-12 15:37:17 +07:00
import TooltipComponent from 'components/TooltipComponent.vue';
2024-06-28 14:14:23 +07:00
import ButtonAddComponent from 'components/ButtonAddCompoent.vue';
import BasicInfoProduct from 'components/04_product-service/BasicInfoProduct.vue';
import PriceDataComponent from 'components/04_product-service/PriceDataComponent.vue';
import ProfileUpload from 'components/ProfileUpload.vue';
import TotalProductCardComponent from 'components/04_product-service/TotalProductCardComponent.vue';
2024-06-28 14:14:23 +07:00
import FormServiceWork from 'components/04_product-service/FormServiceWork.vue';
import ServiceProperties from 'components/04_product-service/ServiceProperties.vue';
import WorkNameManagement from 'components/04_product-service/WorkNameManagement.vue';
2024-06-18 11:47:53 +00:00
import useOptionStore from 'src/stores/options';
2024-06-28 14:14:23 +07:00
import FormServiceProperties from 'components/04_product-service/FormServiceProperties.vue';
import InfoForm from 'components/02_personnel-management/InfoForm.vue';
2024-06-28 14:38:16 +07:00
import NoData from 'components/NoData.vue';
import PaginationComponent from 'src/components/PaginationComponent.vue';
2024-06-14 09:31:45 +07:00
2024-06-26 17:21:26 +07:00
import useFlowStore from 'src/stores/flow';
import useMyBranchStore from 'src/stores/my-branch';
import { dateFormat } from 'src/utils/datetime';
import { formatNumberDecimal } from 'src/stores/utils';
2024-07-10 13:21:32 +07:00
const userBranchStore = useMyBranchStore();
2024-07-10 13:21:32 +07:00
const { currentMyBranch } = storeToRefs(userBranchStore);
2024-06-14 09:31:45 +07:00
import { Status } from 'src/stores/types';
2024-06-26 17:21:26 +07:00
2024-07-02 09:58:59 +00:00
import useUtilsStore, { dialog, dialogWarningClose } from 'src/stores/utils';
2024-06-11 15:04:01 +07:00
import useProductServiceStore from 'src/stores/product-service';
2024-06-14 15:18:59 +07:00
import {
ProductGroup,
ProductGroupCreate,
ProductCreate,
ProductList,
ServiceCreate,
Service,
ServiceById,
2024-06-20 18:02:02 +07:00
WorkItems,
Attributes,
ServiceAndProduct,
2024-06-14 15:18:59 +07:00
} from 'src/stores/product-service/types';
2024-07-15 14:12:53 +07:00
import { computed } from 'vue';
2024-06-11 15:04:01 +07:00
2024-06-28 14:38:16 +07:00
const flowStore = useFlowStore();
2024-07-02 09:58:59 +00:00
const utilsStore = useUtilsStore();
2024-06-11 15:04:01 +07:00
const productServiceStore = useProductServiceStore();
2024-06-18 11:47:53 +00:00
const optionStore = useOptionStore();
2024-06-11 15:04:01 +07:00
const {
fetchStatsProductType,
2024-06-12 15:37:17 +07:00
createProductServiceType,
editProductServiceType,
deleteProductServiceType,
fetchListProductServiceType,
2024-06-11 15:04:01 +07:00
fetchStatsProductGroup,
fetchListProductService,
createProductService,
editProductService,
deleteProductService,
fetchStatsProduct,
fetchListProduct,
createProduct,
editProduct,
deleteProduct,
fetchStatsService,
fetchListService,
fetchListServiceById,
createService,
2024-06-18 16:14:50 +07:00
deleteService,
editService,
createWork,
editWork,
deleteWork,
fetchListProductAndService,
2024-06-11 15:04:01 +07:00
} = productServiceStore;
2024-07-15 14:12:53 +07:00
const { workNameItems, recordTreeProductType } =
storeToRefs(productServiceStore);
2024-06-10 16:42:59 +07:00
const stat = ref<
{
2024-07-03 14:13:22 +07:00
icon: string;
2024-06-10 16:42:59 +07:00
count: number;
label: string;
mode: 'group' | 'type' | 'service' | 'product';
2024-06-12 15:37:17 +07:00
color: 'pink' | 'purple' | 'green' | 'orange';
2024-06-10 16:42:59 +07:00
}[]
>([
{
icon: 'mdi-folder',
count: 0,
label: 'productAndService',
mode: 'group',
color: 'pink',
},
2024-07-03 14:13:22 +07:00
{
icon: 'mdi-folder-table',
2024-07-03 14:13:22 +07:00
count: 0,
label: 'productAndServiceType',
mode: 'type',
2024-07-03 14:13:22 +07:00
color: 'purple',
},
{
icon: 'mdi-server-network',
count: 0,
label: 'service',
mode: 'service',
color: 'orange',
},
{
icon: 'mdi-folder',
count: 0,
label: 'product',
mode: 'product',
color: 'green',
},
2024-06-10 16:42:59 +07:00
]);
const { t } = useI18n();
const baseUrl = ref<string>(import.meta.env.VITE_API_BASE_URL);
const holdDialog = ref(false);
const currentNode = ref<ProductGroup>();
2024-07-15 14:12:53 +07:00
const expandedTree = ref<string[]>([]);
const editByTree = ref<'group' | 'type' | undefined>();
const treeProductTypeAndGroup = computed(() => {
const tempValue =
productGroup.value?.map((item) => {
return {
...item,
2024-07-24 15:52:44 +07:00
_count: {
2024-07-25 10:41:58 +07:00
type:
recordTreeProductType.value[item.id]?.length ?? item._count.type,
2024-07-24 15:52:44 +07:00
service: item._count.service,
product: item._count.product,
},
2024-07-15 14:12:53 +07:00
type: 'group',
children:
item.id === currentIdGrop.value
? recordTreeProductType.value[currentIdGrop.value]?.map((x) => ({
2024-07-19 14:04:02 +07:00
...x,
2024-07-15 14:12:53 +07:00
type: 'type',
})) ?? []
2024-07-19 14:04:02 +07:00
: recordTreeProductType.value[item.id]?.map((x) => ({
2024-07-19 18:05:58 +07:00
...x,
type: 'type',
})) ?? item._count.type > 0
? [
{
id: '',
name: '',
},
]
: [],
2024-07-15 14:12:53 +07:00
};
}) ?? [];
return tempValue;
});
2024-06-28 14:38:16 +07:00
const inputSearch = ref('');
const inputSearchProductAndService = ref('');
2024-06-19 18:00:38 +07:00
2024-06-28 14:38:16 +07:00
const currentStatusProduct = ref(false);
const drawerInfo = ref(false);
const isEdit = ref(false);
const modeView = ref(false);
const splitterModel = ref(25);
2024-06-28 14:38:16 +07:00
const dialogInputForm = ref(false);
const dialogProduct = ref(false);
const dialogService = ref(false);
const dialogProductEdit = ref(false);
const dialogServiceEdit = ref(false);
2024-06-28 14:38:16 +07:00
const statusToggle = ref(false);
const profileSubmit = ref(false);
const infoProductEdit = ref(false);
const infoServiceEdit = ref(false);
const imageProduct = ref<File | undefined>(undefined);
2024-06-14 14:45:17 +07:00
const profileUrl = ref<string | null>('');
2024-06-11 16:52:35 +07:00
2024-06-28 14:38:16 +07:00
const pathGroupName = ref('');
const pathTypeName = ref('');
2024-06-20 18:02:02 +07:00
2024-06-28 14:38:16 +07:00
const dialogProductServiceType = ref(false);
const dialogTotalProduct = ref(false);
const productMode = ref<'group' | 'type' | 'service' | 'product'>('group');
2024-06-14 15:18:59 +07:00
const productGroup = ref<ProductGroup[]>();
const productType = ref<ProductGroup[]>();
const product = ref<(ProductList & { type: 'product' })[]>();
const productIsAdd = ref<(ProductList & { type: 'product' })[]>();
const service = ref<(Service & { type: 'service' })[]>();
const resultSearchProduct = ref<ProductList[]>();
const productAndService = ref<ServiceAndProduct[]>([]);
const productAndServiceTab = ref<'product' | 'service'>('service');
2024-06-28 14:38:16 +07:00
const manageWorkNameDialog = ref(false);
const previousValue = ref();
const propertiesOption = ref();
const formDataGroup = ref<ProductGroupCreate>({
remark: '',
detail: '',
name: '',
code: '',
});
const formDataProduct = ref<ProductCreate>({
registeredBranchId: '',
productTypeId: '',
remark: '',
serviceCharge: 0,
agentPrice: 0,
price: 0,
process: 0,
detail: '',
name: '',
code: '',
image: undefined,
});
const formDataProductService = ref<ServiceCreate>({
work: [],
attributes: {
additional: [],
},
detail: '',
name: '',
code: '',
productTypeId: '',
registeredBranchId: '',
});
2024-06-18 02:01:02 +00:00
const serviceTab = [
{
name: 'serviceInformation',
label: 'serviceInformation',
},
{
name: 'workInformation',
label: 'workInformation',
},
];
2024-07-05 03:57:43 +00:00
const hideStat = ref(false);
2024-06-20 18:02:02 +07:00
const tbColumn = {
groupAndType: [
{
name: 'name',
align: 'left',
label: 'name',
field: 'name',
},
{
name: 'detail',
align: 'left',
label: 'detail',
field: 'detail',
},
{
name: 'formDialogInputRemark',
align: 'left',
label: 'formDialogInputRemark',
field: 'remark',
},
{
name: 'createdAt',
align: 'left',
label: 'createdAt',
field: 'createdAt',
},
],
product: [
{ name: 'productName', align: 'left', label: 'productName', field: 'name' },
{
name: 'productDetail',
align: 'left',
label: 'productDetail',
field: 'detail',
},
{
name: 'productProcessingTime',
align: 'left',
label: 'productProcessingTime',
field: 'process',
},
{
name: 'priceInformation',
align: 'center',
label: 'priceInformation',
field: 'name',
},
],
service: [
{ name: 'serviceName', align: 'left', label: 'serviceName', field: 'name' },
{
name: 'serviceDetail',
align: 'left',
label: 'serviceDetail',
field: 'detail',
},
{
name: 'serviceWorkTotal',
align: 'left',
label: 'serviceWorkTotal',
field: (v) => v.work.length,
},
{
name: 'createdAt',
align: 'left',
label: 'createdAt',
field: 'createdAt',
},
],
} satisfies {
groupAndType: QTableProps['columns'];
product: QTableProps['columns'];
service: QTableProps['columns'];
};
const tbControl = reactive({
groupAndType: {
fieldDisplay: ['name', 'detail', 'formDialogInputRemark', 'createdAt'],
fieldSelected: ['name', 'detail', 'formDialogInputRemark', 'createdAt'],
},
product: {
fieldDisplay: [
'productName',
'productDetail',
'productProcessingTime',
'priceInformation',
],
fieldSelected: [
'productName',
'productDetail',
'productProcessingTime',
'priceInformation',
],
},
service: {
fieldDisplay: [
'serviceName',
'serviceDetail',
'serviceWorkTotal',
'createdAt',
],
fieldSelected: [
'serviceName',
'serviceDetail',
'serviceWorkTotal',
'createdAt',
],
},
});
2024-07-18 09:10:51 +00:00
const $q = useQuasar();
2024-06-20 18:02:02 +07:00
const workItems = ref<WorkItems[]>([]);
2024-06-21 11:06:35 +00:00
const workNameRef = ref();
const selectProduct = ref<ProductList[]>([]);
const currentWorkIndex = ref<number>(0);
2024-06-20 18:02:02 +07:00
2024-06-18 02:01:02 +00:00
const currentServiceTab = ref('serviceInformation');
2024-06-18 11:47:53 +00:00
const propertiesDialog = ref<boolean>(false);
2024-06-18 02:01:02 +00:00
const totalProduct = ref<number>(0);
const totalService = ref<number>(0);
const totalProductAndService = ref<number>(0);
const filterStat = ref<('group' | 'type' | 'service' | 'product')[]>([]);
// แบ่งหน้า
const currentPageGroup = ref<number>(1);
const maxPageGroup = ref<number>(1);
2024-06-26 13:12:41 +07:00
const pageSizeGroup = ref<number>(30);
const currentPageType = ref<number>(1);
const maxPageType = ref<number>(1);
const pageSizeType = ref<number>(30);
const maxPageServiceAndProduct = ref<number>(1);
const pageSizeServiceAndProduct = ref<number>(10);
const currentPageServiceAndProduct = ref<number>(1);
const totalType = ref<number>(0);
const totalGroup = ref<number>(0);
const total = ref<number>(0);
2024-06-26 11:26:29 +07:00
// เก็บ id ที่เข้ามา
const currentIdGrop = ref<string>('');
2024-06-12 15:37:17 +07:00
const currentIdType = ref<string>('');
const currentIdService = ref<string>('');
const currentIdProduct = ref<string>('');
const branchOption = ref<{ id: string; name: string }[]>([]);
const currentStatus = ref<Status | 'All'>('All');
2024-06-14 14:45:17 +07:00
const inputFile = (() => {
const element = document.createElement('input');
element.type = 'file';
element.accept = 'image/*';
const reader = new FileReader();
reader.addEventListener('load', () => {
if (typeof reader.result === 'string') profileUrl.value = reader.result;
});
element.addEventListener('change', () => {
imageProduct.value = element.files?.[0];
if (imageProduct.value) {
reader.readAsDataURL(imageProduct.value);
2024-06-14 14:45:17 +07:00
}
});
return element;
})();
async function searchProduct(isAdd: boolean = true) {
const res = await fetchListProduct({
query: inputSearchProductAndService.value,
});
if (res) {
if (isAdd) {
resultSearchProduct.value = res.result;
}
if (!isAdd) {
product.value = res.result.map((v) => {
return {
...v,
type: 'product',
};
});
}
}
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
async function featchStatsService() {
const resStatsService = await fetchStatsService();
totalService.value = resStatsService;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
async function fetchListOfOptionBranch() {
2024-07-10 13:21:32 +07:00
const uid = getUserId();
const role = getRole();
2024-07-10 13:21:32 +07:00
if (!uid) return;
if (role?.includes('system')) {
const result = await userBranchStore.fetchListOptionBranch();
if (result && result.total > 0) branchOption.value = result.result;
} else {
const result = await userBranchStore.fetchListMyBranch(uid);
if (result && result.total > 0) branchOption.value = result.result;
}
formDataProduct.value.registeredBranchId = currentMyBranch.value?.id || '';
formDataProductService.value.registeredBranchId =
currentMyBranch.value?.id || '';
}
async function featchStatsProduct() {
const resStatsProduct = await fetchStatsProduct({
productTypeId: currentIdType.value,
});
totalProduct.value = resStatsProduct;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
2024-06-12 15:37:17 +07:00
async function fetchListType() {
const res = await fetchListProductServiceType({
page: currentPageType.value,
pageSize: pageSizeType.value,
query: !!inputSearch.value ? inputSearch.value : undefined,
productGroupId: currentIdGrop.value,
status:
currentStatus.value === 'All'
? undefined
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: 'INACTIVE',
2024-06-12 15:37:17 +07:00
});
if (res) {
currentPageType.value = res.page;
totalType.value = res.total;
maxPageType.value = Math.ceil(res.total / pageSizeType.value);
2024-06-26 11:26:29 +07:00
productType.value = res.result;
2024-06-12 15:37:17 +07:00
}
}
async function fetchListGroups() {
const res = await fetchListProductService({
page: currentPageGroup.value,
pageSize: pageSizeGroup.value,
query: !!inputSearch.value ? inputSearch.value : undefined,
status:
currentStatus.value === 'All'
? undefined
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: 'INACTIVE',
});
2024-06-12 15:37:17 +07:00
if (res) {
currentPageGroup.value = res.page;
totalGroup.value = res.total;
maxPageGroup.value = Math.ceil(res.total / pageSizeGroup.value);
2024-06-26 11:26:29 +07:00
productGroup.value = res.result;
2024-06-12 15:37:17 +07:00
}
}
async function fetchListOfProductIsAdd(productTypeId: string) {
const res = await fetchListProduct({
status:
currentStatus.value === 'INACTIVE'
? 'INACTIVE'
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: undefined,
productTypeId,
});
if (res) {
productIsAdd.value = res.result.map((v) => {
return {
...v,
type: 'product',
};
});
}
}
async function fetchListOfProduct() {
const res = await fetchListProduct({
page: currentPageServiceAndProduct.value,
pageSize: pageSizeServiceAndProduct.value,
query: !!inputSearchProductAndService.value
? inputSearchProductAndService.value
: undefined,
status:
currentStatus.value === 'INACTIVE'
? 'INACTIVE'
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: undefined,
productTypeId: currentIdType.value,
});
if (res) {
total.value = res.total;
totalProduct.value = res.total;
currentPageServiceAndProduct.value = res.page;
maxPageServiceAndProduct.value = Math.ceil(
res.total / pageSizeServiceAndProduct.value,
);
product.value = res.result.map((v) => {
return {
...v,
type: 'product',
};
});
}
}
async function fetchListOfService() {
const res = await fetchListService({
page: currentPageServiceAndProduct.value,
query: !!inputSearchProductAndService.value
? inputSearchProductAndService.value
: undefined,
pageSize: pageSizeServiceAndProduct.value,
status:
currentStatus.value === 'INACTIVE'
? 'INACTIVE'
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: undefined,
productTypeId: currentIdType.value,
});
if (res) {
totalService.value = res.total;
total.value = res.total;
currentPageServiceAndProduct.value = res.page;
maxPageServiceAndProduct.value = Math.ceil(
res.total / pageSizeServiceAndProduct.value,
);
service.value = res.result.map((v) => {
return {
...v,
type: 'service',
};
});
}
}
async function submitType() {
2024-06-12 15:37:17 +07:00
if (drawerInfo.value) {
await editProductServiceType(currentIdType.value, {
...formDataGroup.value,
productGroupId: currentIdGrop.value,
2024-06-12 15:37:17 +07:00
});
drawerInfo.value = false;
} else {
dialogInputForm.value = false;
const res = await createProductServiceType(
currentIdGrop.value,
formDataGroup.value,
);
if (res) {
stat.value[1].count = stat.value[1].count + 1;
}
2024-06-12 15:37:17 +07:00
}
await fetchListType();
}
const itemCard = [
{
icon: 'mdi:server-network',
text: 'บริการ',
color: 'var(--orange-5)',
},
{
icon: 'mdi:shopping',
text: 'สินค้า',
color: 'var(--green-9)',
},
];
async function toggleStatusProduct(id: string, status: Status) {
await editProduct(id, {
status: status === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE',
});
await alternativeFetch();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
async function toggleStatusService(id: string, status: Status) {
await editService(id, {
status: status === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE',
});
await alternativeFetch();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
2024-06-19 18:00:38 +07:00
async function toggleStatusType(id: string, status: Status) {
await editProductServiceType(id, {
status: status === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE',
});
await fetchListType();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-19 18:00:38 +07:00
}
async function toggleStatusGroup(id: string, status: Status) {
await editProductService(id, {
status: status === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE',
});
await fetchListGroups();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-19 18:00:38 +07:00
}
2024-07-25 10:49:43 +00:00
async function triggerChangeStatus(
id: string,
status: string,
type?: 'type' | 'group' | 'service' | 'product',
) {
return await new Promise((resolve, reject) => {
dialog({
color: status !== 'INACTIVE' ? 'warning' : 'info',
icon: status !== 'INACTIVE' ? 'mdi-alert' : 'mdi-comment-alert',
title: t('confirmChangeStatusTitle'),
actionText:
status !== 'INACTIVE' ? t('switchOffLabel') : t('switchOnLabel'),
message:
status !== 'INACTIVE'
? t('confirmChangeStatusOffMessage')
: t('confirmChangeStatusOnMessage'),
action: async () => {
if (type === 'group' || productMode.value === 'group') {
await toggleStatusGroup(id, status as Status)
.then(resolve)
.catch(reject);
} else if (type === 'type' || productMode.value === 'type') {
await toggleStatusType(id, status as Status)
.then(resolve)
.catch(reject);
} else if (type === 'service' || productMode.value === 'service') {
await toggleStatusService(id, status as Status)
.then(resolve)
.catch(reject);
} else if (type === 'product' || productMode.value === 'product') {
await toggleStatusProduct(id, status as Status)
.then(resolve)
.catch(reject);
}
},
cancel: () => {},
});
});
}
2024-06-18 16:14:50 +07:00
async function deleteServiceById(serviceId?: string) {
dialog({
color: 'negative',
icon: 'mdi-alert',
title: t('deleteConfirmTitle'),
actionText: t('ok'),
persistent: true,
message: t('deleteConfirmMessage'),
action: async () => {
const res = await deleteService(serviceId ?? currentIdService.value);
2024-06-18 16:14:50 +07:00
if (productAndServiceTab.value === 'service') {
await fetchListOfService();
}
dialogServiceEdit.value = false;
if (res) {
totalService.value = totalService.value - 1;
}
2024-06-26 17:21:26 +07:00
flowStore.rotate();
calculateStats();
2024-06-18 16:14:50 +07:00
},
cancel: () => {},
});
}
async function deleteTypeOfProduct(id?: string) {
dialog({
color: 'negative',
icon: 'mdi-alert',
title: t('deleteConfirmTitle'),
actionText: t('ok'),
persistent: true,
message: t('deleteConfirmMessage'),
action: async () => {
const res = await deleteProduct(id ?? currentIdProduct.value);
if (productAndServiceTab.value === 'product') {
await fetchListOfProduct();
}
dialogProductEdit.value = false;
if (res) {
totalProduct.value = totalProduct.value - 1;
}
2024-06-26 17:21:26 +07:00
flowStore.rotate();
calculateStats();
},
cancel: () => {},
});
}
// type or group
async function deleteProductById(productId?: string) {
dialog({
color: 'negative',
icon: 'mdi-alert',
title: t('deleteConfirmTitle'),
actionText: t('ok'),
persistent: true,
message: t('deleteConfirmMessage'),
action: async () => {
2024-07-17 07:32:56 +00:00
if (editByTree.value !== undefined) {
2024-07-15 14:12:53 +07:00
if (editByTree.value === 'type') {
// Product Type
await deleteProductServiceType(productId ?? currentIdType.value);
await fetchListType();
2024-06-12 15:37:17 +07:00
}
2024-07-15 14:12:53 +07:00
if (editByTree.value === 'group') {
// Product Group
const res = await deleteProductService(
productId ?? currentIdGrop.value,
);
2024-07-15 14:12:53 +07:00
if (res) {
}
await fetchListGroups();
}
flowStore.rotate();
calculateStats();
editByTree.value = undefined;
drawerInfo.value = false;
} else {
if (productMode.value === 'type') {
// Product Type
await deleteProductServiceType(productId ?? currentIdType.value);
await fetchListType();
}
if (productMode.value === 'group') {
// Product Group
const res = await deleteProductService(
productId ?? currentIdGrop.value,
);
2024-07-15 14:12:53 +07:00
if (res) {
}
await fetchListGroups();
}
flowStore.rotate();
calculateStats();
drawerInfo.value = false;
}
},
cancel: () => {},
});
}
2024-06-11 15:04:01 +07:00
function undoProductGroup() {
formDataGroup.value = {
remark: previousValue.value.remark,
detail: previousValue.value.detail,
name: previousValue.value.name,
code: previousValue.value.code,
};
isEdit.value = false;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
function assignFormDataGroup(data: ProductGroup) {
previousValue.value = data;
formDataGroup.value = {
remark: data.remark,
detail: data.detail,
name: data.name,
code: data.code,
};
}
2024-06-24 16:53:33 +07:00
const prevService = ref<ServiceCreate>({
work: [],
attributes: {
additional: [],
},
detail: '',
name: '',
code: '',
productTypeId: '',
registeredBranchId: '',
2024-06-24 16:53:33 +07:00
});
2024-06-26 17:44:13 +07:00
const currentService = ref<ServiceById>();
async function assignFormDataProductService(id: string) {
const res = await fetchListServiceById(id);
if (res) {
2024-06-24 16:53:33 +07:00
statusToggle.value = res.status === 'INACTIVE' ? false : true;
2024-07-01 11:04:24 +00:00
profileUrl.value = res.imageUrl;
profileSubmit.value = true;
2024-06-26 17:44:13 +07:00
currentService.value = JSON.parse(JSON.stringify(res));
2024-06-24 16:53:33 +07:00
prevService.value = {
code: res.code,
name: res.name,
detail: res.detail,
attributes: res.attributes,
work: [],
2024-06-24 16:25:33 +07:00
status: res.status,
productTypeId: res.productTypeId,
registeredBranchId: res.registeredBranchId,
};
2024-06-24 16:53:33 +07:00
formDataProductService.value = { ...prevService.value };
res.work.forEach((item) => {
2024-06-24 16:53:33 +07:00
prevService.value.work.push({
name: item.name,
attributes: item.attributes,
productId: item.productOnWork.map(
(productOnWorkItem) => productOnWorkItem.product.id,
),
});
});
2024-06-24 16:53:33 +07:00
formDataProductService.value.work = prevService.value.work;
workItems.value = res.work.map((item) => {
return {
id: item.id,
name: item.name,
attributes: item.attributes,
product: item.productOnWork.map((productOnWorkItem) => {
return {
...productOnWorkItem.product,
2024-06-26 17:44:13 +07:00
nameEn: productOnWorkItem.product.name,
};
}),
};
});
}
}
const prevProduct = ref<ProductCreate>({
productTypeId: '',
remark: '',
serviceCharge: 0,
agentPrice: 0,
price: 0,
process: 0,
detail: '',
name: '',
code: '',
image: undefined,
registeredBranchId: '',
});
function assignFormDataProduct(data: ProductList) {
statusToggle.value = data.status === 'INACTIVE' ? false : true;
profileUrl.value = `${baseUrl.value}/product/${data?.id}/image`;
profileSubmit.value = true;
prevProduct.value = {
productTypeId: data.productTypeId,
remark: data.remark,
serviceCharge: data.serviceCharge,
agentPrice: data.agentPrice,
price: data.price,
process: data.process,
detail: data.detail === '' ? '-' : data.detail,
name: data.name,
code: data.code,
image: undefined,
registeredBranchId: data.registeredBranchId,
};
formDataProduct.value = { ...prevProduct.value };
}
function clearFormGroup() {
formDataGroup.value = {
2024-06-11 15:04:01 +07:00
remark: '',
detail: '',
name: '',
code: '',
};
dialogInputForm.value = false;
}
function clearFormProduct() {
formDataProduct.value = {
productTypeId: '',
remark: '',
serviceCharge: 0,
agentPrice: 0,
price: 0,
process: 0,
detail: '',
name: '',
code: '',
image: undefined,
registeredBranchId: '',
};
2024-07-01 11:04:24 +00:00
imageProduct.value = undefined;
dialogProduct.value = false;
dialogProductEdit.value = false;
2024-07-01 08:52:56 +00:00
profileUrl.value = '';
profileSubmit.value = false;
}
function clearFormService() {
formDataProductService.value = {
code: '',
name: '',
detail: '',
attributes: {
additional: [],
},
work: [],
2024-06-24 16:25:33 +07:00
status: undefined,
productTypeId: '',
registeredBranchId: '',
};
2024-06-21 11:40:18 +07:00
workItems.value = [];
2024-06-21 11:40:18 +07:00
selectProduct.value = [];
dialogService.value = false;
dialogServiceEdit.value = false;
2024-07-01 08:52:56 +00:00
profileUrl.value = '';
profileSubmit.value = false;
2024-07-01 11:04:24 +00:00
imageProduct.value = undefined;
}
2024-06-21 11:40:18 +07:00
function assignFormDataProductServiceCreate() {
formDataProductService.value.work = [];
2024-06-21 11:40:18 +07:00
workItems.value.forEach((item) => {
formDataProductService.value.work.push({
name: item.name,
attributes: item.attributes,
productId: item.product.map((productItem) => productItem.id),
});
});
}
async function submitService() {
2024-06-21 11:40:18 +07:00
assignFormDataProductServiceCreate();
formDataProductService.value.image = imageProduct.value;
formDataProductService.value.productTypeId = currentIdType.value;
if (dialogService.value) {
formDataProductService.value.productTypeId = currentIdType.value;
if (formDataProductService.value.registeredBranchId === '') {
formDataProductService.value.registeredBranchId = null;
}
const res = await createService(formDataProductService.value);
if (res) {
stat.value[2].count = stat.value[2].count + 1;
}
totalService.value = totalService.value + 1;
2024-07-10 11:36:15 +07:00
productAndServiceTab.value = 'service';
}
if (dialogServiceEdit.value) {
await editService(currentIdService.value, {
...formDataProductService.value,
2024-06-24 16:53:33 +07:00
status: statusToggle.value
? formDataProductService.value.status
: 'INACTIVE',
});
}
if (productAndServiceTab.value === 'service') {
await fetchListOfService();
}
2024-06-26 17:21:26 +07:00
flowStore.rotate();
clearFormService();
}
async function submitProduct() {
formDataProduct.value.productTypeId = currentIdType.value;
if (profileSubmit.value) {
formDataProduct.value.image = imageProduct.value;
}
if (dialogProduct.value) {
const res = await createProduct(formDataProduct.value);
if (res) {
stat.value[3].count = stat.value[3].count + 1;
}
2024-07-10 11:36:15 +07:00
productAndServiceTab.value = 'product';
}
if (dialogProductEdit.value) {
await editProduct(currentIdProduct.value, {
...formDataProduct.value,
status: statusToggle.value ? 'ACTIVE' : 'INACTIVE',
});
}
totalProduct.value = totalProduct.value + 1;
clearFormProduct();
if (productAndServiceTab.value === 'product') {
await fetchListOfProduct();
}
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
2024-06-11 15:04:01 +07:00
async function submitGroup() {
if (drawerInfo.value) {
await editProductService(currentIdGrop.value, formDataGroup.value);
} else {
const res = await createProductService(formDataGroup.value);
2024-06-12 15:37:17 +07:00
if (res) {
stat.value[0].count = stat.value[0].count + 1;
}
}
2024-06-11 15:04:01 +07:00
2024-06-14 09:31:45 +07:00
drawerInfo.value = false;
await fetchListGroups();
clearFormGroup();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-11 15:04:01 +07:00
}
2024-06-21 11:06:35 +00:00
function confirmDeleteWork(id: string) {
dialog({
color: 'negative',
icon: 'mdi-alert',
title: t('deleteConfirmTitle'),
actionText: t('delete'),
message: t('deleteConfirmMessage'),
action: async () => {
deleteWork(id);
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-21 11:06:35 +00:00
},
cancel: () => {},
});
}
function triggerConfirmCloseWork() {
const isWorkNameEdit =
workNameRef.value && workNameRef.value.isWorkNameEdit
? workNameRef.value.isWorkNameEdit()
: false;
if (isWorkNameEdit) {
dialogWarningClose(t, {
action: () => {
manageWorkNameDialog.value = false;
},
});
} else {
manageWorkNameDialog.value = false;
}
2024-06-21 11:06:35 +00:00
}
2024-06-20 18:02:02 +07:00
const tempValueProperties = ref<Attributes>({
additional: [],
});
const currentPropertiesMode = ref<'service' | 'work'>('service');
function openPropertiesDialog(type: 'service' | 'work') {
if (type === 'service') {
2024-06-24 07:21:36 +00:00
propertiesOption.value = optionStore.globalOption.servicePropertiesField;
}
if (type === 'work') {
2024-06-24 07:21:36 +00:00
propertiesOption.value = optionStore.globalOption.workPropertiesField;
}
2024-06-20 18:02:02 +07:00
currentPropertiesMode.value = type;
propertiesDialog.value = true;
}
async function calculateStats() {
2024-06-12 15:37:17 +07:00
const resStatsGroup = await fetchStatsProductGroup();
const resStatsType = await fetchStatsProductType();
const resStatsService = await fetchStatsService();
const resStatsProduct = await fetchStatsProduct();
2024-06-12 15:37:17 +07:00
stat.value[0].count = resStatsGroup ?? 0;
stat.value[1].count = resStatsType ?? 0;
stat.value[2].count = resStatsService ?? 0;
stat.value[3].count = resStatsProduct ?? 0;
}
2024-06-12 15:37:17 +07:00
async function fetchStatus() {
if (productAndServiceTab.value === 'product') {
await fetchListOfProduct();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
if (productAndServiceTab.value === 'service') {
await fetchListOfService();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
}
async function alternativeFetch() {
if (productAndServiceTab.value === 'product') {
await fetchListOfProduct();
flowStore.rotate();
}
if (productAndServiceTab.value === 'service') {
await fetchListOfService();
flowStore.rotate();
}
}
2024-06-26 17:44:13 +07:00
function cloneData() {
if (!currentService.value) return;
formDataProductService.value = {
...prevService.value,
attributes: JSON.parse(JSON.stringify(currentService.value.attributes)),
};
workItems.value = currentService.value.work.map((item) => {
return {
id: item.id,
name: item.name,
attributes: JSON.parse(JSON.stringify(item.attributes)),
product: item.productOnWork.map((productOnWorkItem) => {
return {
...productOnWorkItem.product,
nameEn: productOnWorkItem.product.name,
};
}),
};
});
}
async function enterGroup(id: string, name: string) {
expandedTree.value = [];
expandedTree.value.push(id);
pathGroupName.value = name;
currentIdGrop.value = id;
await fetchListType();
productMode.value = 'type';
flowStore.rotate();
}
async function enterType(id: string, name: string) {
2024-07-23 07:25:11 +00:00
if (
expandedTree.value.length > 1 &&
expandedTree.value[expandedTree.value.length - 1] !== id
) {
expandedTree.value.pop();
expandedTree.value.push(id);
} else {
expandedTree.value.push(id);
}
pathTypeName.value = name;
currentIdType.value = id;
productMode.value = 'service';
productAndServiceTab.value = 'service';
await fetchListOfProduct();
await fetchListOfService();
flowStore.rotate();
}
function handleHold(node: ProductGroup) {
if ($q.screen.gt.xs) return;
return function (props: unknown) {
holdDialog.value = true;
currentNode.value = node;
};
}
onMounted(async () => {
2024-07-02 09:58:59 +00:00
utilsStore.currentTitle.title = 'mainProductTitle';
utilsStore.currentTitle.path = [
{
text: 'manage',
handler: () => {
expandedTree.value = [];
currentIdGrop.value = '';
productMode.value = 'group';
},
},
];
2024-07-18 09:10:51 +00:00
modeView.value = $q.screen.lt.md ? true : false;
2024-07-02 09:58:59 +00:00
calculateStats();
await fetchListGroups();
2024-07-15 14:12:53 +07:00
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-11 15:04:01 +07:00
});
watch(productMode, () => {
inputSearch.value = '';
currentStatus.value = 'All';
const tmp: typeof utilsStore.currentTitle.path = [
{
text: 'manage',
handler: () => {
productMode.value = 'group';
expandedTree.value = [];
currentIdGrop.value = '';
filterStat.value = [];
},
},
];
2024-07-08 16:29:37 +07:00
if (productMode.value === 'type' || productMode.value === 'service') {
tmp.push({
text: 'productGroup',
argsi18n: { name: pathGroupName.value },
handler: () => {
productMode.value = 'type';
filterStat.value = filterStat.value.filter((item) => {
return item !== 'type';
});
2024-07-08 16:29:37 +07:00
},
});
}
if (productMode.value === 'service') {
tmp.push({ text: 'productType', argsi18n: { name: pathTypeName.value } });
}
utilsStore.currentTitle.path = tmp;
});
2024-06-14 09:31:45 +07:00
2024-06-14 13:37:11 +07:00
watch(currentStatus, async () => {
if (productMode.value === 'group') {
await fetchListGroups();
} else {
await fetchListType();
}
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-14 13:37:11 +07:00
});
watch(inputSearch, async () => {
if (productMode.value === 'group') {
await fetchListGroups();
flowStore.rotate();
}
if (productMode.value === 'type') {
await fetchListType();
flowStore.rotate();
}
});
watch(inputSearchProductAndService, async () => {
await alternativeFetch();
});
2024-07-18 09:10:51 +00:00
watch(
() => $q.screen.lt.md,
(v) => {
if (v) modeView.value = true;
},
);
2024-06-10 16:42:59 +07:00
</script>
<template>
2024-06-14 09:31:45 +07:00
<ButtonAddComponent style="z-index: 999">
<q-fab-action
v-if="productMode === 'group'"
2024-06-14 13:37:11 +07:00
id="btn-add-product-group"
2024-06-14 09:31:45 +07:00
style="color: white; background-color: hsla(var(--pink-6-hsl))"
padding="xs"
icon="mdi-folder-multiple-plus"
:label="$t('productAndService')"
external-label
label-position="left"
@click="
() => {
clearFormGroup();
2024-06-14 09:31:45 +07:00
dialogInputForm = true;
}
"
/>
<q-fab-action
v-if="productMode === 'type'"
2024-06-14 13:37:11 +07:00
id="btn-add-product-type"
2024-06-14 09:31:45 +07:00
:label="$t('productAndServiceType')"
external-label
label-position="left"
style="color: white; background-color: hsla(var(--purple-11-hsl))"
padding="xs"
icon="mdi-folder-multiple-plus"
@click="
() => {
clearFormGroup();
2024-06-14 09:31:45 +07:00
dialogInputForm = true;
}
"
></q-fab-action>
2024-06-14 14:45:17 +07:00
<q-fab-action
v-if="productMode === 'service'"
:label="$t('buttonAddProduct')"
2024-06-14 14:45:17 +07:00
external-label
label-position="left"
style="color: white; background-color: hsla(var(--green-11-hsl))"
padding="xs"
icon="mdi-folder-multiple-plus"
@click="
() => {
clearFormProduct();
fetchListOfOptionBranch();
2024-06-14 14:45:17 +07:00
dialogProduct = true;
}
"
></q-fab-action>
<q-fab-action
v-if="productMode === 'service'"
2024-06-18 02:01:02 +00:00
:label="$t('addService')"
2024-06-14 14:45:17 +07:00
external-label
label-position="left"
style="color: white; background-color: hsla(var(--orange-6-hsl))"
2024-06-14 14:45:17 +07:00
padding="xs"
icon="mdi-folder-multiple-plus"
@click="
() => {
clearFormGroup();
2024-07-01 08:52:56 +00:00
clearFormService();
fetchListOfOptionBranch();
2024-06-18 02:01:02 +00:00
currentServiceTab = 'serviceInformation';
dialogService = true;
2024-06-14 14:45:17 +07:00
}
"
></q-fab-action>
2024-06-14 09:31:45 +07:00
</ButtonAddComponent>
2024-07-17 07:32:56 +00:00
<div class="full-height column no-wrap">
<div class="text-body-2 q-mb-xs flex items-center">
{{ $t('dataSum') }}
<q-btn
class="q-ml-xs"
icon="mdi-pin"
color="primary"
size="sm"
flat
dense
rounded
@click="hideStat = !hideStat"
:style="hideStat ? 'rotate: 90deg' : ''"
style="transition: 0.1s ease-in-out"
/>
2024-07-04 04:12:10 +00:00
</div>
2024-06-12 15:37:17 +07:00
<transition name="slide">
<div v-if="!hideStat" class="scroll q-mb-md">
<div style="display: inline-block">
<StatCard
label-i18n
:branch="stat.filter((v) => !filterStat.includes(v.mode))"
:dark="$q.dark.isActive"
nowrap
/>
</div>
</div>
</transition>
2024-07-17 07:32:56 +00:00
<div
class="column col rounded bordered"
style="flex-wrap: nowrap; overflow: hidden"
>
2024-07-17 07:32:56 +00:00
<q-splitter
v-model="splitterModel"
2024-07-18 11:01:39 +00:00
:limits="[0, 100]"
2024-07-17 07:32:56 +00:00
style="width: 100%"
class="col"
after-class="overflow-hidden"
>
<template v-slot:before>
<div class="surface-1 column full-height">
2024-07-15 14:12:53 +07:00
<div
class="row no-wrap full-width bordered-b text-weight-bold surface-3 items-center q-px-md q-py-sm"
2024-07-17 07:32:56 +00:00
:style="`min-height: ${$q.screen.gt.sm ? '57px' : '100.8px'}`"
2024-07-15 14:12:53 +07:00
>
<div class="col ellipsis-2-lines">จัดการสาขาทั้งหมด</div>
2024-07-17 07:32:56 +00:00
</div>
2024-07-24 02:06:33 +00:00
<div class="col full-width scroll">
<div class="q-pa-md">
2024-07-17 07:32:56 +00:00
<q-tree
:nodes="treeProductTypeAndGroup"
color="transparent"
2024-07-17 07:32:56 +00:00
node-key="id"
label-key="name"
children-key="children"
v-model:expanded="expandedTree"
style="color: var(--foreground)"
2024-07-17 07:32:56 +00:00
>
<template #default-header="{ node }">
<div
2024-07-23 02:48:17 +00:00
class="full-width q-py-xs clickable-node"
2024-07-23 07:25:11 +00:00
:class="{
'cursor-pointer':
node.type === 'group' || node.type === 'type',
'active-node':
expandedTree[expandedTree.length - 1] === node.id,
}"
v-touch-hold.mouse="handleHold(node)"
2024-07-17 07:32:56 +00:00
@click.stop="
async () => {
if (node.type === 'group') {
if (currentIdGrop !== node.id) {
2024-07-25 11:01:00 +07:00
filterStat.push('group');
await enterGroup(node.id, node.name);
return;
}
if (currentIdGrop === node.id) {
2024-07-23 07:36:28 +00:00
expandedTree = [];
2024-07-25 11:01:00 +07:00
filterStat = [];
expandedTree = expandedTree.filter(
(i) => node.id !== i,
);
currentIdGrop = '';
2024-07-23 07:36:28 +00:00
currentIdType = '';
productMode = 'group';
return;
}
2024-07-17 07:32:56 +00:00
}
if (node.type === 'type') {
if (currentIdType !== node.id) {
2024-07-25 11:01:00 +07:00
filterStat.push('group', 'type');
await enterType(node.id, node.name);
return;
}
if (currentIdType === node.id) {
2024-07-25 11:01:00 +07:00
filterStat = filterStat.filter((item) => {
return item !== 'type';
});
currentIdType = '';
productMode = 'type';
2024-07-23 07:25:11 +00:00
expandedTree.pop();
return;
}
}
2024-07-15 14:12:53 +07:00
}
2024-07-17 07:32:56 +00:00
"
>
<div
class="row col items-center justify-between full-width no-wrap"
>
2024-07-23 02:26:15 +00:00
<q-icon
v-if="node.type === 'group' && node._count.type > 0"
name="mdi-triangle-down"
size="12px"
2024-07-23 07:25:11 +00:00
class="app-text-muted q-mr-md q-ml-sm"
2024-07-23 02:26:15 +00:00
:style="`rotate: ${expandedTree[0] === node.id ? '0deg' : '30deg'}`"
/>
2024-07-23 07:25:11 +00:00
<div
class="col row"
:class="{ 'q-pl-sm': node.type === 'type' }"
:style="`padding-left:${node.type === 'group' && node._count.type === 0 ? '36px' : ''}`"
>
<span
class="ellipsis col-12"
style="white-space: nowrap"
:class="{
'text-weight-bold':
expandedTree[expandedTree.length - 1] ===
node.id,
'app-text-info':
expandedTree[expandedTree.length - 1] ===
node.id,
}"
>
{{ node.name }}
</span>
<span class="app-text-muted text-caption ellipsis">
{{ node.code }}
</span>
</div>
<div
2024-07-23 02:49:21 +00:00
class="row q-gutter-xs items-center no-wrap app-text-muted-2"
v-if="$q.screen.gt.xs"
>
2024-07-17 07:32:56 +00:00
<q-btn
icon="mdi-eye-outline"
:id="`btn-tree-eye-${node.name}`"
size="sm"
dense
round
flat
@click.stop="
() => {
if (node.type === 'type') {
editByTree = 'type';
currentStatusProduct =
node.status === 'INACTIVE';
clearFormGroup();
currentIdType = node.id;
assignFormDataGroup(node);
isEdit = false;
drawerInfo = true;
}
if (node.type === 'group') {
editByTree = 'group';
currentStatusProduct =
node.status === 'INACTIVE';
clearFormGroup();
assignFormDataGroup(node);
isEdit = false;
currentIdGrop = node.id;
2024-07-17 07:32:56 +00:00
drawerInfo = true;
}
2024-07-15 14:12:53 +07:00
}
2024-07-17 07:32:56 +00:00
"
/>
<q-btn
icon="mdi-dots-vertical"
:id="`btn-tree-dots-${node.name}`"
size="sm"
dense
round
flat
@click.stop
:key="node.id"
>
<q-menu class="bordered">
<q-list v-close-popup>
<q-item
v-close-popup
clickable
:id="`view-detail-btn-${node.name}-view`"
@click.stop="
() => {
editByTree = node.type;
if (node.type === 'type') {
currentStatusProduct =
node.status === 'INACTIVE';
clearFormGroup();
currentIdType = node.id;
assignFormDataGroup(node);
isEdit = false;
drawerInfo = true;
}
if (node.type === 'group') {
currentStatusProduct =
node.status === 'INACTIVE';
clearFormGroup();
assignFormDataGroup(node);
isEdit = false;
currentIdGrop = node.id;
2024-07-17 07:32:56 +00:00
drawerInfo = true;
}
2024-07-15 14:12:53 +07:00
}
2024-07-17 07:32:56 +00:00
"
dense
class="row q-py-sm"
style="white-space: nowrap"
>
<q-icon
name="mdi-eye-outline"
class="col-3"
size="xs"
style="color: hsl(var(--green-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('viewDetail') }}
</span>
</q-item>
<q-item
:id="`view-detail-btn-${node.name}-edit`"
v-close-popup
clickable
dense
class="row q-py-sm"
style="white-space: nowrap"
@click.stop="
() => {
editByTree = node.type;
if (node.type === 'type') {
clearFormGroup();
currentIdType = node.id;
assignFormDataGroup(node);
isEdit = true;
drawerInfo = true;
}
if (node.type === 'group') {
clearFormGroup();
assignFormDataGroup(node);
isEdit = true;
currentIdGrop = node.id;
2024-07-17 07:32:56 +00:00
drawerInfo = true;
}
2024-07-15 14:12:53 +07:00
}
2024-07-17 07:32:56 +00:00
"
>
<q-icon
name="mdi-pencil-outline"
class="col-3"
size="xs"
style="color: hsl(var(--cyan-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('edit') }}
</span>
</q-item>
<q-item
v-close-popup
:id="`view-detail-btn-${node.name}-delete`"
dense
:clickable="node.status === 'CREATED'"
class="row"
2024-07-15 14:12:53 +07:00
:class="{
2024-07-17 07:32:56 +00:00
'surface-3': node.status !== 'CREATED',
'app-text-muted': node.status !== 'CREATED',
2024-07-15 14:12:53 +07:00
}"
2024-07-17 07:32:56 +00:00
style="white-space: nowrap"
@click.stop="
() => {
editByTree = node.type;
if (node.type === 'type') {
deleteProductById(node.id);
}
if (node.type === 'group') {
deleteProductById(node.id);
}
}
"
>
<q-icon
name="mdi-trash-can-outline"
size="xs"
class="col-3"
:class="{
'app-text-negative':
node.status === 'CREATED',
}"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('delete') }}
</span>
</q-item>
<q-item dense>
<q-item-section class="q-py-sm">
<div class="q-pa-sm surface-2 rounded">
<q-toggle
:id="`view-detail-btn-${node.name}-status`"
dense
size="sm"
:label="
node.status !== 'INACTIVE'
? $t('switchOnLabel')
: $t('switchOffLabel')
"
@click.stop="
async () => {
if (node.type === 'type') {
2024-07-25 10:49:43 +00:00
triggerChangeStatus(
2024-07-17 07:32:56 +00:00
node.id,
node.status,
2024-07-25 10:49:43 +00:00
node.type,
2024-07-17 07:32:56 +00:00
);
}
if (node.type === 'group') {
2024-07-25 10:49:43 +00:00
triggerChangeStatus(
2024-07-17 07:32:56 +00:00
node.id,
node.status,
2024-07-25 10:49:43 +00:00
node.type,
2024-07-17 07:32:56 +00:00
);
}
2024-07-15 14:12:53 +07:00
}
2024-07-17 07:32:56 +00:00
"
:model-value="
node.status !== 'INACTIVE'
"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
2024-07-15 14:12:53 +07:00
</div>
</div>
2024-07-17 07:32:56 +00:00
</template>
</q-tree>
</div>
</div>
2024-07-05 06:13:55 +00:00
</div>
2024-07-17 07:32:56 +00:00
</template>
<template v-slot:after>
<div class="column no-wrap full-height">
<!-- group/type -->
<div
2024-07-17 07:32:56 +00:00
v-if="productMode === 'group' || productMode === 'type'"
class="surface-2 items-center full-width full-height column col"
2024-06-12 15:37:17 +07:00
>
2024-07-17 07:32:56 +00:00
<!-- tool bar -->
<div
class="row q-py-sm q-px-md justify-between full-width surface-3 bordered-b"
>
2024-07-17 07:32:56 +00:00
<q-input
lazy-rules="ondemand"
2024-07-17 07:32:56 +00:00
for="input-search"
outlined
2024-07-17 07:32:56 +00:00
:class="{ 'col-12': $q.screen.lt.md }"
dense
2024-07-17 07:32:56 +00:00
:label="$t('search')"
class=""
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="200"
>
2024-07-17 07:32:56 +00:00
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
2024-07-17 07:32:56 +00:00
</q-input>
<div
class="row col-12 col-md-6"
:class="{ 'q-pt-xs': $q.screen.lt.md }"
style="white-space: nowrap"
>
<q-select
lazy-rules="ondemand"
2024-07-17 07:32:56 +00:00
v-model="currentStatus"
2024-07-19 14:04:02 +07:00
for="select-status"
2024-07-17 07:32:56 +00:00
outlined
dense
option-value="value"
option-label="label"
class="col"
map-options
emit-value
2024-07-18 04:51:09 +00:00
:hide-dropdown-icon="$q.screen.lt.sm"
2024-07-17 07:32:56 +00:00
:options="[
{ label: $t('all'), value: 'All' },
{ label: $t('statusACTIVE'), value: 'ACTIVE' },
{
label: $t('statusINACTIVE'),
value: 'INACTIVE',
},
]"
></q-select>
<q-select
lazy-rules="ondemand"
2024-07-17 07:32:56 +00:00
v-if="modeView === false"
id="select-field"
for="select-field"
:options="
tbControl.groupAndType.fieldDisplay.map((x) => ({
label: $t(x),
value: x,
}))
"
2024-07-18 04:51:09 +00:00
:hide-dropdown-icon="$q.screen.lt.sm"
2024-07-17 07:32:56 +00:00
:display-value="$t('displayField')"
v-model="tbControl.groupAndType.fieldSelected"
2024-07-18 09:10:51 +00:00
class="col q-ml-sm"
2024-07-17 07:32:56 +00:00
option-label="label"
option-value="value"
map-options
emit-value
outlined
multiple
dense
/>
<q-btn-toggle
v-model="modeView"
id="btn-mode"
dense
2024-07-18 09:10:51 +00:00
class="no-shadow bordered rounded surface-1 q-ml-sm"
2024-07-17 07:32:56 +00:00
:toggle-color="$q.dark.isActive ? 'grey-9' : 'grey-2'"
size="xs"
:options="[
{ value: true, slot: 'folder' },
{ value: false, slot: 'list' },
]"
>
<template v-slot:folder>
<q-icon
name="mdi-view-grid-outline"
size="16px"
class="q-px-sm q-py-xs rounded"
:style="{
color: $q.dark.isActive
? modeView
? '#C9D3DB '
: '#787B7C'
: modeView
? '#787B7C'
: '#C9D3DB',
}"
/>
</template>
<template v-slot:list>
<q-icon
name="mdi-format-list-bulleted"
class="q-px-sm q-py-xs rounded"
size="16px"
:style="{
color: $q.dark.isActive
? modeView === false
? '#C9D3DB'
: '#787B7C'
: modeView === false
? '#787B7C'
: '#C9D3DB',
}"
/>
</template>
</q-btn-toggle>
</div>
</div>
2024-07-17 07:32:56 +00:00
<div
v-if="
((productMode === 'type' && productType?.length === 0) ||
(productGroup?.length === 0 && productMode === 'group')) &&
stat[0].count !== undefined
"
class="items-center justify-center full-width flex col"
>
<NoData :not-found="!!inputSearch" />
</div>
2024-07-17 07:32:56 +00:00
<template v-else>
<div class="surface-2 q-pa-md scroll col full-width">
<q-table
flat
bordered
:grid="modeView"
:rows="
(productMode === 'type' ? productType : productGroup) ||
[]
"
:columns="tbColumn.groupAndType"
class="full-width"
card-container-class="q-col-gutter-md"
row-key="name"
:rows-per-page-options="[0]"
hide-pagination
:visible-columns="tbControl.groupAndType.fieldSelected"
>
<template v-slot:header="props">
<q-tr class="surface-2" :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ $t(col.label) }}
</q-th>
<q-th auto-width />
</q-tr>
</template>
<template v-slot:body="props">
<q-tr
class="cursor-pointer"
2024-07-17 07:32:56 +00:00
:class="{
'app-text-muted': props.row.status === 'INACTIVE',
'status-active': props.row.status !== 'INACTIVE',
'status-inactive': props.row.status === 'INACTIVE',
}"
:props="props"
2024-07-17 07:32:56 +00:00
@click="
async () => {
filterStat.push(productMode);
if (productMode === 'type') {
await enterType(props.row.id, props.row.name);
2024-07-17 07:32:56 +00:00
}
if (productMode === 'group') {
await enterGroup(props.row.id, props.row.name);
2024-07-17 07:32:56 +00:00
}
}
"
>
2024-07-17 07:32:56 +00:00
<q-td
v-if="
tbControl.groupAndType.fieldSelected.includes(
'name',
)
"
>
<div class="row items-center">
<div
2024-07-17 07:32:56 +00:00
style="
width: 50px;
display: flex;
margin-bottom: var(--size-2);
"
>
2024-07-17 07:32:56 +00:00
<div
:class="`icon-color-${productMode === 'group' ? 'pink' : 'purple'}`"
class="table__icon"
>
<q-icon
size="md"
style="scale: 0.8"
:name="
productMode === 'group'
? 'mdi-folder'
: 'mdi-folder-table'
"
/>
</div>
</div>
<div class="col">
2024-07-17 07:32:56 +00:00
<div class="col">
{{ props.row.name }}
</div>
<div class="col app-text-muted">
{{ props.row.code || '-' }}
</div>
</div>
</div>
2024-07-17 07:32:56 +00:00
</q-td>
<q-td
v-if="
tbControl.groupAndType.fieldSelected.includes(
'detail',
)
"
>
{{ props.row.detail || '-' }}
</q-td>
<q-td
v-if="
tbControl.groupAndType.fieldSelected.includes(
'formDialogInputRemark',
)
"
>
{{ props.row.remark || '-' }}
</q-td>
<q-td
v-if="
tbControl.groupAndType.fieldSelected.includes(
'createdAt',
)
"
>
{{ dateFormat(props.row.createdAt) }}
</q-td>
<q-td>
<q-btn
icon="mdi-eye-outline"
:id="`btn-eye-${props.row.name}`"
size="sm"
dense
round
flat
@click.stop="
() => {
if (productMode === 'type') {
currentStatusProduct =
props.row.status === 'INACTIVE';
clearFormGroup();
currentIdType = props.row.id;
assignFormDataGroup(props.row);
isEdit = false;
drawerInfo = true;
}
if (productMode === 'group') {
currentStatusProduct =
props.row.status === 'INACTIVE';
clearFormGroup();
assignFormDataGroup(props.row);
isEdit = false;
currentIdGrop = props.row.id;
2024-07-17 07:32:56 +00:00
drawerInfo = true;
}
}
"
/>
<q-btn
icon="mdi-dots-vertical"
:id="`btn-dots-${props.row.name}`"
size="sm"
dense
round
flat
@click.stop
:key="props.row.id"
>
<q-menu class="bordered">
<q-list v-close-popup>
<q-item
v-close-popup
clickable
:id="`view-detail-btn-${props.row.name}-view`"
@click.stop="
() => {
if (productMode === 'type') {
currentStatusProduct =
props.row.status === 'INACTIVE';
clearFormGroup();
currentIdType = props.row.id;
assignFormDataGroup(props.row);
isEdit = false;
drawerInfo = true;
}
if (productMode === 'group') {
currentStatusProduct =
props.row.status === 'INACTIVE';
clearFormGroup();
assignFormDataGroup(props.row);
isEdit = false;
currentIdGrop = props.row.id;
2024-07-17 07:32:56 +00:00
drawerInfo = true;
}
}
"
dense
class="row q-py-sm"
style="white-space: nowrap"
>
<q-icon
name="mdi-eye-outline"
class="col-3"
size="xs"
style="color: hsl(var(--green-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('viewDetail') }}
</span>
</q-item>
<q-item
:id="`view-detail-btn-${props.row.name}-edit`"
v-close-popup
clickable
dense
class="row q-py-sm"
style="white-space: nowrap"
@click="
() => {
if (productMode === 'type') {
clearFormGroup();
currentIdType = props.row.id;
assignFormDataGroup(props.row);
isEdit = true;
drawerInfo = true;
}
if (productMode === 'group') {
clearFormGroup();
assignFormDataGroup(props.row);
isEdit = true;
currentIdGrop = props.row.id;
2024-07-17 07:32:56 +00:00
drawerInfo = true;
}
}
"
>
<q-icon
name="mdi-pencil-outline"
class="col-3"
size="xs"
style="color: hsl(var(--cyan-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('edit') }}
</span>
</q-item>
<q-item
v-close-popup
:id="`view-detail-btn-${props.row.name}-delete`"
dense
:clickable="props.row.status === 'CREATED'"
class="row"
:class="{
'surface-3': props.row.status !== 'CREATED',
'app-text-muted':
props.row.status !== 'CREATED',
}"
style="white-space: nowrap"
@click="
() => {
if (productMode === 'type') {
deleteProductById(props.row.id);
}
if (productMode === 'group') {
deleteProductById(props.row.id);
}
}
"
>
<q-icon
name="mdi-trash-can-outline"
size="xs"
class="col-3"
:class="{
'app-text-negative':
props.row.status === 'CREATED',
}"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('delete') }}
</span>
</q-item>
<q-item dense>
<q-item-section class="q-py-sm">
<div class="q-pa-sm surface-2 rounded">
<q-toggle
:id="`view-detail-btn-${props.row.name}-status`"
dense
size="sm"
:label="
props.row.status !== 'INACTIVE'
? $t('switchOnLabel')
: $t('switchOffLabel')
"
@click="
async () => {
2024-07-25 10:49:43 +00:00
triggerChangeStatus(
props.row.id,
props.row.status,
);
2024-07-17 07:32:56 +00:00
}
"
:model-value="
props.row.status !== 'INACTIVE'
"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-td>
</q-tr>
</template>
<template v-slot:item="props">
2024-07-25 13:17:40 +07:00
<div class="col-12 col-md-6 col-lg-4">
2024-07-17 07:32:56 +00:00
<ProductCardComponent
:isType="productMode === 'type'"
:count-product="props.row._count.product"
:count-type="props.row._count.type"
:count-service="props.row._count.service"
:title="props.row.name"
:subtitle="props.row.code"
:date="new Date(props.row.updatedAt)"
:status="props.row.status"
:id="props.row.id"
:isDisabled="props.row.status === 'INACTIVE'"
:color="
{
type: $q.dark.isActive
? 'var(--purple-7-hsl)'
: 'var(--violet-11-hsl)',
group: 'var(--pink-6-hsl)',
}[productMode] || 'var(--pink-6-hsl)'
"
@toggleStatus="
2024-07-25 10:49:43 +00:00
triggerChangeStatus(props.row.id, props.row.status)
2024-07-17 07:32:56 +00:00
"
@viewCard="
() => {
if (productMode === 'type') {
currentStatusProduct =
props.row.status === 'INACTIVE';
clearFormGroup();
currentIdType = props.row.id;
assignFormDataGroup(props.row);
isEdit = false;
drawerInfo = true;
}
if (productMode === 'group') {
currentStatusProduct =
props.row.status === 'INACTIVE';
clearFormGroup();
assignFormDataGroup(props.row);
isEdit = false;
currentIdGrop = props.row.id;
drawerInfo = true;
}
}
"
2024-07-17 07:32:56 +00:00
@updateCard="
() => {
if (productMode === 'type') {
clearFormGroup();
currentIdType = props.row.id;
assignFormDataGroup(props.row);
isEdit = true;
drawerInfo = true;
}
if (productMode === 'group') {
clearFormGroup();
assignFormDataGroup(props.row);
isEdit = true;
currentIdGrop = props.row.id;
2024-07-17 07:32:56 +00:00
drawerInfo = true;
}
}
"
@deleteCard="
() => {
if (productMode === 'type') {
deleteProductById(props.row.id);
}
if (productMode === 'group') {
deleteProductById(props.row.id);
}
}
"
@on-click="
async () => {
filterStat.push(productMode);
if (productMode === 'type') {
await enterType(props.row.id, props.row.name);
2024-07-17 07:32:56 +00:00
}
if (productMode === 'group') {
await enterGroup(props.row.id, props.row.name);
2024-07-17 07:32:56 +00:00
}
}
"
/>
2024-07-17 07:32:56 +00:00
</div>
</template>
</q-table>
</div>
2024-07-17 07:32:56 +00:00
<div
v-if="productMode === 'group'"
class="row items-center justify-between q-px-md q-py-sm full-width"
>
<div class="col-4">
<div class="row items-center">
2024-07-18 09:10:51 +00:00
<div
class="app-text-muted"
style="width: 80px"
v-if="$q.screen.gt.sm"
>
2024-07-17 07:32:56 +00:00
{{ $t('showing') }}
</div>
<div>
<q-btn-dropdown
dense
2024-07-17 07:32:56 +00:00
unelevated
:label="pageSizeGroup"
class="bordered q-pl-md"
>
2024-07-17 07:32:56 +00:00
<q-list>
<q-item
v-for="v in [10, 30, 50, 100, 500, 1000]"
:key="v"
clickable
v-close-popup
@click="
async () => {
pageSizeGroup = v;
await fetchListGroups();
}
"
>
<q-item-section>
<q-item-label>{{ v }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
</div>
2024-07-17 07:32:56 +00:00
<div class="col-4 row justify-center app-text-muted">
{{
$t('recordsPage', {
resultcurrentPage: productGroup?.length,
total: totalGroup,
})
}}
</div>
2024-07-17 07:32:56 +00:00
<div class="col-4 row justify-end">
<PaginationComponent
v-model:current-page="currentPageGroup"
v-model:max-page="maxPageGroup"
:fetch-data="
async () => {
await fetchListGroups();
flowStore.rotate();
}
"
/>
</div>
</div>
<div
v-if="productMode === 'type'"
class="row justify-between items-center q-px-md q-py-sm full-width"
>
<div class="col-4">
<div class="row items-center">
2024-07-18 09:10:51 +00:00
<div
class="app-text-muted"
style="width: 80px"
v-if="$q.screen.gt.sm"
>
2024-07-17 07:32:56 +00:00
{{ $t('showing') }}
</div>
<div>
<q-btn-dropdown
dense
unelevated
:label="pageSizeType"
class="bordered q-pl-md"
>
<q-list>
<q-item
v-for="v in [10, 30, 50, 100, 500, 1000]"
:key="v"
clickable
v-close-popup
@click="
async () => {
pageSizeType = v;
await fetchListType();
}
"
>
<q-item-section>
<q-item-label>{{ v }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
</div>
<div class="col-4 row justify-center app-text-muted">
{{
$t('recordsPage', {
resultcurrentPage: productType?.length,
total: totalType,
})
}}
</div>
<div class="col-4 row justify-end">
<PaginationComponent
v-model:current-page="currentPageType"
v-model:max-page="maxPageType"
:fetch-data="
async () => {
await fetchListType();
flowStore.rotate();
}
"
/>
</div>
</div>
</template>
</div>
<!-- product/service -->
<div
v-else-if="productMode === 'service'"
class="surface-1 col column no-wrap"
style="overflow: hidden"
>
<div
class="row justify-between items-center q-px-md q-py-sm surface-3 bordered-b"
>
<q-input
lazy-rules="ondemand"
2024-07-17 07:32:56 +00:00
:class="{ 'col-12': $q.screen.lt.md }"
outlined
dense
unelavated
:label="$t('search')"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearchProductAndService"
debounce="250"
>
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
2024-07-17 07:32:56 +00:00
<div
class="row col-12 col-md-6"
:class="{ 'q-pt-xs': $q.screen.lt.md }"
style="white-space: nowrap"
>
2024-07-24 06:40:03 +00:00
<q-select
lazy-rules="ondemand"
2024-07-24 06:40:03 +00:00
v-model="currentStatus"
outlined
dense
option-value="value"
:hide-dropdown-icon="$q.screen.lt.sm"
option-label="label"
class="col"
map-options
emit-value
:options="[
{ label: $t('all'), value: 'All' },
{ label: $t('statusACTIVE'), value: 'ACTIVE' },
{
label: $t('statusINACTIVE'),
value: 'INACTIVE',
},
]"
@update:model-value="fetchStatus()"
></q-select>
2024-07-17 07:32:56 +00:00
<q-select
lazy-rules="ondemand"
2024-07-17 07:32:56 +00:00
v-if="modeView === false"
2024-07-18 09:10:51 +00:00
:hide-dropdown-icon="$q.screen.lt.sm"
2024-07-17 07:32:56 +00:00
id="select-field"
for="select-field"
2024-07-18 09:10:51 +00:00
class="col q-ml-sm"
2024-07-17 07:32:56 +00:00
:options="
{
product: tbControl.product.fieldDisplay,
service: tbControl.service.fieldDisplay,
}[productAndServiceTab].map((x) => ({
label: $t(x),
value: x,
}))
"
:display-value="$t('displayField')"
:model-value="
{
product: tbControl.product.fieldSelected,
service: tbControl.service.fieldSelected,
}[productAndServiceTab]
"
@update:model-value="
(v) => (tbControl[productAndServiceTab].fieldSelected = v)
"
option-label="label"
option-value="value"
map-options
emit-value
outlined
multiple
dense
/>
2024-07-17 07:32:56 +00:00
<q-btn-toggle
v-model="modeView"
id="btn-mode"
dense
2024-07-18 09:10:51 +00:00
class="no-shadow bordered rounded surface-1 q-ml-sm"
2024-07-17 07:32:56 +00:00
:toggle-color="$q.dark.isActive ? 'grey-9' : 'grey-2'"
size="xs"
:options="[
{ value: true, slot: 'folder' },
{ value: false, slot: 'list' },
]"
>
<template v-slot:folder>
<q-icon
name="mdi-view-grid-outline"
size="16px"
class="q-px-sm q-py-xs rounded"
:style="{
color: $q.dark.isActive
? modeView
? '#C9D3DB '
: '#787B7C'
: modeView
? '#787B7C'
: '#C9D3DB',
}"
/>
</template>
<template v-slot:list>
<q-icon
name="mdi-format-list-bulleted"
class="q-px-sm q-py-xs rounded"
size="16px"
:style="{
color: $q.dark.isActive
? modeView === false
? '#C9D3DB'
: '#787B7C'
: modeView === false
? '#787B7C'
: '#C9D3DB',
}"
/>
2024-07-17 07:32:56 +00:00
</template>
</q-btn-toggle>
</div>
</div>
<div class="surface-2 bordered-b q-px-md">
<q-tabs
dense
v-model="productAndServiceTab"
active-color="info"
align="left"
>
<q-tab
2024-07-19 14:04:02 +07:00
id="tab-service"
2024-07-17 07:32:56 +00:00
name="service"
@click="
async () => {
currentPageServiceAndProduct = 1;
inputSearchProductAndService = '';
currentStatus = 'All';
// await fetchListOfService();
flowStore.rotate();
}
"
>
<div
class="row"
:class="
productAndServiceTab === 'service'
? 'text-bold'
: 'app-text-muted'
"
>
{{ $t('service') }}
</div>
2024-07-17 07:32:56 +00:00
</q-tab>
<q-tab
2024-07-19 14:04:02 +07:00
id="tab-product"
2024-07-17 07:32:56 +00:00
name="product"
@click="
async () => {
currentPageServiceAndProduct = 1;
inputSearchProductAndService = '';
currentStatus = 'All';
// await fetchListOfProduct();
flowStore.rotate();
}
"
>
<div
class="row"
:class="
productAndServiceTab === 'product'
? 'text-bold'
: 'app-text-muted'
"
>
{{ $t('product') }}
</div>
</q-tab>
</q-tabs>
</div>
<div
2024-07-17 07:32:56 +00:00
v-if="
(productAndServiceTab === 'product' &&
product?.length === 0) ||
(productAndServiceTab === 'service' && service?.length === 0)
"
class="flex justify-center items-center surface-2 col"
>
2024-07-17 07:32:56 +00:00
<NoData :not-found="!!inputSearchProductAndService" />
</div>
<!-- tab -->
<template v-else>
<div
class="flex scroll full-width q-pa-md surface-2 column col"
>
<q-table
class="full-width"
flat
bordered
:rows-per-page-options="[0]"
:rows="{ product, service }[productAndServiceTab] || []"
:columns="
{
product: tbColumn.product,
service: tbColumn.service,
}[productAndServiceTab]
"
:grid="modeView"
card-container-class="row q-col-gutter-md"
row-key="name"
hide-pagination
:visible-columns="
{
product: tbControl.product.fieldSelected,
service: tbControl.service.fieldSelected,
}[productAndServiceTab]
"
>
<template v-slot:header="props">
<q-tr class="surface-2" :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ $t(col.label) }}
</q-th>
<q-th auto-width />
</q-tr>
</template>
<template v-slot:body="props">
<q-tr
:class="{
'app-text-muted': props.row.status === 'INACTIVE',
}"
:props="props"
>
2024-07-17 07:32:56 +00:00
<q-td
v-if="
{
product: tbControl.product.fieldSelected,
service: tbControl.service.fieldSelected,
}[productAndServiceTab].includes(
{
product: 'productName',
service: 'serviceName',
}[productAndServiceTab],
)
"
>
<div class="row items-center">
<div
:class="{
'status-active':
props.row.status !== 'INACTIVE',
'status-inactive':
props.row.status === 'INACTIVE',
}"
style="
width: 50px;
display: flex;
margin-bottom: var(--size-2);
"
>
<div
class="table__icon"
:class="`icon-color-${productAndServiceTab === 'product' ? 'green' : 'orange'}`"
>
<q-icon
size="md"
style="scale: 0.8"
:name="
productAndServiceTab === 'product'
? 'mdi-folder'
: 'mdi-server-network'
"
/>
</div>
</div>
<div class="col">
<div class="col">{{ props.row.name }}</div>
<div class="col app-text-muted">
{{ props.row.code }}
</div>
</div>
</div>
</q-td>
<q-td
v-if="
{
product: tbControl.product.fieldSelected,
service: tbControl.service.fieldSelected,
}[productAndServiceTab].includes(
{
product: 'productDetail',
service: 'serviceDetail',
}[productAndServiceTab],
)
"
>
{{ props.row.detail }}
</q-td>
<q-td
v-if="
{
product: tbControl.product.fieldSelected,
service: tbControl.service.fieldSelected,
}[productAndServiceTab].includes(
{
product: 'productProcessingTime',
service: 'serviceWorkTotal',
}[productAndServiceTab],
)
"
>
{{
{
product: props.row.process,
service: props.row.work?.length,
}[productAndServiceTab]
}}
</q-td>
<q-td
v-if="
{
product: tbControl.product.fieldSelected,
service: tbControl.service.fieldSelected,
}[productAndServiceTab].includes(
{
product: 'priceInformation',
service: 'empty',
}[productAndServiceTab],
)
"
>
<div
class="row tags-color-green q-gutter-x-md no-wrap items-center text-right"
>
<div
class="tags col-3 column ellipsis-2-lines"
style="min-width: 50px"
>
<div class="col-4">{{ $t('salePrice') }}</div>
<div class="col-4 text-weight-bold">
{{
formatNumberDecimal(props.row.price || 0, 2)
}}
</div>
</div>
<div
class="tags col-3 column ellipsis-2-lines"
style="min-width: 50px"
>
<div class="col-4">{{ $t('agentPrice') }}</div>
<div class="col-4 text-weight-bold">
{{
formatNumberDecimal(
props.row.agentPrice || 0,
2,
)
}}
</div>
</div>
<div
class="tags col-3 column ellipsis-2-lines"
style="min-width: 50px"
>
<div class="col-4">
{{ $t('processingPrice') }}
</div>
<div class="col-4 text-weight-bold">
{{
formatNumberDecimal(
props.row.serviceCharge || 0,
2,
)
}}
</div>
</div>
</div>
</q-td>
<q-td
v-if="
{
product: tbControl.product.fieldSelected,
service: tbControl.service.fieldSelected,
}[productAndServiceTab].includes('createdAt')
"
>
{{ dateFormat(props.row.createdAt) }}
</q-td>
<q-td>
<q-btn
icon="mdi-eye-outline"
:id="`btn-eye-${props.row.name}`"
size="sm"
dense
round
flat
@click.stop="
async () => {
2024-07-17 07:32:56 +00:00
if (props.row.type === 'product') {
currentIdProduct = props.row.id;
assignFormDataProduct(props.row);
dialogProductEdit = true;
}
if (props.row.type === 'service') {
currentIdService = props.row.id;
infoServiceEdit = false;
assignFormDataProductService(props.row.id);
dialogServiceEdit = true;
}
await fetchListOfOptionBranch();
}
"
2024-07-17 07:32:56 +00:00
/>
<q-btn
icon="mdi-dots-vertical"
:id="`btn-dots-${props.row.name}`"
size="sm"
dense
round
flat
@click.stop
>
2024-07-17 07:32:56 +00:00
<q-menu class="bordered">
<q-list v-close-popup>
<q-item
:id="`view-detail-btn-${props.row.name}-view`"
@click.stop="
async () => {
if (props.row.type === 'product') {
currentIdProduct = props.row.id;
assignFormDataProduct(props.row);
dialogProductEdit = true;
}
if (props.row.type === 'service') {
currentIdService = props.row.id;
infoServiceEdit = false;
assignFormDataProductService(
props.row.id,
);
dialogServiceEdit = true;
}
await fetchListOfOptionBranch();
}
"
v-close-popup
clickable
dense
class="row q-py-sm"
style="white-space: nowrap"
>
<q-icon
name="mdi-eye-outline"
class="col-3"
size="xs"
style="color: hsl(var(--green-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('viewDetail') }}
</span>
</q-item>
<q-item
:id="`view-detail-btn-${props.row.name}-edit`"
v-close-popup
clickable
dense
class="row q-py-sm"
style="white-space: nowrap"
@click="
async () => {
if (props.row.type === 'product') {
currentIdProduct = props.row.id;
infoProductEdit = true;
assignFormDataProduct(props.row);
dialogProductEdit = true;
}
if (props.row.type === 'service') {
currentIdService = props.row.id;
infoServiceEdit = true;
assignFormDataProductService(
props.row.id,
);
dialogServiceEdit = true;
}
await fetchListOfOptionBranch();
}
"
>
<q-icon
name="mdi-pencil-outline"
class="col-3"
size="xs"
style="color: hsl(var(--cyan-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('edit') }}
</span>
</q-item>
<q-item
:id="`view-detail-btn-${props.row.name}-delete`"
dense
v-close-popup
:clickable="props.row.status === 'CREATED'"
class="row"
:class="{
'surface-3': props.row.status !== 'CREATED',
'app-text-muted':
props.row.status !== 'CREATED',
}"
style="white-space: nowrap"
@click="
() => {
if (props.row.type === 'product') {
deleteTypeOfProduct(props.row.id);
}
if (props.row.type === 'service') {
deleteServiceById(props.row.id);
}
}
"
>
<q-icon
name="mdi-trash-can-outline"
size="xs"
class="col-3"
:class="{
'app-text-negative':
props.row.status === 'CREATED',
}"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('delete') }}
</span>
</q-item>
<q-item dense>
<q-item-section class="q-py-sm">
<div class="q-pa-sm surface-2 rounded">
<q-toggle
:id="`view-detail-btn-${props.row.name}-status`"
dense
size="sm"
:label="
props.row.status !== 'INACTIVE'
? $t('switchOnLabel')
: $t('switchOffLabel')
"
@click="
() => {
2024-07-25 10:49:43 +00:00
triggerChangeStatus(
props.row.id,
props.row.status,
props.row.type,
);
2024-07-17 07:32:56 +00:00
}
"
:model-value="
props.row.status === 'CREATED' ||
props.row.status === 'ACTIVE'
"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-td>
</q-tr>
</template>
<template v-slot:item="{ row }">
<div class="col-12 col-md-6 col-lg-4">
<TotalProductCardComponent
:data="row"
:key="row.id"
:title="row.name"
:isDisabled="row.status === 'INACTIVE' ? true : false"
@toggleStatus="
() => {
2024-07-25 10:49:43 +00:00
triggerChangeStatus(row.id, row.status, row.type);
2024-07-17 07:32:56 +00:00
}
"
@menuViewDetail="
async () => {
if (row.type === 'product') {
currentIdProduct = row.id;
assignFormDataProduct(row);
dialogProductEdit = true;
}
if (row.type === 'service') {
currentIdService = row.id;
infoServiceEdit = false;
assignFormDataProductService(row.id);
dialogServiceEdit = true;
}
await fetchListOfOptionBranch();
}
"
@menuEdit="
async () => {
if (row.type === 'product') {
currentIdProduct = row.id;
infoProductEdit = true;
assignFormDataProduct(row);
dialogProductEdit = true;
}
if (row.type === 'service') {
currentIdService = row.id;
infoServiceEdit = true;
assignFormDataProductService(row.id);
dialogServiceEdit = true;
}
2024-07-17 07:32:56 +00:00
await fetchListOfOptionBranch();
}
"
/>
</div>
</template>
</q-table>
</div>
2024-07-17 07:32:56 +00:00
</template>
<!-- footer -->
<div
2024-07-17 07:32:56 +00:00
class="row items-center justify-between q-py-sm q-px-md surface-2"
>
<div class="col-4">
<div class="row items-center">
2024-07-18 09:10:51 +00:00
<div
class="app-text-muted"
style="width: 80px"
v-if="$q.screen.gt.sm"
>
{{ $t('showing') }}
</div>
<div>
<q-btn-dropdown
dense
unelevated
2024-07-17 07:32:56 +00:00
:label="pageSizeServiceAndProduct"
class="bordered q-pl-md"
>
<q-list>
<q-item
2024-07-23 14:01:30 +07:00
v-for="v in [10, 30, 50, 100, 500, 1000]"
:key="v"
clickable
v-close-popup
@click="
async () => {
2024-07-17 07:32:56 +00:00
pageSizeServiceAndProduct = v;
if (productAndServiceTab === 'product') {
await fetchListOfProduct();
}
if (productAndServiceTab === 'service') {
await fetchListOfService();
}
}
"
>
<q-item-section>
<q-item-label>{{ v }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
</div>
<div class="col-4 row justify-center app-text-muted">
{{
$t('recordsPage', {
2024-07-17 07:32:56 +00:00
resultcurrentPage:
productAndServiceTab === 'product'
? product?.length
: service?.length,
total: total,
})
}}
</div>
<div class="col-4 row justify-end">
<PaginationComponent
2024-07-17 07:32:56 +00:00
v-model:current-page="currentPageServiceAndProduct"
v-model:max-page="maxPageServiceAndProduct"
:fetch-data="
async () => {
2024-07-17 07:32:56 +00:00
await alternativeFetch();
flowStore.rotate();
}
"
/>
</div>
</div>
</div>
2024-06-28 14:14:23 +07:00
</div>
2024-07-17 07:32:56 +00:00
</template>
</q-splitter>
</div>
</div>
<FormDialog
v-model:modal="dialogInputForm"
noAddress
2024-06-12 15:37:17 +07:00
:title="$t('addProduct')"
:submit="() => (productMode === 'type' ? submitType() : submitGroup())"
:close="clearFormGroup"
>
<template #information>
<BasicInformation
dense
2024-06-11 16:52:35 +07:00
:isType="productMode === 'type'"
2024-07-26 10:46:23 +00:00
v-model:options-branch="branchOption"
v-model:remark="formDataGroup.remark"
v-model:name="formDataGroup.name"
v-model:detail="formDataGroup.detail"
/>
</template>
</FormDialog>
<DrawerInfo
:show-edit="!currentStatusProduct"
ref="formDialogRef"
v-model:drawerOpen="drawerInfo"
2024-06-28 14:14:23 +07:00
:title="
2024-07-15 14:12:53 +07:00
$t(
editByTree === 'group'
? 'productGroup'
: editByTree === 'type'
? 'productType'
: productMode === 'group'
? 'productGroup'
2024-07-23 07:25:11 +00:00
: 'productType',
2024-07-15 14:12:53 +07:00
{
name: formDataGroup.code,
},
)
2024-06-28 14:14:23 +07:00
"
:undo="() => undoProductGroup()"
:isEdit="isEdit"
:editData="() => (isEdit = true)"
2024-07-15 14:12:53 +07:00
:submit="
() => {
if (editByTree !== undefined) {
if (editByTree === 'group') {
submitGroup();
}
if (editByTree === 'type') {
submitType();
}
editByTree = undefined;
} else {
if (productMode === 'group') {
submitGroup();
}
if (productMode === 'type') {
submitType();
}
}
}
"
:delete-data="() => deleteProductById()"
:close="() => (drawerInfo = false)"
>
<template #info>
2024-06-27 04:55:39 +00:00
<InfoForm noAddress>
<template #information>
<!-- <AppBox class="q-ma-md" bordered> -->
<BasicInformation
dense
:isType="productMode === 'type'"
:readonly="!isEdit"
2024-07-26 10:46:23 +00:00
v-model:options-branch="branchOption"
2024-06-27 04:55:39 +00:00
v-model:remark="formDataGroup.remark"
v-model:name="formDataGroup.name"
v-model:code="formDataGroup.code"
v-model:detail="formDataGroup.detail"
/>
<!-- </AppBox> -->
</template>
</InfoForm>
</template>
</DrawerInfo>
<FormDialog
v-model:modal="dialogProductServiceType"
:title="'สร้างสินค้าและบริการ'"
no-footer
no-app-box
2024-06-18 11:47:53 +00:00
max-width="80%"
:close="() => (dialogProductServiceType = false)"
>
<template #body>
<div class="row q-gutter-xl q-pa-sm">
<ItemCard
class="col"
v-for="i in itemCard"
changeColor
:key="i.text"
:icon="i.icon"
:text="i.text"
:color="i.color"
@trigger="
() => {
dialogProductServiceType = false;
}
"
/>
</div>
</template>
</FormDialog>
<FormDialog
v-model:modal="dialogTotalProduct"
noAddress
noAppBox
2024-06-19 18:00:38 +07:00
title="สินค้าทั้งหมด"
:submit="
() => {
2024-06-20 18:02:02 +07:00
selectProduct.forEach((i) => {
workItems[currentWorkIndex].product.push({
...i,
nameEn: '',
});
});
dialogTotalProduct = false;
selectProduct = [];
}
"
:close="
() => {
dialogTotalProduct = false;
inputSearchProductAndService = '';
}
"
>
<div class="full-width full-height">
<div class="row items-center">
2024-06-19 18:00:38 +07:00
<q-space />
<q-input
lazy-rules="ondemand"
2024-07-19 14:04:02 +07:00
id="input-search-add-product"
2024-06-19 18:00:38 +07:00
outlined
2024-07-09 11:54:03 +07:00
style="width: 250px"
2024-06-19 18:00:38 +07:00
dense
:label="$t('search')"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearchProductAndService"
2024-06-19 18:00:38 +07:00
debounce="500"
@update:model-value="searchProduct()"
2024-06-19 18:00:38 +07:00
>
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
2024-06-13 15:59:09 +07:00
</div>
<div>
2024-07-09 11:54:03 +07:00
<AppBox
no-padding
class="row q-mt-sm q-col-gutter-sm"
style="background: none"
>
<div
class="col-12 text-center"
v-if="resultSearchProduct?.length === 0"
>
<NoData />
</div>
<div
:class="`${$q.screen.gt.sm ? 'col-3 ' : $q.screen.gt.xs ? 'col-6' : 'col-12'}`"
v-for="i in (!!inputSearchProductAndService
? resultSearchProduct
: productIsAdd
)?.filter((i) => {
if (i.status === 'INACTIVE') {
return false;
}
return true;
})"
:key="i.id"
>
<TotalProductCardComponent
:index="selectProduct.findIndex((v) => v.id === i.id)"
:isAddProduct="!!selectProduct.find((v) => v.id === i.id)"
:isSelected="true"
:data="{ ...i, type: 'product' }"
:title="i.name"
:status="i.status === 'INACTIVE' ? true : false"
@menuViewDetail="
() => {
currentIdProduct = i.id;
assignFormDataProduct(i);
dialogProductEdit = true;
}
"
@menuEdit="
() => {
currentIdProduct = i.id;
infoProductEdit = true;
assignFormDataProduct(i);
dialogProductEdit = true;
}
"
@viewDetail="
() => {
currentIdProduct = i.id;
infoProductEdit = false;
assignFormDataProduct(i);
dialogProductEdit = true;
}
"
@select="
(data) => {
const tempValue = selectProduct.find((v) => v.id === i.id);
if (tempValue) {
selectProduct = selectProduct.filter((v) => v.id !== i.id);
} else {
selectProduct.push(data);
}
}
"
/>
</div>
</AppBox>
</div>
2024-06-19 18:00:38 +07:00
</div>
</FormDialog>
2024-06-14 14:45:17 +07:00
<!-- Add Product -->
2024-06-14 14:45:17 +07:00
<FormDialog
v-model:modal="dialogProduct"
noAddress
:title="$t('buttonAddProduct')"
2024-06-14 14:45:17 +07:00
:submit="
() => {
submitProduct();
}
"
:close="
() => {
dialogProduct = false;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
"
>
<template #prepend>
<ProfileUpload
2024-07-25 14:44:32 +07:00
prefix-id="form-dialog-product"
isProduct
v-model:url-profile="profileUrl"
v-model:status-toggle="statusToggle"
v-model:profile-submit="profileSubmit"
@input-file="inputFile.click()"
@cancel-file="inputFile.value = ''"
/>
</template>
2024-06-21 08:49:20 +00:00
<template #information>
<BasicInfoProduct
2024-07-26 10:46:23 +00:00
v-model:options-branch="branchOption"
v-model:registered-branch-id="formDataProduct.registeredBranchId"
v-model:detail="formDataProduct.detail"
v-model:remark="formDataProduct.remark"
v-model:name="formDataProduct.name"
v-model:code="formDataProduct.code"
2024-06-18 16:35:40 +07:00
v-model:process="formDataProduct.process"
dense
separator
/>
2024-06-21 08:49:20 +00:00
</template>
<template #person>
<PriceDataComponent
v-model:price="formDataProduct.price"
v-model:agent-price="formDataProduct.agentPrice"
v-model:service-charge="formDataProduct.serviceCharge"
dense
/>
2024-06-21 08:49:20 +00:00
</template>
</FormDialog>
<!-- edit product -->
<FormDialog
:edit="!(formDataProduct.status === 'INACTIVE')"
:isEdit="infoProductEdit"
v-model:modal="dialogProductEdit"
noAddress
:title="$t('editProduct')"
:editData="() => (infoProductEdit = true)"
:undo="
() => {
formDataProduct = { ...prevProduct };
infoProductEdit = false;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
}
"
:deleteData="
() => {
deleteTypeOfProduct();
}
"
:submit="
() => {
submitProduct();
2024-06-14 14:45:17 +07:00
}
"
:close="
() => {
dialogProductEdit = false;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-14 14:45:17 +07:00
}
"
>
<template #prepend>
<ProfileUpload
2024-07-25 14:44:32 +07:00
prefix-id="form-dialog-product"
2024-06-14 14:45:17 +07:00
isProduct
v-model:url-profile="profileUrl"
v-model:status-toggle="statusToggle"
v-model:profile-submit="profileSubmit"
@input-file="inputFile.click()"
@cancel-file="inputFile.value = ''"
:label-status-toggle="'formDialogTitleProductStatus'"
2024-06-14 14:45:17 +07:00
/>
</template>
2024-06-21 08:49:20 +00:00
<template #information>
<BasicInfoProduct
:readonly="!infoProductEdit"
2024-07-26 10:46:23 +00:00
v-model:options-branch="branchOption"
v-model:registered-branch-id="formDataProduct.registeredBranchId"
v-model:detail="formDataProduct.detail"
v-model:remark="formDataProduct.remark"
v-model:name="formDataProduct.name"
v-model:code="formDataProduct.code"
v-model:process="formDataProduct.process"
dense
separator
/>
2024-06-21 08:49:20 +00:00
</template>
<template #person>
<PriceDataComponent
:readonly="!infoProductEdit"
v-model:price="formDataProduct.price"
v-model:agent-price="formDataProduct.agentPrice"
v-model:service-charge="formDataProduct.serviceCharge"
dense
/>
2024-06-21 08:49:20 +00:00
</template>
2024-06-14 14:45:17 +07:00
</FormDialog>
2024-06-18 02:01:02 +00:00
2024-06-18 11:47:53 +00:00
<!-- add service -->
2024-06-18 02:01:02 +00:00
<FormDialog
no-address
2024-06-25 09:35:06 +00:00
no-app-box
2024-06-21 08:49:20 +00:00
height="95vh"
2024-06-18 02:01:02 +00:00
:title="$t('addService')"
v-model:modal="dialogService"
2024-06-28 02:21:21 +00:00
:tabs-list="serviceTab"
2024-06-18 02:01:02 +00:00
v-model:current-tab="currentServiceTab"
2024-06-21 11:40:18 +07:00
:submit="
() => {
submitService();
}
"
:close="
() => {
clearFormService();
2024-06-21 11:40:18 +07:00
dialogService = false;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-21 11:40:18 +07:00
}
"
2024-06-18 02:01:02 +00:00
>
<template #prepend>
<ProfileUpload
2024-07-25 14:44:32 +07:00
prefix-id="form-dialog-service"
2024-06-18 02:28:29 +00:00
isService
2024-06-18 02:01:02 +00:00
v-model:url-profile="profileUrl"
v-model:status-toggle="statusToggle"
v-model:profile-submit="profileSubmit"
@cancel-file="inputFile.value = ''"
2024-06-18 02:01:02 +00:00
@input-file="inputFile.click()"
/>
</template>
2024-07-01 08:40:37 +00:00
<div
v-if="currentServiceTab === 'serviceInformation'"
class="col-md-10 col-sm-12"
>
2024-06-25 09:35:06 +00:00
<div class="surface-1 rounded bordered q-pa-lg full-width row">
<BasicInformation
dense
service
2024-07-26 10:46:23 +00:00
v-model:options-branch="branchOption"
v-model:registered-branch-id="
formDataProductService.registeredBranchId
"
2024-06-25 09:35:06 +00:00
v-model:service-code="formDataProductService.code"
v-model:service-description="formDataProductService.detail"
v-model:service-name-th="formDataProductService.name"
/>
</div>
<div class="surface-1 rounded bordered q-mt-md q-pa-lg row">
<FormServiceProperties
v-model:service-attributes="formDataProductService.attributes"
@service-properties="
() => {
tempValueProperties = formDataProductService.attributes;
openPropertiesDialog('service');
}
"
/>
</div>
</div>
<div
v-if="currentServiceTab === 'workInformation'"
2024-07-01 08:40:37 +00:00
class="surface-1 rounded bordered col-md-10 col-sm-12 q-pa-lg"
2024-06-25 09:35:06 +00:00
>
2024-06-18 02:01:02 +00:00
<FormServiceWork
2024-06-20 18:02:02 +07:00
v-model:work-items="workItems"
2024-06-18 02:01:02 +00:00
dense
2024-06-20 18:02:02 +07:00
@addProduct="
async (index) => {
await fetchListOfProductIsAdd(currentIdType);
2024-06-20 18:02:02 +07:00
currentWorkIndex = index;
dialogTotalProduct = true;
}
"
@manage-work-name="
() => {
manageWorkNameDialog = true;
}
"
2024-06-20 18:02:02 +07:00
@work-properties="
(index) => {
currentWorkIndex = index;
tempValueProperties = workItems[index].attributes;
openPropertiesDialog('work');
}
"
2024-06-18 02:01:02 +00:00
/>
2024-06-25 09:35:06 +00:00
</div>
2024-06-18 02:01:02 +00:00
</FormDialog>
2024-06-18 11:47:53 +00:00
<!-- service properties -->
<FormDialog
no-address
no-app-box
height="75vh"
2024-06-18 11:47:53 +00:00
width="75%"
2024-06-24 04:45:58 +00:00
:title="$t('properties')"
2024-06-18 11:47:53 +00:00
v-model:modal="propertiesDialog"
2024-06-20 18:02:02 +07:00
:submit="
() => {
if (currentPropertiesMode === 'service') {
formDataProductService.attributes = tempValueProperties;
}
if (currentPropertiesMode === 'work') {
workItems[currentWorkIndex].attributes = tempValueProperties;
}
propertiesDialog = false;
}
"
:close="
() => {
propertiesDialog = false;
}
"
2024-06-18 11:47:53 +00:00
>
<ServiceProperties
v-model:properties-option="propertiesOption"
2024-06-20 18:02:02 +07:00
v-model:form-service-properties="tempValueProperties"
2024-06-18 11:47:53 +00:00
/>
</FormDialog>
<!-- manage work name -->
<FormDialog
no-address
no-app-box
2024-06-21 11:06:35 +00:00
no-footer
height="65vh"
width="65%"
v-model:modal="manageWorkNameDialog"
:title="$t('manage')"
:close="triggerConfirmCloseWork"
>
2024-06-21 11:06:35 +00:00
<WorkNameManagement
ref="workNameRef"
v-model:nameList="workNameItems"
@delete="confirmDeleteWork"
@edit="editWork"
@add="createWork"
/>
</FormDialog>
<!-- edit service -->
<FormDialog
no-address
2024-06-25 11:04:34 +00:00
:no-app-box="currentServiceTab !== 'workInformation'"
2024-06-24 16:53:33 +07:00
:edit="!(formDataProductService.status === 'INACTIVE')"
2024-06-24 10:13:23 +00:00
height="95vh"
:isEdit="infoServiceEdit"
:title="$t('service')"
2024-06-28 02:21:21 +00:00
:tabs-list="serviceTab"
v-model:modal="dialogServiceEdit"
v-model:current-tab="currentServiceTab"
:submit="
() => {
submitService();
}
"
:close="
() => {
clearFormService();
2024-06-26 17:21:26 +07:00
flowStore.rotate();
dialogServiceEdit = false;
}
"
:edit-data="
() => {
infoServiceEdit = true;
}
"
2024-06-24 16:53:33 +07:00
:undo="
() => {
infoServiceEdit = false;
2024-06-26 17:44:13 +07:00
cloneData();
2024-06-24 16:53:33 +07:00
statusToggle = prevService.status === 'INACTIVE' ? false : true;
2024-06-26 17:21:26 +07:00
flowStore.rotate();
2024-06-24 16:53:33 +07:00
}
"
:delete-data="() => deleteServiceById()"
>
<template #prepend>
<ProfileUpload
2024-07-25 14:44:32 +07:00
prefix-id="form-dialog-service"
isService
2024-07-01 11:04:24 +00:00
:readonly="!infoServiceEdit"
v-model:url-profile="profileUrl"
v-model:status-toggle="statusToggle"
v-model:profile-submit="profileSubmit"
@cancel-file="inputFile.value = ''"
@input-file="inputFile.click()"
/>
</template>
2024-06-25 11:04:34 +00:00
<div
v-if="currentServiceTab === 'serviceInformation'"
class="col-md-10 col-sm-12"
>
2024-06-25 09:35:06 +00:00
<div class="surface-1 rounded bordered q-pa-lg full-width row">
<BasicInformation
:readonly="!infoServiceEdit"
v-if="currentServiceTab === 'serviceInformation'"
dense
service
2024-07-26 10:46:23 +00:00
v-model:options-branch="branchOption"
v-model:registered-branch-id="
formDataProductService.registeredBranchId
"
2024-06-25 09:35:06 +00:00
v-model:service-code="formDataProductService.code"
v-model:service-description="formDataProductService.detail"
v-model:service-name-th="formDataProductService.name"
/>
</div>
<div class="surface-1 rounded bordered q-mt-md q-pa-lg row">
<FormServiceProperties
:readonly="!infoServiceEdit"
v-model:service-attributes="formDataProductService.attributes"
@service-properties="
() => {
tempValueProperties = formDataProductService.attributes;
openPropertiesDialog('service');
}
"
/>
</div>
</div>
2024-06-25 11:04:34 +00:00
<template #information>
<FormServiceWork
:readonly="!infoServiceEdit"
v-model:work-items="workItems"
v-if="currentServiceTab === 'workInformation'"
dense
@addProduct="
async (index) => {
2024-07-09 11:54:03 +07:00
await fetchListOfProductIsAdd(currentIdType);
currentWorkIndex = index;
dialogTotalProduct = true;
}
"
@manage-work-name="manageWorkNameDialog = true"
@work-properties="
(index) => {
currentWorkIndex = index;
tempValueProperties = workItems[index].attributes;
openPropertiesDialog('work');
}
"
/>
2024-06-25 11:04:34 +00:00
</template>
</FormDialog>
<q-dialog v-model="holdDialog" position="bottom">
<div class="surface-1 full-width rounded column q-pb-md">
<div class="flex q-py-sm justify-center full-width">
<div
class="rounded"
style="
width: 8%;
height: 4px;
background-color: hsla(0, 0%, 50%, 0.75);
"
></div>
</div>
<q-list v-if="currentNode">
<q-item
clickable
v-ripple
v-close-popup
@click.stop="
() => {
if (!currentNode) return;
if (currentNode.type === 'type') {
editByTree = 'type';
currentStatusProduct = currentNode.status === 'INACTIVE';
clearFormGroup();
currentIdType = currentNode.id;
assignFormDataGroup(currentNode);
isEdit = false;
drawerInfo = true;
}
if (currentNode.type === 'group') {
editByTree = 'group';
currentStatusProduct = currentNode.status === 'INACTIVE';
clearFormGroup();
assignFormDataGroup(currentNode);
isEdit = false;
currentIdGrop = currentNode.id;
drawerInfo = true;
}
}
"
>
<q-item-section avatar>
<q-icon
name="mdi-eye-outline"
style="color: hsl(var(--green-6-hsl))"
/>
</q-item-section>
<q-item-section>{{ $t('viewDetail') }}</q-item-section>
</q-item>
<q-item
clickable
v-ripple
v-close-popup
@click.stop="
() => {
if (!currentNode) return;
editByTree = currentNode.type as 'type' | 'group';
if (currentNode.type === 'type') {
clearFormGroup();
currentIdType = currentNode.id;
assignFormDataGroup(currentNode);
isEdit = true;
drawerInfo = true;
}
if (currentNode.type === 'group') {
clearFormGroup();
assignFormDataGroup(currentNode);
isEdit = true;
currentIdGrop = currentNode.id;
drawerInfo = true;
}
}
"
>
<q-item-section avatar>
<q-icon
name="mdi-pencil-outline"
style="color: hsl(var(--cyan-6-hsl))"
/>
</q-item-section>
<q-item-section>{{ $t('edit') }}</q-item-section>
</q-item>
<q-item
clickable
v-ripple
v-close-popup
@click.stop="
() => {
if (!currentNode) return;
editByTree = currentNode.type as 'type' | 'group';
if (currentNode.type === 'type') {
deleteProductById(currentNode.id);
}
if (currentNode.type === 'group') {
deleteProductById(currentNode.id);
}
}
"
>
<q-item-section avatar>
<q-icon name="mdi-trash-can-outline" class="app-text-negative" />
</q-item-section>
<q-item-section>{{ $t('delete') }}</q-item-section>
</q-item>
<q-item clickable v-ripple>
<q-item-section avatar>
<q-toggle
dense
:id="`view-detail-btn-${currentNode.name}-status`"
size="sm"
@click.stop="
async () => {
if (!currentNode) return;
if (currentNode.type === 'type') {
2024-07-25 10:49:43 +00:00
triggerChangeStatus(
currentNode.id,
currentNode.status,
currentNode.type,
);
currentNode.status === 'ACTIVE'
? (currentNode.status = 'INACTIVE')
: (currentNode.status = 'ACTIVE');
}
if (currentNode.type === 'group') {
2024-07-25 10:49:43 +00:00
triggerChangeStatus(
currentNode.id,
currentNode.status,
currentNode.type,
);
currentNode.status === 'ACTIVE'
? (currentNode.status = 'INACTIVE')
: (currentNode.status = 'ACTIVE');
}
}
"
:model-value="currentNode.status !== 'INACTIVE'"
/>
</q-item-section>
<q-item-section>
{{
currentNode.status !== 'INACTIVE'
? $t('switchOnLabel')
: $t('switchOffLabel')
}}
</q-item-section>
</q-item>
</q-list>
</div>
</q-dialog>
2024-06-10 16:42:59 +07:00
</template>
<style scoped>
.hover-underline:hover {
cursor: pointer;
text-decoration: underline;
}
.status-active {
--_branch-status-color: var(--green-6-hsl);
}
.status-inactive {
--_branch-status-color: var(--red-4-hsl);
--_branch-badge-bg: var(--red-4-hsl);
filter: grayscale(0.5);
opacity: 0.5;
}
.icon-color-purple {
--_color: var(--violet-11-hsl);
}
.icon-color-pink {
--_color: var(--pink-6-hsl);
}
.icon-color-orange {
--_color: var(--orange-5-hsl);
}
.icon-color-green {
--_color: var(--teal-10-hsl);
}
.tags-color-green {
--_color-tag: var(--teal-10-hsl);
}
.dark .tags-color-green {
--_color-tag: var(--teal-8-hsl);
}
.dark .icon-color-purple {
--_color: var(--violet-10-hsl);
}
.dark .icon-color-green {
--_color: var(--teal-8-hsl);
}
.dark .icon-color-orange {
--_color: var(--orange-6-hsl);
}
.table__icon {
background-color: hsla(var(--_color) / 0.15);
color: hsla(var(--_color) / 1);
border-radius: 50%;
padding: var(--size-1);
position: relative;
transform: rotate(45deg);
&::after {
content: ' ';
display: block;
block-size: 0.5rem;
aspect-ratio: 1;
position: absolute;
border-radius: 50%;
right: -0.1rem;
top: calc(50% - 0.25rem);
bottom: calc(50% - 0.25rem);
background-color: hsla(var(--_branch-status-color) / 1);
}
&:deep(.q-icon) {
transform: rotate(-45deg);
color: hsla(var(--_branch-card-bg) / 1);
}
}
.tags {
display: inline-block;
color: hsla(var(--_color-tag) / 1);
background: hsla(var(--_color-tag) / 0.15);
border-radius: var(--radius-2);
padding-inline: var(--size-2);
}
2024-07-19 18:05:58 +07:00
* :deep(.q-icon.mdi-play) {
display: none;
}
</style>