5372 lines
167 KiB
Vue
5372 lines
167 KiB
Vue
<script setup lang="ts">
|
|
import { nextTick, ref, watch, reactive } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { onMounted } from 'vue';
|
|
import { storeToRefs } from 'pinia';
|
|
import { QSelect, useQuasar, type QTableProps } from 'quasar';
|
|
|
|
import DialogProperties from 'src/components/dialog/DialogProperties.vue';
|
|
import ProductCardComponent from 'components/04_product-service/ProductCardComponent.vue';
|
|
import StatCard from 'components/StatCardComponent.vue';
|
|
import DrawerInfo from 'components/DrawerInfo.vue';
|
|
import BasicInformation from 'components/04_product-service/BasicInformation.vue';
|
|
import FloatingActionButton from 'components/FloatingActionButton.vue';
|
|
import BasicInfoProduct from 'components/04_product-service/BasicInfoProduct.vue';
|
|
import PriceDataComponent from 'components/04_product-service/PriceDataComponent.vue';
|
|
import TotalProductCardComponent from 'components/04_product-service/TotalProductCardComponent.vue';
|
|
import FormServiceWork from 'components/04_product-service/FormServiceWork.vue';
|
|
import WorkNameManagement from 'components/04_product-service/WorkNameManagement.vue';
|
|
import useOptionStore from 'stores/options';
|
|
import InfoForm from 'components/02_personnel-management/InfoForm.vue';
|
|
import NoData from 'components/NoData.vue';
|
|
import PaginationComponent from 'components/PaginationComponent.vue';
|
|
import TreeComponent from 'components/TreeComponent.vue';
|
|
import DialogForm from 'components/DialogForm.vue';
|
|
import ProfileBanner from 'components/ProfileBanner.vue';
|
|
import SideMenu from 'components/SideMenu.vue';
|
|
import ImageUploadDialog from 'components/ImageUploadDialog.vue';
|
|
import FormDocument from 'src/components/04_product-service/FormDocument.vue';
|
|
import KebabAction from 'src/components/shared/KebabAction.vue';
|
|
import {
|
|
EditButton,
|
|
DeleteButton,
|
|
SaveButton,
|
|
UndoButton,
|
|
ToggleButton,
|
|
PasteButton,
|
|
} from 'components/button';
|
|
import TableProduct from 'src/components/04_product-service/TableProduct.vue';
|
|
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
|
|
|
import useFlowStore from 'stores/flow';
|
|
|
|
import { dateFormat } from 'src/utils/datetime';
|
|
import { formatNumberDecimal, isRoleInclude, notify } from 'stores/utils';
|
|
const { getWorkflowTemplate } = useWorkflowTemplate();
|
|
|
|
import { Status } from 'stores/types';
|
|
|
|
import { dialog, dialogWarningClose } from 'stores/utils';
|
|
import { useNavigator } from 'src/stores/navigator';
|
|
|
|
import useProductServiceStore from 'stores/product-service';
|
|
import {
|
|
ProductGroup,
|
|
ProductGroupCreate,
|
|
ProductCreate,
|
|
Product,
|
|
ServiceCreate,
|
|
Service,
|
|
ServiceById,
|
|
WorkItems,
|
|
Attributes,
|
|
} from 'stores/product-service/types';
|
|
import { computed } from 'vue';
|
|
import {
|
|
WorkflowStep,
|
|
WorkflowTemplate,
|
|
} from 'src/stores/workflow-template/types';
|
|
import { useWorkflowTemplate } from 'src/stores/workflow-template';
|
|
import { deepEquals } from 'src/utils/arr';
|
|
import { toRaw } from 'vue';
|
|
|
|
const flowStore = useFlowStore();
|
|
const navigatorStore = useNavigator();
|
|
const productServiceStore = useProductServiceStore();
|
|
const optionStore = useOptionStore();
|
|
|
|
const {
|
|
fetchStatsProductGroup,
|
|
fetchProductGroup,
|
|
createProductGroup,
|
|
editProductGroup,
|
|
deleteProductGroup,
|
|
|
|
fetchStatsProduct,
|
|
fetchListProduct,
|
|
createProduct,
|
|
editProduct,
|
|
deleteProduct,
|
|
|
|
fetchStatsService,
|
|
fetchListService,
|
|
fetchListServiceById,
|
|
createService,
|
|
deleteService,
|
|
editService,
|
|
|
|
createWork,
|
|
editWork,
|
|
deleteWork,
|
|
} = productServiceStore;
|
|
|
|
const currentCopy = ref<{
|
|
id: string | undefined;
|
|
type: 'service' | 'product';
|
|
}>();
|
|
const { workNameItems } = storeToRefs(productServiceStore);
|
|
const allStat = ref<{ mode: string; count: number }[]>([]);
|
|
const stat = ref<
|
|
{
|
|
icon: string;
|
|
count: number;
|
|
label: string;
|
|
mode: 'group' | 'service' | 'product';
|
|
color: 'pink' | 'green' | 'orange';
|
|
}[]
|
|
>([
|
|
{
|
|
icon: 'mdi-folder-outline',
|
|
count: 0,
|
|
label: 'productService.group.title',
|
|
mode: 'group',
|
|
color: 'pink',
|
|
},
|
|
{
|
|
icon: 'mdi-server-outline',
|
|
count: 0,
|
|
label: 'productService.service.title',
|
|
mode: 'service',
|
|
color: 'orange',
|
|
},
|
|
{
|
|
icon: 'mdi-shopping-outline',
|
|
count: 0,
|
|
label: 'productService.product.title',
|
|
mode: 'product',
|
|
color: 'green',
|
|
},
|
|
]);
|
|
|
|
const { t } = useI18n();
|
|
const baseUrl = ref<string>(import.meta.env.VITE_API_BASE_URL);
|
|
|
|
const priceDisplay = computed(() => ({
|
|
price: !isRoleInclude(['sale_agent']),
|
|
agentPrice: isRoleInclude([
|
|
'admin',
|
|
'head_of_admin',
|
|
'head_of_sale',
|
|
'system',
|
|
'owner',
|
|
'accountant',
|
|
'sale_agent',
|
|
]),
|
|
serviceCharge: isRoleInclude([
|
|
'admin',
|
|
'head_of_admin',
|
|
'system',
|
|
'owner',
|
|
'accountant',
|
|
]),
|
|
}));
|
|
const actionDisplay = computed(() =>
|
|
isRoleInclude(['admin', 'head_of_admin', 'system', 'owner', 'accountant']),
|
|
);
|
|
const splitterModel = computed(() =>
|
|
$q.screen.lt.md ? (productMode.value !== 'group' ? 0 : 100) : 25,
|
|
);
|
|
|
|
const refFilterGroup = ref<InstanceType<typeof QSelect>>();
|
|
const refFilterProductService = ref<InstanceType<typeof QSelect>>();
|
|
const holdDialog = ref(false);
|
|
const imageDialog = ref(false);
|
|
const currentNode = ref<ProductGroup & { type: string }>();
|
|
const expandedTree = ref<string[]>([]);
|
|
const editByTree = ref<'group' | 'type' | undefined>();
|
|
const formProductDocument = ref<string[]>([]);
|
|
|
|
const treeProductTypeAndGroup = computed(
|
|
() =>
|
|
productGroup.value?.map((item) => ({
|
|
...item,
|
|
_count: {
|
|
service: item._count.service,
|
|
product: item._count.product,
|
|
},
|
|
type: 'group',
|
|
actionDisabled: false,
|
|
// actionDisabled: item.status === 'INACTIVE',
|
|
children: [
|
|
{
|
|
id: 'type',
|
|
name: t('productService.service.title'),
|
|
type: 'type',
|
|
actionDisabled: true,
|
|
for: item.name,
|
|
},
|
|
{
|
|
id: 'productService',
|
|
name: t('productService.title'),
|
|
type: 'productService',
|
|
actionDisabled: true,
|
|
for: item.name,
|
|
},
|
|
],
|
|
})) ?? [],
|
|
);
|
|
|
|
const profileFileImg = ref<File | null>(null);
|
|
const refImageUpload = ref<InstanceType<typeof ImageUploadDialog>>();
|
|
|
|
const inputSearch = ref('');
|
|
const inputSearchProductAndService = ref('');
|
|
const inputSearchWorkProduct = ref('');
|
|
|
|
const currentStatusProduct = ref(false);
|
|
const drawerInfo = ref(false);
|
|
const isEdit = ref(false);
|
|
|
|
const modeView = ref(false);
|
|
|
|
const dialogInputForm = ref(false);
|
|
const dialogProduct = ref(false);
|
|
const dialogService = ref(false);
|
|
const dialogProductEdit = ref(false);
|
|
const dialogServiceEdit = ref(false);
|
|
|
|
const serviceTreeView = ref(false);
|
|
|
|
const statusToggle = ref(false);
|
|
const profileSubmit = ref(false);
|
|
const infoProductEdit = ref(false);
|
|
const infoServiceEdit = ref(false);
|
|
|
|
const imageProduct = ref<File | undefined>(undefined);
|
|
const profileUrl = ref<string | null>('');
|
|
|
|
const pathGroupName = ref('');
|
|
const pathTypeName = ref('');
|
|
|
|
const dialogTotalProduct = ref(false);
|
|
const productMode = ref<'group' | 'service' | 'product'>('group');
|
|
|
|
const productTab = ref(1);
|
|
const productGroup = ref<ProductGroup[]>();
|
|
const product = ref<(Product & { type: 'product' })[]>();
|
|
const productIsAdd = ref<(Product & { type: 'product' })[]>();
|
|
const modeViewIsAdd = ref<boolean>(false);
|
|
|
|
const service = ref<(Service & { type: 'service' })[]>();
|
|
const resultSearchProduct = ref<Product[]>([]);
|
|
|
|
const productAndServiceTab = ref<'product' | 'service'>('service');
|
|
const manageWorkNameDialog = ref(false);
|
|
const previousValue = ref();
|
|
|
|
const propertiesOption = ref();
|
|
|
|
const formDataGroup = ref<ProductGroupCreate>({
|
|
remark: '',
|
|
detail: '',
|
|
name: '',
|
|
code: '',
|
|
shared: false,
|
|
registeredBranchId: '',
|
|
});
|
|
|
|
const formProduct = ref<ProductCreate>({
|
|
expenseType: '',
|
|
vatIncluded: true,
|
|
productGroupId: '',
|
|
remark: '',
|
|
serviceCharge: 0,
|
|
calcVat: true,
|
|
agentPrice: 0,
|
|
price: 0,
|
|
process: 0,
|
|
detail: '',
|
|
name: '',
|
|
code: '',
|
|
image: undefined,
|
|
shared: false,
|
|
serviceChargeCalcVat: true,
|
|
serviceChargeVatIncluded: true,
|
|
agentPriceCalcVat: true,
|
|
agentPriceVatIncluded: true,
|
|
});
|
|
|
|
const currWorkflow = ref<WorkflowTemplate>();
|
|
const formService = ref<ServiceCreate>({
|
|
work: [],
|
|
attributes: {
|
|
showTotalPrice: false,
|
|
additional: [],
|
|
workflowId: '',
|
|
workflowStep: [],
|
|
},
|
|
detail: '',
|
|
name: '',
|
|
code: '',
|
|
productGroupId: '',
|
|
installments: 1,
|
|
});
|
|
|
|
const hideStat = ref(false);
|
|
|
|
const tbColumn = {
|
|
groupAndType: [
|
|
{
|
|
name: 'branchLabelNo',
|
|
align: 'center',
|
|
label: 'general.order',
|
|
field: 'branchNo',
|
|
},
|
|
{
|
|
name: 'name',
|
|
align: 'left',
|
|
label: 'general.name',
|
|
field: 'name',
|
|
},
|
|
{
|
|
name: 'detail',
|
|
align: 'left',
|
|
label: 'general.detail',
|
|
field: 'detail',
|
|
},
|
|
{
|
|
name: 'formDialogInputRemark',
|
|
align: 'left',
|
|
label: 'general.remark',
|
|
field: 'remark',
|
|
},
|
|
{
|
|
name: 'createdAt',
|
|
align: 'left',
|
|
label: 'general.createdAt',
|
|
field: 'createdAt',
|
|
},
|
|
],
|
|
|
|
product: [
|
|
{
|
|
name: 'branchLabelNo',
|
|
align: 'center',
|
|
label: 'general.order',
|
|
field: 'branchNo',
|
|
},
|
|
{
|
|
name: 'productName',
|
|
align: 'left',
|
|
label: 'general.name',
|
|
field: 'name',
|
|
},
|
|
{
|
|
name: 'productDetail',
|
|
align: 'left',
|
|
label: 'general.detail',
|
|
field: 'detail',
|
|
},
|
|
{
|
|
name: 'productExpenseType',
|
|
align: 'left',
|
|
label: 'productService.product.expenseType',
|
|
field: 'expenseType',
|
|
},
|
|
{
|
|
name: 'productProcessingTime',
|
|
align: 'left',
|
|
label: 'productService.product.processingTimeDay',
|
|
field: 'process',
|
|
},
|
|
{
|
|
name: 'productVat',
|
|
align: 'left',
|
|
label: 'productService.product.vat',
|
|
field: 'expenseType',
|
|
},
|
|
{
|
|
name: 'priceInformation',
|
|
align: 'center',
|
|
label: 'productService.product.priceInformation',
|
|
field: 'name',
|
|
},
|
|
],
|
|
service: [
|
|
{
|
|
name: 'branchLabelNo',
|
|
align: 'center',
|
|
label: 'general.order',
|
|
field: 'branchNo',
|
|
},
|
|
{
|
|
name: 'serviceName',
|
|
align: 'left',
|
|
label: 'general.name',
|
|
field: 'name',
|
|
},
|
|
{
|
|
name: 'serviceDetail',
|
|
align: 'left',
|
|
label: 'general.detail',
|
|
field: 'detail',
|
|
},
|
|
{
|
|
name: 'serviceWorkTotal',
|
|
align: 'left',
|
|
label: 'productService.service.totalWork',
|
|
field: (v) => v.work.length,
|
|
},
|
|
{
|
|
name: 'createdAt',
|
|
align: 'left',
|
|
label: 'general.createdAt',
|
|
field: 'createdAt',
|
|
},
|
|
],
|
|
} satisfies {
|
|
groupAndType: QTableProps['columns'];
|
|
product: QTableProps['columns'];
|
|
service: QTableProps['columns'];
|
|
};
|
|
|
|
const tbControl = reactive({
|
|
groupAndType: {
|
|
fieldDisplay: [
|
|
{ value: 'branchLabelNo', label: 'general.order' },
|
|
{ value: 'name', label: 'general.name' },
|
|
{ value: 'detail', label: 'general.detail' },
|
|
{ value: 'formDialogInputRemark', label: 'general.remark' },
|
|
{ value: 'createdAt', label: 'general.createdAt' },
|
|
],
|
|
fieldSelected: [
|
|
'branchLabelNo',
|
|
'name',
|
|
'detail',
|
|
'formDialogInputRemark',
|
|
'createdAt',
|
|
],
|
|
},
|
|
product: {
|
|
fieldDisplay: [
|
|
{ value: 'branchLabelNo', label: 'general.order' },
|
|
{ value: 'productName', label: 'general.name' },
|
|
{
|
|
value: 'productExpenseType',
|
|
label: 'productService.product.expenseType',
|
|
},
|
|
{ value: 'productDetail', label: 'general.detail' },
|
|
{
|
|
value: 'productProcessingTime',
|
|
label: 'productService.product.processingTimeDay',
|
|
},
|
|
{
|
|
value: 'productVat',
|
|
label: 'productService.product.vat',
|
|
},
|
|
{
|
|
value: 'priceInformation',
|
|
label: 'productService.product.priceInformation',
|
|
},
|
|
],
|
|
fieldSelected: [
|
|
'branchLabelNo',
|
|
'productName',
|
|
'productExpenseType',
|
|
'productDetail',
|
|
'productProcessingTime',
|
|
'productVat',
|
|
'priceInformation',
|
|
],
|
|
},
|
|
service: {
|
|
fieldDisplay: [
|
|
{ value: 'branchLabelNo', label: 'general.order' },
|
|
{ value: 'serviceName', label: 'general.name' },
|
|
{ value: 'serviceDetail', label: 'general.detail' },
|
|
{ value: 'serviceWorkTotal', label: 'productService.service.totalWork' },
|
|
{ value: 'createdAt', label: 'general.createdAt' },
|
|
],
|
|
fieldSelected: [
|
|
'branchLabelNo',
|
|
'serviceName',
|
|
'serviceDetail',
|
|
'serviceWorkTotal',
|
|
'createdAt',
|
|
],
|
|
},
|
|
});
|
|
const $q = useQuasar();
|
|
|
|
const workItems = ref<WorkItems[]>([]);
|
|
const workNameRef = ref();
|
|
const selectProduct = ref<Product[]>([]);
|
|
const currentWorkIndex = ref<number>(0);
|
|
|
|
const serviceTab = ref(1);
|
|
const propertiesDialog = ref<boolean>(false);
|
|
|
|
const totalProduct = ref<number>(0);
|
|
const totalService = ref<number>(0);
|
|
const filterStat = ref<('group' | 'service' | 'product')[]>([]);
|
|
|
|
const refAddServiceWork = ref();
|
|
const refEditServiceWork = ref();
|
|
|
|
const tempWorkItems = ref<WorkItems[]>([]);
|
|
|
|
// แบ่งหน้า
|
|
const currentPageGroup = ref<number>(1);
|
|
const maxPageGroup = ref<number>(1);
|
|
const pageSizeGroup = ref<number>(30);
|
|
const maxPageServiceAndProduct = ref<number>(1);
|
|
const pageSizeServiceAndProduct = ref<number>(10);
|
|
const currentPageServiceAndProduct = ref<number>(1);
|
|
const totalGroup = ref<number>(0);
|
|
const total = ref<number>(0);
|
|
|
|
// เก็บ id ที่เข้ามา
|
|
const currentIdGroup = ref<string>('');
|
|
const currentIdType = ref<string>('');
|
|
const currentIdService = ref<string>('');
|
|
const currentIdProduct = ref<string>('');
|
|
const currentIdGroupTree = ref<string>('');
|
|
|
|
const currentStatusGroupType = ref<Status>('CREATED');
|
|
const currentIdGroupType = ref('');
|
|
|
|
const currentStatus = ref<Status | 'All'>('All');
|
|
|
|
// img
|
|
const isImageEdit = ref<boolean>(false);
|
|
const refreshImageState = ref(false);
|
|
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
|
const onCreateImageList = ref<{
|
|
selectedImage: string;
|
|
list: { url: string; imgFile: File | null; name: string }[];
|
|
}>({ selectedImage: '', list: [] });
|
|
|
|
const isSelectAll = computed(() => {
|
|
const tempProduct = !!inputSearchWorkProduct.value
|
|
? resultSearchProduct.value
|
|
: productIsAdd.value;
|
|
|
|
const activeProducts = tempProduct?.filter((i) => i.status !== 'INACTIVE');
|
|
|
|
if (!!inputSearchWorkProduct.value) {
|
|
return tempProduct?.length !== 0
|
|
? activeProducts?.every((activeProduct) =>
|
|
selectProduct.value.some(
|
|
(product) => product.id === activeProduct.id,
|
|
),
|
|
)
|
|
: false;
|
|
}
|
|
|
|
return selectProduct.value.length === activeProducts?.length;
|
|
});
|
|
|
|
async function searchProduct(isAdd: boolean = true) {
|
|
const res = await fetchListProduct({
|
|
query: inputSearchWorkProduct.value,
|
|
productGroupId: currentIdGroup.value,
|
|
shared: true,
|
|
activeOnly: isAdd ? true : undefined,
|
|
orderBy: 'asc',
|
|
});
|
|
|
|
if (res) {
|
|
if (isAdd) {
|
|
resultSearchProduct.value = res.result;
|
|
}
|
|
if (!isAdd) {
|
|
product.value = res.result.map((v) => {
|
|
return {
|
|
...v,
|
|
type: 'product',
|
|
};
|
|
});
|
|
}
|
|
}
|
|
flowStore.rotate();
|
|
}
|
|
|
|
function deleteSelectAllAtSearch() {
|
|
selectProduct.value = selectProduct.value.filter(
|
|
(product) =>
|
|
!resultSearchProduct.value.some((result) => result.id === product.id),
|
|
);
|
|
}
|
|
|
|
function selectAllProduct(list: Product[]) {
|
|
list
|
|
?.filter((i) => {
|
|
if (i.status === 'INACTIVE') {
|
|
return false;
|
|
}
|
|
return true;
|
|
})
|
|
.forEach((i) => {
|
|
const productExists = selectProduct.value.some(
|
|
(product) => product.id === i.id,
|
|
);
|
|
if (!productExists) {
|
|
selectProduct.value.push({ ...i });
|
|
}
|
|
});
|
|
}
|
|
|
|
async function fetchListGroups(mobileFetch?: boolean) {
|
|
const productGroupLength = productGroup.value?.length || 0;
|
|
const res = await fetchProductGroup({
|
|
page: mobileFetch ? 1 : currentPageGroup.value,
|
|
pageSize: mobileFetch
|
|
? productGroupLength +
|
|
(stat.value[0]?.count - productGroupLength === 1 ? 1 : 0)
|
|
: pageSizeGroup.value,
|
|
query: !!inputSearch.value ? inputSearch.value : undefined,
|
|
status:
|
|
currentStatus.value === 'All'
|
|
? undefined
|
|
: currentStatus.value === 'ACTIVE'
|
|
? 'ACTIVE'
|
|
: 'INACTIVE',
|
|
});
|
|
|
|
if (res) {
|
|
// currentPageGroup.value = res.page;
|
|
totalGroup.value = res.total;
|
|
maxPageGroup.value = Math.ceil(res.total / pageSizeGroup.value);
|
|
if (!productGroup.value) productGroup.value = [];
|
|
productGroup.value =
|
|
$q.screen.xs && !mobileFetch
|
|
? [...productGroup.value, ...res.result]
|
|
: res.result;
|
|
}
|
|
}
|
|
|
|
async function fetchListOfProductIsAdd(
|
|
productGroupId: string,
|
|
isSort?: boolean,
|
|
) {
|
|
const res = await fetchListProduct({
|
|
status:
|
|
currentStatus.value === 'INACTIVE'
|
|
? 'INACTIVE'
|
|
: currentStatus.value === 'ACTIVE'
|
|
? 'ACTIVE'
|
|
: undefined,
|
|
productGroupId,
|
|
shared: true,
|
|
pageSize: 150,
|
|
orderField: 'name',
|
|
orderBy: true ? 'asc' : 'desc',
|
|
activeOnly: true,
|
|
});
|
|
|
|
if (res) {
|
|
productIsAdd.value = res.result.map((v) => {
|
|
return {
|
|
...v,
|
|
type: 'product',
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
async function fetchListOfProduct(mobileFetch?: boolean) {
|
|
const productLength = product.value?.length || 0;
|
|
const res = await fetchListProduct({
|
|
page: mobileFetch ? 1 : currentPageServiceAndProduct.value,
|
|
pageSize: mobileFetch
|
|
? productLength + (stat.value[2]?.count - productLength === 1 ? 1 : 0)
|
|
: pageSizeServiceAndProduct.value,
|
|
query: !!inputSearchProductAndService.value
|
|
? inputSearchProductAndService.value
|
|
: undefined,
|
|
status:
|
|
currentStatus.value === 'INACTIVE'
|
|
? 'INACTIVE'
|
|
: currentStatus.value === 'ACTIVE'
|
|
? 'ACTIVE'
|
|
: undefined,
|
|
productGroupId: currentIdGroup.value,
|
|
});
|
|
|
|
if (res) {
|
|
// currentPageServiceAndProduct.value = res.page;
|
|
total.value = res.total;
|
|
totalProduct.value = res.total;
|
|
maxPageServiceAndProduct.value = Math.ceil(
|
|
res.total / pageSizeServiceAndProduct.value,
|
|
);
|
|
if (!product.value) product.value = [];
|
|
$q.screen.xs && !mobileFetch
|
|
? product.value.push(
|
|
...res.result.map((v) => {
|
|
return {
|
|
...v,
|
|
type: 'product' as const,
|
|
};
|
|
}),
|
|
)
|
|
: (product.value = res.result.map((v) => {
|
|
return {
|
|
...v,
|
|
type: 'product',
|
|
};
|
|
}));
|
|
}
|
|
}
|
|
|
|
async function fetchListOfService(mobileFetch?: boolean) {
|
|
const serviceLength = service.value?.length || 0;
|
|
const res = await fetchListService({
|
|
page: mobileFetch ? 1 : currentPageServiceAndProduct.value,
|
|
query: !!inputSearchProductAndService.value
|
|
? inputSearchProductAndService.value
|
|
: undefined,
|
|
pageSize: mobileFetch
|
|
? serviceLength + (stat.value[1]?.count - serviceLength === 1 ? 1 : 0)
|
|
: pageSizeServiceAndProduct.value,
|
|
status:
|
|
currentStatus.value === 'INACTIVE'
|
|
? 'INACTIVE'
|
|
: currentStatus.value === 'ACTIVE'
|
|
? 'ACTIVE'
|
|
: undefined,
|
|
productGroupId: currentIdGroup.value,
|
|
});
|
|
|
|
if (res) {
|
|
// currentPageServiceAndProduct.value = res.page;
|
|
totalService.value = res.total;
|
|
total.value = res.total;
|
|
maxPageServiceAndProduct.value = Math.ceil(
|
|
res.total / pageSizeServiceAndProduct.value,
|
|
);
|
|
if (!service.value) service.value = [];
|
|
$q.screen.xs && !mobileFetch
|
|
? service.value.push(
|
|
...res.result.map((v) => {
|
|
return {
|
|
...v,
|
|
type: 'service' as const,
|
|
};
|
|
}),
|
|
)
|
|
: (service.value = res.result.map((v) => {
|
|
return {
|
|
...v,
|
|
type: 'service',
|
|
};
|
|
}));
|
|
}
|
|
}
|
|
|
|
async function toggleStatusProduct(id: string, status: Status) {
|
|
const res = await editProduct(id, {
|
|
status: status === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE',
|
|
});
|
|
if (res) formProduct.value.status = res.status;
|
|
|
|
await alternativeFetch();
|
|
flowStore.rotate();
|
|
}
|
|
|
|
async function toggleStatusService(id: string, status: Status) {
|
|
const res = await editService(id, {
|
|
status: status === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE',
|
|
});
|
|
if (res) formService.value.status = res.status;
|
|
|
|
await alternativeFetch();
|
|
flowStore.rotate();
|
|
}
|
|
|
|
async function toggleStatusGroup(id: string, status: Status) {
|
|
const res = await editProductGroup(id, {
|
|
status: status === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE',
|
|
});
|
|
if (res) currentStatusGroupType.value = res.status;
|
|
|
|
await fetchListGroups();
|
|
flowStore.rotate();
|
|
}
|
|
|
|
async function triggerChangeStatus(
|
|
id: string,
|
|
status: string,
|
|
type?: 'group' | 'service' | 'product',
|
|
) {
|
|
return await new Promise((resolve, reject) => {
|
|
dialog({
|
|
color: status !== 'INACTIVE' ? 'warning' : 'info',
|
|
icon:
|
|
status !== 'INACTIVE' ? 'mdi-alert' : 'mdi-message-processing-outline',
|
|
title: t('dialog.title.confirmChangeStatus'),
|
|
actionText:
|
|
status !== 'INACTIVE' ? t('general.close') : t('general.open'),
|
|
message:
|
|
status !== 'INACTIVE'
|
|
? t('dialog.message.confirmChangeStatusOff')
|
|
: t('dialog.message.confirmChangeStatusOn'),
|
|
action: async () => {
|
|
if (type === 'group' || productMode.value === 'group') {
|
|
await toggleStatusGroup(id, status as Status)
|
|
.then(resolve)
|
|
.catch(reject);
|
|
} else if (type === 'service') {
|
|
await toggleStatusService(id, status as Status)
|
|
.then(resolve)
|
|
.catch(reject);
|
|
} else if (type === 'product') {
|
|
await toggleStatusProduct(id, status as Status)
|
|
.then(resolve)
|
|
.catch(reject);
|
|
}
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
});
|
|
}
|
|
|
|
async function deleteServiceConfirm(serviceId?: string) {
|
|
dialog({
|
|
color: 'negative',
|
|
icon: 'mdi-alert',
|
|
title: t('dialog.title.confirmDelete'),
|
|
actionText: t('general.delete'),
|
|
persistent: true,
|
|
message: t('dialog.message.confirmDelete'),
|
|
action: async () => {
|
|
const res = await deleteService(serviceId ?? currentIdService.value);
|
|
|
|
dialogServiceEdit.value = false;
|
|
|
|
if (res) {
|
|
totalService.value = totalService.value - 1;
|
|
allStat.value[1].count = allStat.value[1].count - 1;
|
|
stat.value[1].count = stat.value[1].count - 1;
|
|
|
|
if (productAndServiceTab.value === 'service') {
|
|
await fetchListOfService($q.screen.xs);
|
|
}
|
|
}
|
|
|
|
flowStore.rotate();
|
|
calculateStats({ reFetch: true });
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
|
|
async function deleteProductConfirm(id?: string) {
|
|
dialog({
|
|
color: 'negative',
|
|
icon: 'mdi-alert',
|
|
title: t('dialog.title.confirmDelete'),
|
|
actionText: t('general.delete'),
|
|
persistent: true,
|
|
message: t('dialog.message.confirmDelete'),
|
|
action: async () => {
|
|
const res = await deleteProduct(id ?? currentIdProduct.value);
|
|
|
|
if (!res) {
|
|
flowStore.rotate();
|
|
return;
|
|
}
|
|
|
|
dialogProductEdit.value = false;
|
|
|
|
if (res) {
|
|
totalProduct.value = totalProduct.value - 1;
|
|
allStat.value[2].count = allStat.value[2].count - 1;
|
|
stat.value[2].count = stat.value[2].count - 1;
|
|
|
|
if (productAndServiceTab.value === 'product') {
|
|
await fetchListOfProduct($q.screen.xs);
|
|
}
|
|
}
|
|
flowStore.rotate();
|
|
calculateStats({ reFetch: true });
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
|
|
// type or group
|
|
async function deleteGroupById(productId?: string) {
|
|
dialog({
|
|
color: 'negative',
|
|
icon: 'mdi-alert',
|
|
title: t('dialog.title.confirmDelete'),
|
|
actionText: t('general.delete'),
|
|
persistent: true,
|
|
message: t('dialog.message.confirmDelete'),
|
|
action: async () => {
|
|
if (editByTree.value !== undefined) {
|
|
if (editByTree.value === 'group') {
|
|
// Product Group
|
|
const res = await deleteProductGroup(
|
|
productId ?? currentIdGroup.value,
|
|
);
|
|
if (res) {
|
|
allStat.value[0].count = allStat.value[0].count - 1;
|
|
stat.value[0].count = stat.value[0].count - 1;
|
|
await fetchListGroups($q.screen.xs);
|
|
}
|
|
}
|
|
|
|
flowStore.rotate();
|
|
calculateStats({ reFetch: true });
|
|
editByTree.value = undefined;
|
|
drawerInfo.value = false;
|
|
} else {
|
|
if (productMode.value === 'group') {
|
|
// Product Group
|
|
const res = await deleteProductGroup(
|
|
productId ?? currentIdGroup.value,
|
|
);
|
|
if (res) {
|
|
allStat.value[0].count = allStat.value[0].count - 1;
|
|
stat.value[0].count = stat.value[0].count - 1;
|
|
await fetchListGroups();
|
|
}
|
|
}
|
|
|
|
flowStore.rotate();
|
|
calculateStats({ reFetch: true });
|
|
drawerInfo.value = false;
|
|
}
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
|
|
function undoProductGroup() {
|
|
formDataGroup.value = {
|
|
remark: previousValue.value.remark,
|
|
detail: previousValue.value.detail,
|
|
name: previousValue.value.name,
|
|
code: previousValue.value.code,
|
|
registeredBranchId: previousValue.value.registeredBranchId,
|
|
};
|
|
isEdit.value = false;
|
|
flowStore.rotate();
|
|
}
|
|
|
|
async function assignFormDataGroup(data: ProductGroup) {
|
|
previousValue.value = data;
|
|
currentStatusGroupType.value = data.status;
|
|
currentIdGroupType.value = data.id;
|
|
|
|
formDataGroup.value = {
|
|
remark: data.remark,
|
|
detail: data.detail,
|
|
name: data.name,
|
|
code: data.code,
|
|
shared: data.shared,
|
|
registeredBranchId: data.registeredBranchId,
|
|
};
|
|
}
|
|
|
|
const prevService = ref<ServiceCreate>({
|
|
work: [],
|
|
attributes: {
|
|
showTotalPrice: false,
|
|
workflowId: '',
|
|
additional: [],
|
|
workflowStep: [],
|
|
},
|
|
detail: '',
|
|
name: '',
|
|
code: '',
|
|
productGroupId: '',
|
|
});
|
|
|
|
const currentService = ref<ServiceById>();
|
|
|
|
async function assignFormService(id: string) {
|
|
const res = await fetchListServiceById(id);
|
|
|
|
if (res) {
|
|
await fetchImageList(res.id, res.selectedImage || '', 'service');
|
|
serviceTab.value = 1;
|
|
statusToggle.value = res.status === 'INACTIVE' ? false : true;
|
|
profileUrl.value = `${baseUrl.value}/service/${res.id}/image/${res.selectedImage}`;
|
|
profileSubmit.value = true;
|
|
|
|
currentService.value = JSON.parse(JSON.stringify(res));
|
|
|
|
if (res.attributes && res.attributes.workflowId) {
|
|
const workflowRet = await getWorkflowTemplate(res.attributes.workflowId);
|
|
if (workflowRet) currWorkflow.value = workflowRet;
|
|
}
|
|
|
|
prevService.value = {
|
|
code: res.code,
|
|
name: res.name,
|
|
detail: res.detail,
|
|
attributes: res.attributes,
|
|
work: [],
|
|
status: res.status,
|
|
productGroupId: res.productGroupId,
|
|
selectedImage: res.selectedImage,
|
|
installments: res.installments,
|
|
};
|
|
|
|
formService.value = { ...prevService.value };
|
|
|
|
res.work.forEach((item) => {
|
|
prevService.value.work.push({
|
|
id: item.id,
|
|
name: item.name,
|
|
attributes: item.attributes,
|
|
product: item.productOnWork.map((productOnWorkItem) => ({
|
|
id: productOnWorkItem.product.id,
|
|
installmentNo: productOnWorkItem.installmentNo,
|
|
stepCount: productOnWorkItem.stepCount,
|
|
})),
|
|
});
|
|
});
|
|
|
|
formService.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,
|
|
nameEn: productOnWorkItem.product.name,
|
|
installmentNo: productOnWorkItem.installmentNo,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
const currentNoAction = ref(false);
|
|
const prevProduct = ref<ProductCreate>({
|
|
expenseType: '',
|
|
vatIncluded: true,
|
|
agentPriceVatIncluded: true,
|
|
agentPriceCalcVat: true,
|
|
serviceChargeVatIncluded: true,
|
|
serviceChargeCalcVat: true,
|
|
productGroupId: '',
|
|
remark: '',
|
|
serviceCharge: 0,
|
|
agentPrice: 0,
|
|
price: 0,
|
|
process: 0,
|
|
detail: '',
|
|
name: '',
|
|
code: '',
|
|
image: undefined,
|
|
shared: false,
|
|
});
|
|
|
|
async function assignFormDataProduct(data: Product) {
|
|
productTab.value = 1;
|
|
statusToggle.value = data.status === 'INACTIVE' ? false : true;
|
|
profileUrl.value = `${baseUrl.value}/product/${data?.id}/image/${data?.selectedImage}`;
|
|
|
|
await fetchImageList(data.id, data.selectedImage || '', 'product');
|
|
// profileSubmit.value = true;
|
|
|
|
prevProduct.value = {
|
|
productGroupId: data.productGroupId,
|
|
remark: data.remark,
|
|
serviceCharge: data.serviceCharge,
|
|
agentPrice: data.agentPrice,
|
|
price: data.price,
|
|
process: data.process,
|
|
detail: data.detail,
|
|
name: data.name,
|
|
code: data.code,
|
|
calcVat: data.calcVat,
|
|
image: undefined,
|
|
status: data.status,
|
|
expenseType: data.expenseType,
|
|
vatIncluded: data.vatIncluded,
|
|
serviceChargeCalcVat: data.serviceChargeCalcVat,
|
|
serviceChargeVatIncluded: data.serviceChargeVatIncluded,
|
|
agentPriceCalcVat: data.agentPriceCalcVat,
|
|
agentPriceVatIncluded: data.agentPriceVatIncluded,
|
|
selectedImage: data.selectedImage,
|
|
document: data.document,
|
|
shared: data.shared,
|
|
};
|
|
if (prevProduct.value.document)
|
|
formProductDocument.value = prevProduct.value.document;
|
|
formProduct.value = { ...prevProduct.value };
|
|
}
|
|
|
|
function clearFormGroup() {
|
|
formDataGroup.value = {
|
|
remark: '',
|
|
detail: '',
|
|
name: '',
|
|
code: '',
|
|
registeredBranchId: '',
|
|
};
|
|
currentStatusGroupType.value = 'CREATED';
|
|
dialogInputForm.value = false;
|
|
}
|
|
|
|
function clearFormProduct() {
|
|
formProduct.value = {
|
|
productGroupId: '',
|
|
remark: '',
|
|
serviceCharge: 0,
|
|
agentPrice: 0,
|
|
price: 0,
|
|
process: 0,
|
|
detail: '',
|
|
name: '',
|
|
code: '',
|
|
image: undefined,
|
|
expenseType: '',
|
|
calcVat: true,
|
|
vatIncluded: true,
|
|
agentPriceCalcVat: true,
|
|
agentPriceVatIncluded: true,
|
|
serviceChargeCalcVat: true,
|
|
serviceChargeVatIncluded: true,
|
|
shared: false,
|
|
};
|
|
imageProduct.value = undefined;
|
|
dialogProduct.value = false;
|
|
dialogProductEdit.value = false;
|
|
profileUrl.value = '';
|
|
profileFileImg.value = null;
|
|
profileSubmit.value = false;
|
|
infoProductEdit.value = false;
|
|
}
|
|
|
|
function clearFormService() {
|
|
currWorkflow.value = undefined;
|
|
formService.value = {
|
|
code: '',
|
|
name: '',
|
|
detail: '',
|
|
attributes: {
|
|
workflowId: '',
|
|
additional: [],
|
|
showTotalPrice: false,
|
|
workflowStep: [],
|
|
},
|
|
work: [],
|
|
status: undefined,
|
|
productGroupId: '',
|
|
installments: 1,
|
|
};
|
|
tempWorkItems.value = [];
|
|
workItems.value = [];
|
|
selectProduct.value = [];
|
|
dialogService.value = false;
|
|
dialogServiceEdit.value = false;
|
|
profileUrl.value = '';
|
|
profileSubmit.value = false;
|
|
imageProduct.value = undefined;
|
|
profileFileImg.value = null;
|
|
}
|
|
|
|
function sameFormService() {
|
|
const isEdit = dialogServiceEdit.value;
|
|
const defaultFormService = {
|
|
code: isEdit ? currentService.value?.code : '',
|
|
name: isEdit ? currentService.value?.name : '',
|
|
detail: isEdit ? currentService.value?.detail : '',
|
|
attributes: isEdit
|
|
? currentService.value?.attributes
|
|
: {
|
|
workflowId: '',
|
|
additional: [],
|
|
showTotalPrice: false,
|
|
workflowStep: [],
|
|
},
|
|
work: isEdit
|
|
? currentService.value?.work.map((v) => ({
|
|
attributes: v.attributes,
|
|
id: v.id,
|
|
name: v.name,
|
|
product: v.productOnWork.map((p) => ({
|
|
id: p.productId,
|
|
installmentNo: p.installmentNo,
|
|
stepCount: p.stepCount,
|
|
})),
|
|
}))
|
|
: [],
|
|
status: isEdit ? currentService.value?.status : undefined,
|
|
productGroupId: isEdit ? currentService.value?.productGroupId : '',
|
|
installments: isEdit ? currentService.value?.installments : 1,
|
|
selectedImage: isEdit ? currentService.value?.selectedImage : '',
|
|
};
|
|
|
|
if (
|
|
deepEquals(
|
|
isEdit
|
|
? formService.value
|
|
: {
|
|
...formService.value,
|
|
selectedImage: '',
|
|
detail: formService.value.detail.replace(/<\/?[^>]+(>|$)/g, ''),
|
|
},
|
|
defaultFormService,
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function assignFormDataProductServiceCreate() {
|
|
formService.value.work = [];
|
|
|
|
workItems.value.forEach((item) => {
|
|
formService.value.work.push({
|
|
id: item.id,
|
|
name: item.name,
|
|
attributes: item.attributes,
|
|
product: item.product.map((productItem) => {
|
|
const stepCount = item.attributes.workflowStep.reduce((count, step) => {
|
|
if (
|
|
step.attributes?.properties?.length > 0 &&
|
|
step.productsId.includes(productItem.id)
|
|
) {
|
|
return count + 1;
|
|
}
|
|
return count;
|
|
}, 0);
|
|
|
|
return {
|
|
id: productItem.id,
|
|
installmentNo: productItem.installmentNo,
|
|
stepCount: stepCount,
|
|
};
|
|
}),
|
|
});
|
|
});
|
|
}
|
|
|
|
async function submitService(notClose = false) {
|
|
assignFormDataProductServiceCreate();
|
|
formService.value.productGroupId = currentIdGroup.value;
|
|
|
|
if (profileFileImg.value) formService.value.image = profileFileImg.value;
|
|
|
|
if (dialogService.value) {
|
|
formService.value.productGroupId = currentIdGroup.value;
|
|
formService.value.work.forEach((s) => (s.id = undefined));
|
|
|
|
if (formService.value.code === '' || formService.value.name === '') {
|
|
serviceTab.value = 1;
|
|
return;
|
|
}
|
|
|
|
const res = await createService(
|
|
{
|
|
...formService.value,
|
|
workflowId: currWorkflow.value?.id || '',
|
|
},
|
|
onCreateImageList.value,
|
|
);
|
|
if (res) {
|
|
const group = productGroup.value?.find(
|
|
(g) => g.id === currentIdGroup.value,
|
|
);
|
|
if (group) group.status = 'ACTIVE';
|
|
|
|
allStat.value[1].count = allStat.value[1].count + 1;
|
|
stat.value[1].count = stat.value[1].count + 1;
|
|
} else {
|
|
return;
|
|
}
|
|
totalService.value = totalService.value + 1;
|
|
productAndServiceTab.value = 'service';
|
|
}
|
|
|
|
if (dialogServiceEdit.value) {
|
|
const res = await editService(currentIdService.value, {
|
|
...formService.value,
|
|
workflowId: currWorkflow.value?.id || '',
|
|
status: statusToggle.value ? formService.value.status : 'INACTIVE',
|
|
});
|
|
if (!res) return;
|
|
}
|
|
|
|
if (!notClose) clearFormService();
|
|
|
|
if (productAndServiceTab.value === 'service') {
|
|
await fetchListOfService($q.screen.xs);
|
|
}
|
|
|
|
flowStore.rotate();
|
|
}
|
|
|
|
async function submitProduct(notClose = false) {
|
|
formProduct.value.productGroupId = currentIdGroup.value;
|
|
if (profileFileImg.value) {
|
|
formProduct.value.image = profileFileImg.value;
|
|
}
|
|
|
|
if (dialogProduct.value) {
|
|
if (formProduct.value.name === '' || formProduct.value.code === '') {
|
|
productTab.value = 1;
|
|
return;
|
|
}
|
|
const res = await createProduct(
|
|
{ ...formProduct.value, document: formProductDocument.value },
|
|
onCreateImageList.value,
|
|
);
|
|
|
|
if (res) {
|
|
const group = productGroup.value?.find(
|
|
(g) => g.id === currentIdGroup.value,
|
|
);
|
|
if (group) group.status = 'ACTIVE';
|
|
|
|
allStat.value[2].count = allStat.value[2].count + 1;
|
|
stat.value[2].count = stat.value[2].count + 1;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
productAndServiceTab.value = 'product';
|
|
}
|
|
|
|
if (dialogProductEdit.value) {
|
|
const res = await editProduct(currentIdProduct.value, {
|
|
...formProduct.value,
|
|
status: statusToggle.value ? 'ACTIVE' : 'INACTIVE',
|
|
document: formProductDocument.value,
|
|
});
|
|
|
|
if (!res) return;
|
|
}
|
|
totalProduct.value = totalProduct.value + 1;
|
|
if (!notClose) clearFormProduct();
|
|
|
|
if (productAndServiceTab.value === 'product') {
|
|
await fetchListOfProduct($q.screen.xs);
|
|
}
|
|
flowStore.rotate();
|
|
}
|
|
|
|
async function submitGroup() {
|
|
if (drawerInfo.value) {
|
|
if (currentIdGroupTree.value)
|
|
await editProductGroup(currentIdGroupTree.value, formDataGroup.value);
|
|
else await editProductGroup(currentIdGroup.value, formDataGroup.value);
|
|
} else {
|
|
const res = await createProductGroup({
|
|
...formDataGroup.value,
|
|
status:
|
|
currentStatusGroupType.value === 'CREATED' ? undefined : 'INACTIVE',
|
|
});
|
|
|
|
if (res) {
|
|
allStat.value[0].count = allStat.value[0].count + 1;
|
|
stat.value[0].count = stat.value[0].count + 1;
|
|
}
|
|
}
|
|
|
|
currentIdGroupTree.value = '';
|
|
drawerInfo.value = false;
|
|
await fetchListGroups($q.screen.xs);
|
|
clearFormGroup();
|
|
flowStore.rotate();
|
|
}
|
|
|
|
function submitAddWorkProduct() {
|
|
selectProduct.value.forEach((i) => {
|
|
const productExists = workItems.value[currentWorkIndex.value].product.some(
|
|
(product) => product.id === i.id,
|
|
);
|
|
// add not exists product
|
|
if (!productExists) {
|
|
workItems.value[currentWorkIndex.value].product.push({
|
|
...i,
|
|
installmentNo: !!formService.value.installments ? 1 : 1,
|
|
nameEn: '',
|
|
});
|
|
workItems.value[currentWorkIndex.value].attributes.workflowStep.forEach(
|
|
(s) => {
|
|
if (!s.hasOwnProperty('productsId')) {
|
|
s.productsId = [];
|
|
}
|
|
s.productsId.push(i.id);
|
|
},
|
|
);
|
|
}
|
|
});
|
|
|
|
// filter remain product
|
|
workItems.value[currentWorkIndex.value].attributes.workflowStep.forEach(
|
|
(s) => {
|
|
s.productsId = s.productsId.filter((pid) =>
|
|
selectProduct.value.some((i) => i.id === pid),
|
|
);
|
|
},
|
|
);
|
|
workItems.value[currentWorkIndex.value].product = workItems.value[
|
|
currentWorkIndex.value
|
|
].product.filter((product) =>
|
|
selectProduct.value.some((i) => i.id === product.id),
|
|
);
|
|
|
|
dialogTotalProduct.value = false;
|
|
selectProduct.value = [];
|
|
}
|
|
|
|
function confirmDeleteWork(id: string, noDialog?: boolean) {
|
|
if (noDialog) {
|
|
deleteWork(id);
|
|
} else {
|
|
const currUseName = workItems.value?.map((v) => v.name) || [];
|
|
const deleteTarget = workNameItems.value.find(
|
|
(v: { id: string }) => v.id === id,
|
|
);
|
|
if (!deleteTarget) return;
|
|
const isNameInUse = currUseName.includes(deleteTarget.name);
|
|
|
|
dialog({
|
|
color: 'negative',
|
|
icon: 'mdi-alert',
|
|
title: t('dialog.title.confirmDelete'),
|
|
actionText: t('general.delete'),
|
|
message: isNameInUse
|
|
? `${t('dialog.message.beingUse', { msg: deleteTarget.name })} ${t('dialog.message.confirmDelete')}`
|
|
: t('dialog.message.confirmDelete'),
|
|
action: async () => {
|
|
deleteWork(id);
|
|
flowStore.rotate();
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
}
|
|
|
|
function triggerConfirmCloseWork() {
|
|
dialogWarningClose(t, {
|
|
message: t('dialog.message.warningClose'),
|
|
action: () => {
|
|
manageWorkNameDialog.value = false;
|
|
if (workNameItems.value[workNameItems.value.length - 1].name === '') {
|
|
confirmDeleteWork(
|
|
workNameItems.value[workNameItems.value.length - 1].id,
|
|
true,
|
|
);
|
|
}
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
|
|
const tempValueProperties = ref<Attributes>({
|
|
showTotalPrice: false,
|
|
workflowId: '',
|
|
additional: [],
|
|
workflowStep: [],
|
|
});
|
|
const currentPropertiesMode = ref<'service' | 'work'>('service');
|
|
|
|
function openPropertiesDialog(type: 'service' | 'work') {
|
|
if (type === 'service') {
|
|
propertiesOption.value = optionStore.globalOption.propertiesField;
|
|
}
|
|
|
|
if (type === 'work') {
|
|
propertiesOption.value = optionStore.globalOption.workPropertiesField;
|
|
}
|
|
|
|
currentPropertiesMode.value = type;
|
|
propertiesDialog.value = true;
|
|
}
|
|
|
|
async function calculateStats(opt?: {
|
|
type?: 'service' | 'product';
|
|
reFetch?: boolean;
|
|
}) {
|
|
if (opt && opt.type === 'service' && productMode.value === 'service') {
|
|
const resStatsService = await fetchStatsService({
|
|
productGroupId: currentIdGroup.value,
|
|
});
|
|
stat.value[1].count = resStatsService ?? 0;
|
|
return;
|
|
}
|
|
|
|
if (opt && opt.type === 'product' && productMode.value === 'product') {
|
|
const resStatsProduct = await fetchStatsProduct({
|
|
productGroupId: currentIdGroup.value,
|
|
});
|
|
stat.value[2].count = resStatsProduct ?? 0;
|
|
return;
|
|
}
|
|
|
|
if (allStat.value.length === 0 || (opt && opt.reFetch)) {
|
|
const resStatsGroup = await fetchStatsProductGroup();
|
|
const resStatsService = await fetchStatsService();
|
|
const resStatsProduct = await fetchStatsProduct();
|
|
|
|
allStat.value.push({ mode: 'group', count: resStatsGroup ?? 0 });
|
|
allStat.value.push({ mode: 'service', count: resStatsService ?? 0 });
|
|
allStat.value.push({ mode: 'product', count: resStatsProduct ?? 0 });
|
|
}
|
|
stat.value[0].count = allStat.value[0].count;
|
|
if (productMode.value === 'group') {
|
|
stat.value[1].count = allStat.value[1].count;
|
|
stat.value[2].count = allStat.value[2].count;
|
|
}
|
|
}
|
|
|
|
async function fetchStatus() {
|
|
currentPageServiceAndProduct.value = 1;
|
|
if (productAndServiceTab.value === 'product') {
|
|
product.value = [];
|
|
await fetchListOfProduct();
|
|
flowStore.rotate();
|
|
}
|
|
|
|
if (productAndServiceTab.value === 'service') {
|
|
service.value = [];
|
|
await fetchListOfService();
|
|
flowStore.rotate();
|
|
}
|
|
}
|
|
|
|
async function alternativeFetch() {
|
|
if (productAndServiceTab.value === 'product') {
|
|
await fetchListOfProduct();
|
|
flowStore.rotate();
|
|
}
|
|
if (productAndServiceTab.value === 'service') {
|
|
await fetchListOfService();
|
|
flowStore.rotate();
|
|
}
|
|
}
|
|
|
|
async function cloneServiceData() {
|
|
if (!currentService.value) return;
|
|
const currentSelectedImage = formService.value.selectedImage;
|
|
formService.value = {
|
|
...prevService.value,
|
|
attributes: JSON.parse(JSON.stringify(currentService.value.attributes)),
|
|
};
|
|
formService.value.selectedImage = currentSelectedImage;
|
|
|
|
await nextTick();
|
|
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,
|
|
installmentNo: productOnWorkItem.installmentNo,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
}
|
|
|
|
async function enterGroup(
|
|
id: string,
|
|
name: string,
|
|
status: Status,
|
|
toService?: boolean,
|
|
) {
|
|
expandedTree.value = [];
|
|
filterStat.value = [];
|
|
expandedTree.value.push(id);
|
|
pathGroupName.value = name;
|
|
currentIdType.value = '';
|
|
currentIdGroup.value = id;
|
|
currentNoAction.value = status === 'INACTIVE';
|
|
pathTypeName.value = name;
|
|
|
|
if (productMode.value === 'service') await fetchListOfService();
|
|
if (productMode.value === 'product') await fetchListOfProduct();
|
|
if (toService) await enterNext('service');
|
|
else productMode.value = 'group';
|
|
|
|
flowStore.rotate();
|
|
}
|
|
|
|
async function enterNext(type: 'service' | 'product') {
|
|
currentPageServiceAndProduct.value = 1;
|
|
inputSearchProductAndService.value = '';
|
|
currentStatus.value = 'All';
|
|
filterStat.value = [];
|
|
|
|
if (
|
|
expandedTree.value.length > 1 &&
|
|
expandedTree.value[expandedTree.value.length - 1] !== type
|
|
) {
|
|
expandedTree.value.pop();
|
|
}
|
|
|
|
if (type === 'service') {
|
|
productMode.value = 'service';
|
|
productAndServiceTab.value = 'service';
|
|
currentIdType.value = 'type';
|
|
pathTypeName.value = 'type';
|
|
expandedTree.value.push('type');
|
|
filterStat.value.push('group');
|
|
filterStat.value.push('product');
|
|
} else {
|
|
productMode.value = 'product';
|
|
productAndServiceTab.value = 'product';
|
|
currentIdType.value = 'productService';
|
|
pathTypeName.value = 'productService';
|
|
expandedTree.value.push('productService');
|
|
filterStat.value.push('group');
|
|
filterStat.value.push('service');
|
|
}
|
|
|
|
if (productMode.value === 'service') {
|
|
service.value = [];
|
|
await fetchListOfService();
|
|
}
|
|
if (productMode.value === 'product') {
|
|
product.value = [];
|
|
await fetchListOfProduct();
|
|
}
|
|
flowStore.rotate();
|
|
}
|
|
|
|
function handleHold(node: ProductGroup & { type: string }) {
|
|
if ($q.screen.gt.xs) return;
|
|
holdDialog.value = true;
|
|
currentNode.value = node;
|
|
}
|
|
|
|
async function fetchImageList(
|
|
id: string,
|
|
selectedName: string,
|
|
type: 'product' | 'service',
|
|
) {
|
|
const res = await productServiceStore.fetchImageListById(id, type);
|
|
imageList.value = {
|
|
selectedImage: selectedName,
|
|
list: res.map((n: string) => `${type}/${id}/image/${n}`),
|
|
};
|
|
return res;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
$q.screen.gt.sm && (await fetchListGroups());
|
|
|
|
navigatorStore.current.title = 'productService.title';
|
|
navigatorStore.current.path = [
|
|
{
|
|
text: 'productService.caption',
|
|
i18n: true,
|
|
handler: () => {
|
|
expandedTree.value = [];
|
|
currentIdGroup.value = '';
|
|
productMode.value = 'group';
|
|
},
|
|
},
|
|
];
|
|
modeView.value = $q.screen.lt.md ? true : false;
|
|
|
|
calculateStats();
|
|
|
|
flowStore.rotate();
|
|
});
|
|
|
|
watch(
|
|
() => expandedTree.value,
|
|
(v) => {
|
|
inputSearch.value = '';
|
|
currentStatus.value = 'All';
|
|
|
|
let tmp: typeof navigatorStore.current.path = [
|
|
{
|
|
text: 'productService.caption',
|
|
i18n: true,
|
|
handler: () => {
|
|
productMode.value = 'group';
|
|
expandedTree.value = [];
|
|
currentIdGroup.value = '';
|
|
currentIdType.value = '';
|
|
filterStat.value = [];
|
|
},
|
|
},
|
|
];
|
|
|
|
if (
|
|
productMode.value === 'group' ||
|
|
productMode.value === 'service' ||
|
|
productMode.value === 'product'
|
|
) {
|
|
tmp.push({
|
|
text: 'productService.group.withName',
|
|
i18n: true,
|
|
argsi18n: { name: pathGroupName.value },
|
|
handler: () => {
|
|
if (
|
|
productMode.value === 'service' ||
|
|
productMode.value === 'product'
|
|
) {
|
|
currentIdType.value = '';
|
|
filterStat.value = [];
|
|
productMode.value = 'group';
|
|
expandedTree.value.pop();
|
|
navigatorStore.current.path.pop();
|
|
}
|
|
},
|
|
});
|
|
if (expandedTree.value.length === 0) {
|
|
navigatorStore.current.path = [
|
|
{ text: 'productService.caption', i18n: true },
|
|
];
|
|
return;
|
|
}
|
|
}
|
|
if (productMode.value === 'service' && v.length !== 1) {
|
|
tmp.push({
|
|
text: 'productService.service.title',
|
|
i18n: true,
|
|
});
|
|
}
|
|
|
|
if (productMode.value === 'product' && v.length !== 1) {
|
|
tmp.push({
|
|
text: 'productService.title',
|
|
i18n: true,
|
|
});
|
|
}
|
|
|
|
navigatorStore.current.path = tmp;
|
|
},
|
|
{ deep: true },
|
|
);
|
|
|
|
watch(currentStatus, async () => {
|
|
if (productMode.value === 'group') {
|
|
productGroup.value = [];
|
|
currentPageGroup.value = 1;
|
|
await fetchListGroups();
|
|
}
|
|
flowStore.rotate();
|
|
});
|
|
|
|
watch(inputSearch, async () => {
|
|
if (productMode.value === 'group') {
|
|
productGroup.value = [];
|
|
currentPageGroup.value = 1;
|
|
await fetchListGroups();
|
|
flowStore.rotate();
|
|
}
|
|
});
|
|
|
|
watch(inputSearchProductAndService, async () => {
|
|
product.value = [];
|
|
service.value = [];
|
|
currentPageServiceAndProduct.value = 1;
|
|
await alternativeFetch();
|
|
});
|
|
|
|
watch(
|
|
() => $q.screen.lt.md,
|
|
(v) => {
|
|
if (v) modeView.value = true;
|
|
},
|
|
);
|
|
|
|
watch(productMode, async () => {
|
|
if (productMode.value === 'group') {
|
|
await calculateStats();
|
|
}
|
|
if (productMode.value === 'service') {
|
|
await calculateStats({ type: 'service' });
|
|
}
|
|
if (productMode.value === 'product') {
|
|
await calculateStats({ type: 'product' });
|
|
}
|
|
});
|
|
|
|
watch(
|
|
() => profileFileImg.value,
|
|
() => {
|
|
if (profileFileImg.value !== null) isImageEdit.value = true;
|
|
},
|
|
);
|
|
|
|
function handleSubmitWorkflow(workflowId: string) {
|
|
if (workItems.value.length === 0 && !currWorkflow.value) return;
|
|
|
|
const workflow = JSON.parse(JSON.stringify(currWorkflow.value));
|
|
|
|
workItems.value.forEach((w, wIndex) => {
|
|
w.attributes.workflowId = workflowId;
|
|
|
|
if (wIndex === currentWorkIndex.value) {
|
|
w.attributes.workflowStep.forEach((s) => {
|
|
if (!s.hasOwnProperty('productsId')) {
|
|
s.productsId = [];
|
|
}
|
|
s.productsId = workItems.value[wIndex]?.product.map((p) => p.id);
|
|
});
|
|
return;
|
|
} else {
|
|
w.attributes.workflowStep = JSON.parse(
|
|
JSON.stringify(
|
|
workflow.step.map((step: WorkflowStep) => ({
|
|
name: step.name,
|
|
attributes: step.attributes,
|
|
productsId: workItems.value[wIndex]?.product.map((p) => p.id),
|
|
})),
|
|
),
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
function handleSubmitSameWorkflow() {
|
|
const tempStep = JSON.parse(
|
|
JSON.stringify(
|
|
tempWorkItems.value[currentWorkIndex.value].attributes.workflowStep,
|
|
),
|
|
);
|
|
|
|
workItems.value[currentWorkIndex.value].attributes.workflowStep.forEach(
|
|
(step, i) => {
|
|
step.productsId = tempStep[i].productsId;
|
|
},
|
|
);
|
|
}
|
|
|
|
async function paste() {
|
|
if (
|
|
!!currentCopy.value &&
|
|
currentCopy.value.type === 'service' &&
|
|
!!currentCopy.value.id
|
|
)
|
|
dialogWarningClose(t, {
|
|
message: t('dialog.message.warningPaste'),
|
|
action: async () => {
|
|
const res = await fetchListServiceById(currentCopy.value.id);
|
|
if (res) {
|
|
formService.value = {
|
|
code: res.code,
|
|
name: res.name,
|
|
detail: res.detail,
|
|
attributes: res.attributes,
|
|
work: res.work.map((v) => ({
|
|
id: v.id,
|
|
name: v.name,
|
|
attributes: v.attributes,
|
|
product: v.productOnWork.map((productOnWorkItem) => ({
|
|
id: productOnWorkItem.product.id,
|
|
installmentNo: productOnWorkItem.installmentNo,
|
|
stepCount: productOnWorkItem.stepCount,
|
|
})),
|
|
})),
|
|
status: res.status,
|
|
productGroupId: res.productGroupId,
|
|
selectedImage: res.selectedImage,
|
|
installments: res.installments,
|
|
};
|
|
|
|
workItems.value = res.work.map((item) => {
|
|
return {
|
|
id: item.id,
|
|
name: item.name,
|
|
attributes: item.attributes,
|
|
product: item.productOnWork.map((productOnWorkItem) => {
|
|
return {
|
|
...productOnWorkItem.product,
|
|
nameEn: productOnWorkItem.product.name,
|
|
installmentNo: productOnWorkItem.installmentNo,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
}
|
|
},
|
|
});
|
|
else {
|
|
dialog({
|
|
color: 'warning',
|
|
icon: 'mdi-alert',
|
|
title: t('form.warning.title'),
|
|
actionText: t('dialog.action.ok'),
|
|
message: t('dialog.message.warningCopyEmpty'),
|
|
action: async () => {},
|
|
});
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => formService.value.attributes.workflowId,
|
|
async (a, b) => {
|
|
if (a && b && a !== b) {
|
|
handleSubmitWorkflow(a);
|
|
}
|
|
},
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<FloatingActionButton
|
|
hide-icon
|
|
v-if="actionDisplay && !currentNoAction"
|
|
style="z-index: 999"
|
|
@click="
|
|
async () => {
|
|
if (productMode === 'group') {
|
|
clearFormGroup();
|
|
dialogInputForm = true;
|
|
}
|
|
if (productMode === 'product') {
|
|
productTab = 1;
|
|
clearFormProduct();
|
|
dialogProduct = true;
|
|
}
|
|
if (productMode === 'service') {
|
|
serviceTab = 1;
|
|
clearFormGroup();
|
|
clearFormService();
|
|
serviceTab = 1;
|
|
dialogService = true;
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
|
|
<div class="full-height column no-wrap">
|
|
<div class="text-body-2 q-mb-xs flex items-center">
|
|
{{ $t('general.dataSum') }}
|
|
<q-badge
|
|
rounded
|
|
class="q-ml-sm"
|
|
style="
|
|
background-color: hsla(var(--info-bg) / 0.15);
|
|
color: hsl(var(--info-bg));
|
|
"
|
|
>
|
|
{{
|
|
productMode === 'group'
|
|
? stat[0].count
|
|
: productMode === 'service'
|
|
? stat[1].count
|
|
: productMode === 'product'
|
|
? stat[2].count
|
|
: 0
|
|
}}
|
|
</q-badge>
|
|
<q-btn
|
|
class="q-ml-xs"
|
|
icon="mdi-pin-outline"
|
|
color="primary"
|
|
size="sm"
|
|
flat
|
|
dense
|
|
rounded
|
|
@click="hideStat = !hideStat"
|
|
:style="hideStat ? 'rotate: 90deg' : ''"
|
|
style="transition: 0.1s ease-in-out"
|
|
/>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<div class="column col rounded bordered overflow-hidden no-wrap">
|
|
<q-splitter
|
|
v-model="splitterModel"
|
|
:limits="[0, 100]"
|
|
style="width: 100%"
|
|
class="col"
|
|
after-class="overflow-hidden"
|
|
:disable="$q.screen.lt.sm"
|
|
>
|
|
<template v-slot:before>
|
|
<div class="surface-1 column full-height">
|
|
<div
|
|
class="row no-wrap full-width bordered-b text-weight-bold surface-3 items-center q-px-md q-py-sm"
|
|
:style="`min-height: ${$q.screen.gt.sm ? '57px' : ''}`"
|
|
>
|
|
<div v-if="$q.screen.gt.sm" class="col ellipsis-2-lines">
|
|
{{ $t('productService.caption') }}
|
|
</div>
|
|
<q-input
|
|
v-else
|
|
for="input-search"
|
|
outlined
|
|
dense
|
|
:label="$t('general.search')"
|
|
class="col"
|
|
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
|
|
v-model="inputSearch"
|
|
debounce="200"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="mdi-magnify" />
|
|
</template>
|
|
<template v-if="$q.screen.lt.md" v-slot:append>
|
|
<span class="row">
|
|
<q-separator vertical />
|
|
<q-btn
|
|
icon="mdi-filter-variant"
|
|
unelevated
|
|
class="q-ml-sm"
|
|
padding="4px"
|
|
size="sm"
|
|
rounded
|
|
@click="refFilterGroup?.showPopup"
|
|
/>
|
|
</span>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
|
|
<div class="col full-width scroll q-pa-md">
|
|
<q-infinite-scroll
|
|
:offset="10"
|
|
@load="
|
|
async (_, done) => {
|
|
if ($q.screen.gt.sm) return;
|
|
|
|
fetchListGroups().then(() => {
|
|
currentPageGroup = currentPageGroup + 1;
|
|
done(currentPageGroup > maxPageGroup);
|
|
});
|
|
}
|
|
"
|
|
>
|
|
<TreeComponent
|
|
v-model:nodes="treeProductTypeAndGroup"
|
|
v-model:expanded-tree="expandedTree"
|
|
node-key="id"
|
|
label-key="name"
|
|
children-key="children"
|
|
type-tree="product"
|
|
:action="actionDisplay"
|
|
@select="
|
|
async (v: (typeof treeProductTypeAndGroup)[number]) => {
|
|
if (v.type === 'group') {
|
|
if (currentIdGroup !== v.id) {
|
|
await enterGroup(v.id, v.name, v.status);
|
|
|
|
return;
|
|
}
|
|
|
|
if (currentIdGroup === v.id) {
|
|
expandedTree = [];
|
|
filterStat = [];
|
|
expandedTree = expandedTree.filter((i) => v.id !== i);
|
|
currentIdGroup = '';
|
|
currentIdType = '';
|
|
productMode = 'group';
|
|
currentNoAction = false;
|
|
return;
|
|
}
|
|
}
|
|
if (v.type === 'type') {
|
|
if (currentIdType !== v.id) {
|
|
await enterNext('service');
|
|
return;
|
|
}
|
|
if (currentIdType === v.id) {
|
|
expandedTree.pop();
|
|
currentIdType = '';
|
|
filterStat = [];
|
|
productMode = 'group';
|
|
currentNoAction = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (v.type === 'productService') {
|
|
if (currentIdType !== v.id) {
|
|
await enterNext('product');
|
|
|
|
return;
|
|
}
|
|
if (currentIdType === v.id) {
|
|
expandedTree.pop();
|
|
currentIdType = '';
|
|
filterStat = [];
|
|
productMode = 'group';
|
|
currentNoAction = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
"
|
|
@view="
|
|
async (v: (typeof treeProductTypeAndGroup)[number]) => {
|
|
if (v.type === 'group') {
|
|
editByTree = 'group';
|
|
currentStatusProduct = v.status === 'INACTIVE';
|
|
clearFormGroup();
|
|
await assignFormDataGroup(v);
|
|
isEdit = false;
|
|
currentIdGroupTree = v.id;
|
|
drawerInfo = true;
|
|
}
|
|
}
|
|
"
|
|
@edit="
|
|
async (v: (typeof treeProductTypeAndGroup)[number]) => {
|
|
editByTree = v.type as typeof editByTree;
|
|
if (v.type === 'group') {
|
|
clearFormGroup();
|
|
await assignFormDataGroup(v);
|
|
isEdit = true;
|
|
currentIdGroupTree = v.id;
|
|
drawerInfo = true;
|
|
}
|
|
}
|
|
"
|
|
@delete="
|
|
(v: (typeof treeProductTypeAndGroup)[number]) => {
|
|
editByTree = v.type as typeof editByTree;
|
|
if (v.type === 'group') {
|
|
deleteGroupById(v.id);
|
|
}
|
|
}
|
|
"
|
|
@change-status="
|
|
async (v: (typeof treeProductTypeAndGroup)[number]) => {
|
|
if (v.type === 'group') {
|
|
await triggerChangeStatus(v.id, v.status, v.type);
|
|
}
|
|
}
|
|
"
|
|
@handle-hold="handleHold"
|
|
/>
|
|
<template v-slot:loading>
|
|
<div v-if="$q.screen.lt.sm" class="row justify-center">
|
|
<q-spinner-dots color="primary" size="40px" />
|
|
</div>
|
|
</template>
|
|
</q-infinite-scroll>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-slot:after>
|
|
<div class="column no-wrap full-height">
|
|
<!-- group/type -->
|
|
<div
|
|
v-if="productMode === 'group'"
|
|
class="surface-2 items-center full-width full-height column col"
|
|
>
|
|
<!-- tool bar -->
|
|
<div
|
|
class="row q-py-sm q-px-md justify-between full-width surface-3 bordered-b"
|
|
>
|
|
<q-input
|
|
for="input-search"
|
|
outlined
|
|
dense
|
|
:label="$t('general.search')"
|
|
class="col col-md-3"
|
|
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
|
|
v-model="inputSearch"
|
|
debounce="200"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="mdi-magnify" />
|
|
</template>
|
|
<template v-if="$q.screen.lt.md" v-slot:append>
|
|
<span class="row">
|
|
<q-separator vertical />
|
|
<q-btn
|
|
icon="mdi-filter-variant"
|
|
unelevated
|
|
class="q-ml-sm"
|
|
padding="4px"
|
|
size="sm"
|
|
rounded
|
|
@click="refFilterGroup?.showPopup"
|
|
/>
|
|
</span>
|
|
</template>
|
|
</q-input>
|
|
|
|
<div class="row col-md-6" style="white-space: nowrap">
|
|
<q-select
|
|
v-show="$q.screen.gt.sm"
|
|
ref="refFilterGroup"
|
|
v-model="currentStatus"
|
|
for="select-status"
|
|
outlined
|
|
dense
|
|
option-value="value"
|
|
option-label="label"
|
|
class="col"
|
|
:class="{ 'offset-md-5': modeView }"
|
|
map-options
|
|
emit-value
|
|
:hide-dropdown-icon="$q.screen.lt.sm"
|
|
:options="[
|
|
{ label: $t('general.all'), value: 'All' },
|
|
{ label: $t('general.active'), value: 'ACTIVE' },
|
|
{
|
|
label: $t('general.inactive'),
|
|
value: 'INACTIVE',
|
|
},
|
|
]"
|
|
></q-select>
|
|
|
|
<q-select
|
|
v-if="modeView === false"
|
|
id="select-field"
|
|
for="select-field"
|
|
:options="
|
|
tbControl.groupAndType.fieldDisplay.map((x) => ({
|
|
label: $t(x.label),
|
|
value: x.value,
|
|
}))
|
|
"
|
|
:hide-dropdown-icon="$q.screen.lt.sm"
|
|
:display-value="$t('general.displayField')"
|
|
v-model="tbControl.groupAndType.fieldSelected"
|
|
class="col q-ml-sm"
|
|
option-label="label"
|
|
option-value="value"
|
|
map-options
|
|
emit-value
|
|
outlined
|
|
multiple
|
|
dense
|
|
/>
|
|
|
|
<q-btn-toggle
|
|
v-model="modeView"
|
|
id="btn-mode"
|
|
dense
|
|
class="no-shadow bordered rounded surface-1 q-ml-sm"
|
|
: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>
|
|
|
|
<div
|
|
v-if="
|
|
productGroup?.length === 0 &&
|
|
productMode === 'group' &&
|
|
stat[0].count !== undefined
|
|
"
|
|
class="items-center justify-center full-width flex col"
|
|
>
|
|
<NoData :not-found="!!inputSearch" />
|
|
</div>
|
|
|
|
<template v-else>
|
|
<div class="surface-2 q-pa-md scroll col full-width">
|
|
<q-table
|
|
flat
|
|
bordered
|
|
:grid="modeView"
|
|
:rows="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
|
|
style="background-color: hsla(var(--info-bg) / 0.07)"
|
|
: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
|
|
:style="
|
|
props.rowIndex % 2 !== 0
|
|
? $q.dark.isActive
|
|
? 'background: hsl(var(--gray-11-hsl)/0.2)'
|
|
: `background: #f9fafc`
|
|
: ''
|
|
"
|
|
:id="`enter-${props.row.name}`"
|
|
class="cursor-pointer"
|
|
:class="{
|
|
'app-text-muted': props.row.status === 'INACTIVE',
|
|
'status-active': props.row.status !== 'INACTIVE',
|
|
'status-inactive': props.row.status === 'INACTIVE',
|
|
}"
|
|
:props="props"
|
|
@click="
|
|
async () => {
|
|
filterStat.push(productMode);
|
|
if (productMode === 'group') {
|
|
await enterGroup(
|
|
props.row.id,
|
|
props.row.name,
|
|
props.row.status,
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
"
|
|
>
|
|
<q-td
|
|
class="text-center"
|
|
v-if="
|
|
tbControl.groupAndType.fieldSelected.includes(
|
|
'branchLabelNo',
|
|
)
|
|
"
|
|
>
|
|
{{
|
|
$q.screen.xs
|
|
? props.rowIndex + 1
|
|
: (currentPageGroup - 1) * pageSizeGroup +
|
|
props.rowIndex +
|
|
1
|
|
}}
|
|
</q-td>
|
|
<q-td
|
|
v-if="
|
|
tbControl.groupAndType.fieldSelected.includes(
|
|
'name',
|
|
)
|
|
"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<div
|
|
style="
|
|
width: 50px;
|
|
display: flex;
|
|
margin-bottom: var(--size-2);
|
|
"
|
|
>
|
|
<div
|
|
:class="`icon-color-${productMode === 'group' ? 'pink' : 'purple'}`"
|
|
class="table__icon"
|
|
>
|
|
<q-icon
|
|
size="md"
|
|
style="scale: 0.8"
|
|
:name="
|
|
productMode === 'group'
|
|
? 'mdi-folder-outline'
|
|
: 'mdi-folder-table-outline'
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="ellipsis" style="max-width: 20vw">
|
|
{{ props.row.name }}
|
|
<q-tooltip>
|
|
{{ props.row.name }}
|
|
</q-tooltip>
|
|
</div>
|
|
<div class="app-text-muted">
|
|
{{ props.row.code || '-' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</q-td>
|
|
<q-td
|
|
class="ellipsis"
|
|
style="max-width: 150px"
|
|
v-if="
|
|
tbControl.groupAndType.fieldSelected.includes(
|
|
'detail',
|
|
)
|
|
"
|
|
>
|
|
{{ props.row.detail || '-' }}
|
|
</q-td>
|
|
|
|
<q-td
|
|
class="ellipsis"
|
|
style="max-width: 150px"
|
|
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="
|
|
async () => {
|
|
if (productMode === 'group') {
|
|
editByTree = 'group';
|
|
currentStatusProduct =
|
|
props.row.status === 'INACTIVE';
|
|
clearFormGroup();
|
|
await assignFormDataGroup(props.row);
|
|
isEdit = false;
|
|
currentIdGroup = props.row.id;
|
|
drawerInfo = true;
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
|
|
<KebabAction
|
|
:disable-delete="props.row.status !== 'CREATED'"
|
|
:status="props.row.status"
|
|
:id-name="props.row.name"
|
|
@view="
|
|
async () => {
|
|
if (productMode === 'group') {
|
|
editByTree = 'group';
|
|
currentStatusProduct =
|
|
props.row.status === 'INACTIVE';
|
|
clearFormGroup();
|
|
await assignFormDataGroup(props.row);
|
|
isEdit = false;
|
|
currentIdGroup = props.row.id;
|
|
drawerInfo = true;
|
|
}
|
|
}
|
|
"
|
|
@edit="
|
|
async () => {
|
|
if (productMode === 'group') {
|
|
editByTree = 'group';
|
|
clearFormGroup();
|
|
await assignFormDataGroup(props.row);
|
|
isEdit = true;
|
|
currentIdGroup = props.row.id;
|
|
|
|
drawerInfo = true;
|
|
}
|
|
}
|
|
"
|
|
@delete="
|
|
() => {
|
|
if (productMode === 'group') {
|
|
deleteGroupById(props.row.id);
|
|
}
|
|
}
|
|
"
|
|
@change-status="
|
|
async () => {
|
|
triggerChangeStatus(
|
|
props.row.id,
|
|
props.row.status,
|
|
);
|
|
}
|
|
"
|
|
/>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
|
|
<template v-slot:item="props">
|
|
<div class="col-12 col-md-6 col-lg-4 column">
|
|
<ProductCardComponent
|
|
class="col"
|
|
: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="`enter-${props.row.name}`"
|
|
:for="`enter-${props.row.name}`"
|
|
:is-disabled="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)'
|
|
"
|
|
:action="actionDisplay"
|
|
@toggle-status="
|
|
triggerChangeStatus(props.row.id, props.row.status)
|
|
"
|
|
@view-card="
|
|
async () => {
|
|
if (productMode === 'group') {
|
|
editByTree = 'group';
|
|
currentStatusProduct =
|
|
props.row.status === 'INACTIVE';
|
|
clearFormGroup();
|
|
await assignFormDataGroup(props.row);
|
|
isEdit = false;
|
|
currentIdGroup = props.row.id;
|
|
drawerInfo = true;
|
|
}
|
|
}
|
|
"
|
|
@update-card="
|
|
async () => {
|
|
if (productMode === 'group') {
|
|
clearFormGroup();
|
|
await assignFormDataGroup(props.row);
|
|
isEdit = true;
|
|
currentIdGroup = props.row.id;
|
|
|
|
drawerInfo = true;
|
|
}
|
|
}
|
|
"
|
|
@delete-card="
|
|
() => {
|
|
if (productMode === 'group') {
|
|
deleteGroupById(props.row.id);
|
|
}
|
|
}
|
|
"
|
|
@on-click="
|
|
async () => {
|
|
filterStat.push(productMode);
|
|
if (productMode === 'group') {
|
|
await enterGroup(
|
|
props.row.id,
|
|
props.row.name,
|
|
props.row.status,
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</q-table>
|
|
</div>
|
|
|
|
<!-- footer group -->
|
|
<footer
|
|
v-if="productMode === 'group' && $q.screen.gt.xs"
|
|
class="row items-center justify-between q-px-md q-py-sm full-width"
|
|
>
|
|
<div class="col-4">
|
|
<div class="row items-center">
|
|
<div
|
|
class="app-text-muted q-mr-sm"
|
|
v-if="$q.screen.gt.sm"
|
|
>
|
|
{{ $t('general.recordPerPage') }}
|
|
</div>
|
|
<div>
|
|
<PaginationPageSize v-model="pageSizeGroup" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-4 row justify-center app-text-muted">
|
|
{{
|
|
$q.screen.gt.sm
|
|
? $t('general.recordsPage', {
|
|
resultcurrentPage: productGroup?.length,
|
|
total: totalGroup,
|
|
})
|
|
: $t('general.ofPage', {
|
|
current: productGroup?.length,
|
|
total: totalGroup,
|
|
})
|
|
}}
|
|
</div>
|
|
|
|
<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>
|
|
</footer>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- product/service -->
|
|
<div
|
|
v-else-if="productMode === 'service' || productMode === 'product'"
|
|
class="surface-1 col column no-wrap"
|
|
style="overflow: hidden"
|
|
>
|
|
<div
|
|
class="row justify-between q-px-md q-py-sm surface-3 bordered-b"
|
|
>
|
|
<q-input
|
|
for="input-search"
|
|
class="col col-md-3"
|
|
outlined
|
|
dense
|
|
unelavated
|
|
:label="$t('general.search')"
|
|
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
|
|
v-model="inputSearchProductAndService"
|
|
debounce="250"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="mdi-magnify" />
|
|
</template>
|
|
<template v-if="$q.screen.lt.md" v-slot:append>
|
|
<span class="row">
|
|
<q-separator vertical />
|
|
<q-btn
|
|
icon="mdi-filter-variant"
|
|
unelevated
|
|
class="q-ml-sm"
|
|
padding="4px"
|
|
size="sm"
|
|
rounded
|
|
@click="refFilterProductService?.showPopup"
|
|
/>
|
|
</span>
|
|
</template>
|
|
</q-input>
|
|
|
|
<div class="row col-md-6" style="white-space: nowrap">
|
|
<q-select
|
|
v-show="$q.screen.gt.sm"
|
|
ref="refFilterProductService"
|
|
:for="'field-select-status'"
|
|
v-model="currentStatus"
|
|
outlined
|
|
dense
|
|
option-value="value"
|
|
:hide-dropdown-icon="$q.screen.lt.sm"
|
|
option-label="label"
|
|
:class="{ 'offset-md-5': modeView }"
|
|
class="col"
|
|
map-options
|
|
emit-value
|
|
:options="[
|
|
{ label: $t('general.all'), value: 'All' },
|
|
{ label: $t('general.active'), value: 'ACTIVE' },
|
|
{
|
|
label: $t('general.inactive'),
|
|
value: 'INACTIVE',
|
|
},
|
|
]"
|
|
@update:model-value="fetchStatus()"
|
|
></q-select>
|
|
|
|
<q-select
|
|
v-if="modeView === false"
|
|
:hide-dropdown-icon="$q.screen.lt.sm"
|
|
id="select-field"
|
|
for="select-field"
|
|
class="col q-ml-sm"
|
|
:options="
|
|
{
|
|
product: tbControl.product.fieldDisplay,
|
|
service: tbControl.service.fieldDisplay,
|
|
}[productAndServiceTab].map((x) => ({
|
|
label: $t(x.label),
|
|
value: x.value,
|
|
}))
|
|
"
|
|
:display-value="$t('general.displayField')"
|
|
:model-value="
|
|
{
|
|
product: tbControl.product.fieldSelected,
|
|
service: tbControl.service.fieldSelected,
|
|
}[productAndServiceTab]
|
|
"
|
|
@update:model-value="
|
|
(v: string[]) =>
|
|
(tbControl[productAndServiceTab].fieldSelected = v)
|
|
"
|
|
option-label="label"
|
|
option-value="value"
|
|
map-options
|
|
emit-value
|
|
outlined
|
|
multiple
|
|
dense
|
|
/>
|
|
|
|
<q-btn-toggle
|
|
v-model="modeView"
|
|
id="btn-mode"
|
|
dense
|
|
class="no-shadow bordered rounded surface-1 q-ml-sm"
|
|
: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>
|
|
|
|
<div
|
|
v-if="
|
|
(productAndServiceTab === 'product' &&
|
|
product?.length === 0) ||
|
|
(productAndServiceTab === 'service' && service?.length === 0)
|
|
"
|
|
class="flex justify-center items-center surface-2 col"
|
|
>
|
|
<NoData :not-found="!!inputSearchProductAndService" />
|
|
</div>
|
|
<!-- tab -->
|
|
<template v-else>
|
|
<div
|
|
class="flex scroll full-width q-pa-md surface-2 column col"
|
|
>
|
|
<q-infinite-scroll
|
|
:offset="10"
|
|
@load="
|
|
(_, done) => {
|
|
if ($q.screen.gt.xs) return;
|
|
currentPageServiceAndProduct =
|
|
currentPageServiceAndProduct + 1;
|
|
alternativeFetch().then(() => {
|
|
done(
|
|
currentPageServiceAndProduct >=
|
|
maxPageServiceAndProduct,
|
|
);
|
|
});
|
|
}
|
|
"
|
|
>
|
|
<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
|
|
style="background-color: hsla(var(--info-bg) / 0.07)"
|
|
: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
|
|
:style="
|
|
props.rowIndex % 2 !== 0
|
|
? $q.dark.isActive
|
|
? 'background: hsl(var(--gray-11-hsl)/0.2)'
|
|
: `background: #f9fafc`
|
|
: ''
|
|
"
|
|
:class="{
|
|
'app-text-muted': props.row.status === 'INACTIVE',
|
|
}"
|
|
:props="props"
|
|
>
|
|
<q-td
|
|
class="text-center"
|
|
v-if="
|
|
{
|
|
product: tbControl.product.fieldSelected,
|
|
|
|
service: tbControl.service.fieldSelected,
|
|
}[productAndServiceTab].includes(
|
|
{
|
|
product: 'branchLabelNo',
|
|
service: 'branchLabelNo',
|
|
}[productAndServiceTab],
|
|
)
|
|
"
|
|
>
|
|
{{
|
|
$q.screen.xs
|
|
? props.rowIndex + 1
|
|
: (currentPageServiceAndProduct - 1) *
|
|
pageSizeServiceAndProduct +
|
|
props.rowIndex +
|
|
1
|
|
}}
|
|
</q-td>
|
|
|
|
<q-td
|
|
v-if="
|
|
{
|
|
product: tbControl.product.fieldSelected,
|
|
|
|
service: tbControl.service.fieldSelected,
|
|
}[productAndServiceTab].includes(
|
|
{
|
|
product: 'productName',
|
|
service: 'serviceName',
|
|
}[productAndServiceTab],
|
|
)
|
|
"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<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-avatar size="md">
|
|
<q-img
|
|
class="text-center"
|
|
:ratio="1"
|
|
:src="`${baseUrl}/${productAndServiceTab}/${props.row.id}/image/${props.row.selectedImage}`"
|
|
>
|
|
<template #error>
|
|
<q-icon
|
|
size="sm"
|
|
:name="
|
|
productAndServiceTab === 'product'
|
|
? 'mdi-shopping-outline'
|
|
: 'mdi-server-outline'
|
|
"
|
|
style="top: 10%"
|
|
:style="`color: var(--${productAndServiceTab === 'product' ? 'teal-10' : 'orange-5'})`"
|
|
/>
|
|
</template>
|
|
</q-img>
|
|
</q-avatar>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="ellipsis" style="max-width: 20vw">
|
|
{{ props.row.name }}
|
|
<q-tooltip
|
|
anchor="bottom left"
|
|
self="center left"
|
|
:delay="300"
|
|
>
|
|
{{ props.row.name }}
|
|
</q-tooltip>
|
|
</div>
|
|
<div class="app-text-muted">
|
|
{{ props.row.code }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</q-td>
|
|
|
|
<q-td
|
|
class="ellipsis"
|
|
style="max-width: 150px"
|
|
v-if="
|
|
{
|
|
product: tbControl.product.fieldSelected,
|
|
service: tbControl.service.fieldSelected,
|
|
}[productAndServiceTab].includes(
|
|
{
|
|
product: 'productDetail',
|
|
service: 'serviceDetail',
|
|
}[productAndServiceTab],
|
|
)
|
|
"
|
|
>
|
|
{{
|
|
props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') ||
|
|
'-'
|
|
}}
|
|
<!-- <div
|
|
class="ellipsis"
|
|
style="max-width: 150px"
|
|
v-html="props.row.detail || '-'"
|
|
/> -->
|
|
</q-td>
|
|
|
|
<q-td
|
|
v-if="
|
|
{
|
|
product: tbControl.product.fieldSelected,
|
|
service: tbControl.service.fieldSelected,
|
|
}[productAndServiceTab].includes(
|
|
{
|
|
product: 'productExpenseType',
|
|
service: 'serviceWorkTotal',
|
|
}[productAndServiceTab],
|
|
)
|
|
"
|
|
>
|
|
{{
|
|
optionStore.mapOption(
|
|
{
|
|
product: props.row.expenseType || '-',
|
|
service: props.row.work?.length,
|
|
}[productAndServiceTab],
|
|
)
|
|
}}
|
|
</q-td>
|
|
<q-td
|
|
v-if="
|
|
{
|
|
product: tbControl.product.fieldSelected,
|
|
service: tbControl.service.fieldSelected,
|
|
}[productAndServiceTab].includes(
|
|
{
|
|
product: 'productProcessingTime',
|
|
service: 'empty',
|
|
}[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: 'productVat',
|
|
service: 'empty',
|
|
}[productAndServiceTab],
|
|
)
|
|
"
|
|
>
|
|
{{
|
|
{
|
|
product: props.row.vatIncluded,
|
|
service: props.row.work?.length,
|
|
}[productAndServiceTab]
|
|
? $t('productService.product.vatIncluded')
|
|
: $t('productService.product.vatExcluded')
|
|
}}
|
|
</q-td>
|
|
<q-td
|
|
v-if="
|
|
{
|
|
product: tbControl.product.fieldSelected,
|
|
service: tbControl.service.fieldSelected,
|
|
}[productAndServiceTab].includes(
|
|
{
|
|
product: 'priceInformation',
|
|
service: 'empty',
|
|
}[productAndServiceTab],
|
|
)
|
|
"
|
|
>
|
|
<div
|
|
class="row full-width q-gutter-x-md no-wrap items-center text-right"
|
|
>
|
|
<div
|
|
class="tags tags-color-orange col column ellipsis-2-lines"
|
|
:class="{
|
|
disable: props.row.status === 'INACTIVE',
|
|
}"
|
|
style="min-width: 50px"
|
|
v-if="priceDisplay.price"
|
|
>
|
|
<div class="col app-text-muted-2 text-caption">
|
|
{{ $t('productService.product.salePrice') }}
|
|
</div>
|
|
<div class="col text-weight-bold">
|
|
฿{{
|
|
formatNumberDecimal(props.row.price || 0, 2)
|
|
}}
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="tags tags-color-purple col column ellipsis-2-lines"
|
|
:class="{
|
|
disable: props.row.status === 'INACTIVE',
|
|
}"
|
|
style="min-width: 50px"
|
|
v-if="priceDisplay.agentPrice"
|
|
>
|
|
<div class="col app-text-muted-2 text-caption">
|
|
{{ $t('productService.product.agentPrice') }}
|
|
</div>
|
|
<div class="col text-weight-bold">
|
|
฿{{
|
|
formatNumberDecimal(
|
|
props.row.agentPrice || 0,
|
|
2,
|
|
)
|
|
}}
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="tags tags-color-pink col column ellipsis-2-lines"
|
|
:class="{
|
|
disable: props.row.status === 'INACTIVE',
|
|
}"
|
|
style="min-width: 50px"
|
|
v-if="priceDisplay.serviceCharge"
|
|
>
|
|
<div class="col app-text-muted-2 text-caption">
|
|
{{
|
|
$t('productService.product.processingPrice')
|
|
}}
|
|
</div>
|
|
<div class="col">
|
|
฿{{
|
|
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 () => {
|
|
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;
|
|
assignFormService(props.row.id);
|
|
dialogServiceEdit = true;
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
<KebabAction
|
|
:use-copy="productAndServiceTab === 'service'"
|
|
:status="props.row.status"
|
|
:id-name="props.row.name"
|
|
@copy="
|
|
() => {
|
|
notify('create', $t('dialog.message.copy'));
|
|
currentCopy = {
|
|
id: props.row.id,
|
|
type: productAndServiceTab,
|
|
};
|
|
}
|
|
"
|
|
@view="
|
|
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;
|
|
assignFormService(props.row.id);
|
|
dialogServiceEdit = true;
|
|
}
|
|
}
|
|
"
|
|
@edit="
|
|
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;
|
|
assignFormService(props.row.id);
|
|
dialogServiceEdit = true;
|
|
}
|
|
}
|
|
"
|
|
@delete="
|
|
() => {
|
|
if (props.row.type === 'product') {
|
|
deleteProductConfirm(props.row.id);
|
|
}
|
|
if (props.row.type === 'service') {
|
|
deleteServiceConfirm(props.row.id);
|
|
}
|
|
}
|
|
"
|
|
@change-status="
|
|
() => {
|
|
triggerChangeStatus(
|
|
props.row.id,
|
|
props.row.status,
|
|
props.row.type,
|
|
);
|
|
}
|
|
"
|
|
/>
|
|
</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"
|
|
:is-disabled="
|
|
row.status === 'INACTIVE' ? true : false
|
|
"
|
|
:action="actionDisplay"
|
|
:price-display="priceDisplay"
|
|
@toggle-status="
|
|
() => {
|
|
triggerChangeStatus(
|
|
row.id,
|
|
row.status,
|
|
row.type,
|
|
);
|
|
}
|
|
"
|
|
@menu-view-detail="
|
|
async () => {
|
|
if (row.type === 'product') {
|
|
currentIdProduct = row.id;
|
|
assignFormDataProduct(row);
|
|
dialogProductEdit = true;
|
|
}
|
|
if (row.type === 'service') {
|
|
currentIdService = row.id;
|
|
infoServiceEdit = false;
|
|
assignFormService(row.id);
|
|
dialogServiceEdit = true;
|
|
}
|
|
}
|
|
"
|
|
@menu-edit="
|
|
async () => {
|
|
if (row.type === 'product') {
|
|
currentIdProduct = row.id;
|
|
infoProductEdit = true;
|
|
assignFormDataProduct(row);
|
|
dialogProductEdit = true;
|
|
}
|
|
if (row.type === 'service') {
|
|
currentIdService = row.id;
|
|
infoServiceEdit = true;
|
|
assignFormService(row.id);
|
|
dialogServiceEdit = true;
|
|
}
|
|
}
|
|
"
|
|
@menu-delete="
|
|
() => {
|
|
if (row.type === 'product') {
|
|
deleteProductConfirm(row.id);
|
|
}
|
|
if (row.type === 'service') {
|
|
deleteServiceConfirm(row.id);
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</q-table>
|
|
<template v-slot:loading>
|
|
<div
|
|
v-if="
|
|
$q.screen.lt.sm &&
|
|
currentPageServiceAndProduct !==
|
|
maxPageServiceAndProduct
|
|
"
|
|
class="row justify-center"
|
|
>
|
|
<q-spinner-dots color="primary" size="40px" />
|
|
</div>
|
|
</template>
|
|
</q-infinite-scroll>
|
|
</div>
|
|
</template>
|
|
<!-- footer product service -->
|
|
<footer
|
|
v-if="$q.screen.gt.xs"
|
|
class="row items-center justify-between q-py-sm q-px-md surface-2"
|
|
>
|
|
<div class="col-4">
|
|
<div class="row items-center">
|
|
<div class="app-text-muted q-mr-sm" v-if="$q.screen.gt.sm">
|
|
{{ $t('general.recordPerPage') }}
|
|
</div>
|
|
<div>
|
|
<PaginationPageSize v-model="pageSizeServiceAndProduct" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-4 row justify-center app-text-muted">
|
|
{{
|
|
$q.screen.gt.sm
|
|
? $t('general.recordsPage', {
|
|
resultcurrentPage:
|
|
productAndServiceTab === 'product'
|
|
? product?.length
|
|
: service?.length,
|
|
total: total,
|
|
})
|
|
: $t('general.ofPage', {
|
|
current:
|
|
productAndServiceTab === 'product'
|
|
? product?.length
|
|
: service?.length,
|
|
total: total,
|
|
})
|
|
}}
|
|
</div>
|
|
<div class="col-4 row justify-end">
|
|
<PaginationComponent
|
|
v-model:current-page="currentPageServiceAndProduct"
|
|
v-model:max-page="maxPageServiceAndProduct"
|
|
:fetch-data="
|
|
async () => {
|
|
await alternativeFetch();
|
|
flowStore.rotate();
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</q-splitter>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- add group, add type -->
|
|
<DialogForm
|
|
v-model:modal="dialogInputForm"
|
|
noAddress
|
|
hide-footer
|
|
:title="$t(`productService.${productMode}.addTitle`)"
|
|
:submit="() => submitGroup()"
|
|
:close="clearFormGroup"
|
|
>
|
|
<div
|
|
:class="{
|
|
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
|
'q-mx-md q-my-sm': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<ProfileBanner
|
|
prefix="dialog"
|
|
readonly
|
|
no-image-action
|
|
active
|
|
hide-fade
|
|
hide-active
|
|
use-toggle
|
|
:img="`/images/product-service-${productMode}-avatar-add.png`"
|
|
:toggle-title="$t('status.title')"
|
|
:icon="
|
|
productMode === 'group'
|
|
? 'mdi-folder-plus-outline'
|
|
: 'mdi-folder-table-outline'
|
|
"
|
|
:title="formDataGroup.name"
|
|
:caption="formDataGroup.code"
|
|
:fallback-cover="`/images/product-service-${productMode}-banner.png`"
|
|
:color="`hsla(var(${
|
|
productMode === 'group'
|
|
? '--pink-6'
|
|
: $q.dark.isActive
|
|
? '--violet-10'
|
|
: '--violet-11'
|
|
}-hsl)/1)`"
|
|
:bg-color="`hsla(var(${
|
|
productMode === 'group'
|
|
? '--pink-6'
|
|
: $q.dark.isActive
|
|
? '--violet-10'
|
|
: '--violet-11'
|
|
}-hsl)/0.1)`"
|
|
v-model:toggle-status="currentStatusGroupType"
|
|
@update:toggle-status="
|
|
() => {
|
|
currentStatusGroupType =
|
|
currentStatusGroupType === 'CREATED' ? 'INACTIVE' : 'CREATED';
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="col surface-1 rounded bordered scroll row relative-position"
|
|
:class="{
|
|
'q-mb-lg q-mx-lg ': $q.screen.gt.sm,
|
|
'q-mb-sm q-mx-md': !$q.screen.gt.sm,
|
|
}"
|
|
id="group-form"
|
|
>
|
|
<div
|
|
class="col"
|
|
style="height: 100%; max-height: 100; overflow-y: auto"
|
|
v-if="$q.screen.gt.sm"
|
|
>
|
|
<div class="q-py-md q-pl-md q-pr-sm">
|
|
<SideMenu
|
|
:menu="[
|
|
{
|
|
name: $t('form.field.basicInformation'),
|
|
anchor: 'form-group',
|
|
},
|
|
]"
|
|
background="transparent"
|
|
:active="{
|
|
background: 'hsla(var(--blue-6-hsl) / .2)',
|
|
foreground: 'var(--blue-6)',
|
|
}"
|
|
scroll-element="#group-form"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="col-12 col-md-10"
|
|
id="customer-form-content"
|
|
:class="{
|
|
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
|
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
|
}"
|
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
|
>
|
|
<div
|
|
class="q-py-md q-px-lg"
|
|
style="position: absolute; z-index: 99999; top: 0; right: 0"
|
|
>
|
|
<div class="surface-1 row rounded">
|
|
<SaveButton id="btn-info-basic-save" icon-only type="submit" />
|
|
</div>
|
|
</div>
|
|
<!-- :isType="productMode === 'type'" -->
|
|
<BasicInformation
|
|
ide="form-group"
|
|
dense
|
|
v-model:remark="formDataGroup.remark"
|
|
v-model:name="formDataGroup.name"
|
|
v-model:detail="formDataGroup.detail"
|
|
v-model:shared="formDataGroup.shared"
|
|
v-model:registered-branch-id="formDataGroup.registeredBranchId"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</DialogForm>
|
|
|
|
<!-- edit group, edit type -->
|
|
<DrawerInfo
|
|
ref="formDialogRef"
|
|
v-model:drawer-open="drawerInfo"
|
|
:title="
|
|
$t(
|
|
editByTree === 'group'
|
|
? 'productService.group.withName'
|
|
: editByTree === 'type'
|
|
? 'productService.type.withName'
|
|
: productMode === 'group'
|
|
? 'productService.group.withName'
|
|
: 'productService.type.withName',
|
|
{
|
|
name: formDataGroup.code,
|
|
},
|
|
)
|
|
"
|
|
:undo="() => undoProductGroup()"
|
|
:submit="
|
|
() => {
|
|
if (editByTree !== undefined) {
|
|
if (editByTree === 'group') {
|
|
submitGroup();
|
|
}
|
|
editByTree = undefined;
|
|
} else {
|
|
if (productMode === 'group') {
|
|
submitGroup();
|
|
}
|
|
}
|
|
}
|
|
"
|
|
:delete-data="() => deleteGroupById()"
|
|
:close="
|
|
() => {
|
|
(drawerInfo = false), (currentIdGroupType = '');
|
|
}
|
|
"
|
|
hide-action
|
|
>
|
|
<InfoForm>
|
|
<div
|
|
:class="{
|
|
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
|
'q-mx-md q-my-sm': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<ProfileBanner
|
|
:prefix="formDataGroup.name"
|
|
no-image-action
|
|
:active="currentStatusGroupType !== 'INACTIVE'"
|
|
hide-fade
|
|
:use-toggle="actionDisplay"
|
|
:readonly="!isEdit"
|
|
:icon="
|
|
editByTree === 'group'
|
|
? 'mdi-folder-outline'
|
|
: 'mdi-folder-table-outline'
|
|
"
|
|
:fallback-cover="`/images/product-service-${editByTree}-banner.png`"
|
|
v-model:toggle-status="currentStatusGroupType"
|
|
:color="`hsla(var(${
|
|
editByTree === 'group'
|
|
? '--pink-6'
|
|
: $q.dark.isActive
|
|
? '--violet-10'
|
|
: '--violet-11'
|
|
}-hsl)/1)`"
|
|
:bg-color="`hsla(var(${
|
|
editByTree === 'group'
|
|
? '--pink-6'
|
|
: $q.dark.isActive
|
|
? '--violet-10'
|
|
: '--violet-11'
|
|
}-hsl)/0.1)`"
|
|
:title="formDataGroup.name"
|
|
:caption="formDataGroup.code"
|
|
:toggle-title="$t('status.title')"
|
|
@update:toggle-status="
|
|
async (v) => {
|
|
await triggerChangeStatus(currentIdGroupType, v, productMode);
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="col"
|
|
:class="{
|
|
'q-mb-lg q-mx-lg ': $q.screen.gt.sm,
|
|
'q-mb-sm q-mx-md': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<div
|
|
style="overflow-y: auto"
|
|
class="row full-width full-height surface-1 rounded relative-position"
|
|
>
|
|
<div
|
|
class="q-py-md q-px-lg"
|
|
style="position: absolute; z-index: 99999; top: 0; right: 0"
|
|
v-if="actionDisplay"
|
|
>
|
|
<div
|
|
class="surface-1 row rounded"
|
|
v-if="currentStatusGroupType !== 'INACTIVE' && !currentNoAction"
|
|
>
|
|
<UndoButton
|
|
v-if="isEdit"
|
|
icon-only
|
|
@click="undoProductGroup()"
|
|
type="button"
|
|
/>
|
|
<SaveButton
|
|
v-if="isEdit"
|
|
id="btn-info-basic-save"
|
|
icon-only
|
|
type="submit"
|
|
/>
|
|
<EditButton
|
|
v-if="!isEdit"
|
|
id="btn-info-basic-edit"
|
|
icon-only
|
|
@click="isEdit = true"
|
|
type="button"
|
|
/>
|
|
<DeleteButton
|
|
v-if="!isEdit"
|
|
id="btn-info-basic-delete"
|
|
icon-only
|
|
@click="deleteGroupById()"
|
|
type="button"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="$q.screen.gt.sm"
|
|
class="col full-height rounded scroll row q-py-md q-pl-md q-pr-sm"
|
|
>
|
|
<SideMenu
|
|
:menu="[
|
|
{
|
|
name: $t('form.field.basicInformation'),
|
|
anchor: 'info-group',
|
|
},
|
|
]"
|
|
background="transparent"
|
|
:active="{
|
|
background: 'hsla(var(--blue-6-hsl) / .2)',
|
|
foreground: 'var(--blue-6)',
|
|
}"
|
|
scroll-element="#group-info"
|
|
style="width: 100%"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="col-12 col-md-10 full-height"
|
|
id="group-info"
|
|
:class="{
|
|
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
|
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
|
}"
|
|
style="overflow-y: auto"
|
|
>
|
|
<!-- :isType="productMode === 'type'" -->
|
|
|
|
<BasicInformation
|
|
id="info-group"
|
|
dense
|
|
:readonly="!isEdit"
|
|
branch-readonly
|
|
v-model:registered-branch-id="formDataGroup.registeredBranchId"
|
|
v-model:remark="formDataGroup.remark"
|
|
v-model:name="formDataGroup.name"
|
|
v-model:code="formDataGroup.code"
|
|
v-model:shared="formDataGroup.shared"
|
|
v-model:detail="formDataGroup.detail"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</InfoForm>
|
|
</DrawerInfo>
|
|
|
|
<!-- work product, product work, product service, service product -->
|
|
<DialogForm
|
|
v-model:modal="dialogTotalProduct"
|
|
:submit-label="$t('general.select')"
|
|
no-address
|
|
no-appBox
|
|
:title="$t('productService.product.allProduct')"
|
|
:save-amount="selectProduct.length"
|
|
:submit="() => submitAddWorkProduct()"
|
|
:close="
|
|
() => {
|
|
dialogTotalProduct = false;
|
|
inputSearchWorkProduct = '';
|
|
}
|
|
"
|
|
>
|
|
<div class="full-width q-pa-lg column full-height no-wrap">
|
|
<div class="row items-center q-mb-md" v-if="productIsAdd?.length !== 0">
|
|
<q-checkbox
|
|
:label="$t('general.selectAll')"
|
|
:model-value="isSelectAll"
|
|
@click="
|
|
() => {
|
|
if (isSelectAll) {
|
|
!!inputSearchWorkProduct
|
|
? deleteSelectAllAtSearch()
|
|
: (selectProduct = []);
|
|
} else {
|
|
!!inputSearchWorkProduct
|
|
? resultSearchProduct && selectAllProduct(resultSearchProduct)
|
|
: productIsAdd && selectAllProduct(productIsAdd);
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
|
|
<q-btn-toggle
|
|
v-model="modeViewIsAdd"
|
|
id="btn-mode"
|
|
dense
|
|
class="no-shadow bordered rounded surface-1 q-ml-auto q-mr-sm"
|
|
: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
|
|
? modeViewIsAdd
|
|
? '#C9D3DB '
|
|
: '#787B7C'
|
|
: modeViewIsAdd
|
|
? '#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>
|
|
<q-input
|
|
id="input-search-add-product"
|
|
outlined
|
|
dense
|
|
:label="$t('general.search')"
|
|
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
|
|
v-model="inputSearchWorkProduct"
|
|
debounce="500"
|
|
@update:model-value="searchProduct()"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="mdi-magnify" />
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
|
|
<div
|
|
class="flex col justify-center items-center col"
|
|
v-if="
|
|
(!!inputSearchWorkProduct && resultSearchProduct?.length === 0) ||
|
|
productIsAdd?.length === 0
|
|
"
|
|
>
|
|
<NoData
|
|
:not-found="resultSearchProduct?.length === 0"
|
|
:text="
|
|
productIsAdd?.length === 0
|
|
? $t('productService.product.noProduct')
|
|
: ''
|
|
"
|
|
/>
|
|
</div>
|
|
<div v-else class="flex col scroll" style="max-height: 100%">
|
|
<TableProduct
|
|
class="col"
|
|
card-container-class="row q-col-gutter-md"
|
|
:row="!!inputSearchWorkProduct ? resultSearchProduct : productIsAdd"
|
|
:column="tbColumn.product"
|
|
:fieldSelected="tbControl.product.fieldSelected"
|
|
:grid="modeViewIsAdd"
|
|
v-model:selectedItem="selectProduct"
|
|
@sort="
|
|
(isSort) => {
|
|
fetchListOfProductIsAdd(currentIdGroup, isSort);
|
|
}
|
|
"
|
|
@select="
|
|
(data) => {
|
|
{
|
|
const tempValue = selectProduct.find((v) => v.id === data.id);
|
|
if (tempValue) {
|
|
selectProduct = selectProduct.filter((v) => v.id !== data.id);
|
|
} else {
|
|
selectProduct.push(data);
|
|
}
|
|
}
|
|
}
|
|
"
|
|
>
|
|
<template #grid="{ row }">
|
|
<TotalProductCardComponent
|
|
no-time-img
|
|
:index="selectProduct.findIndex((v) => v.id === row.id)"
|
|
:is-add-product="!!selectProduct.find((v) => v.id === row.id)"
|
|
:action="false"
|
|
:data="{ ...row, type: 'product' }"
|
|
:title="row.name"
|
|
:status="row.status === 'INACTIVE' ? true : false"
|
|
:price-display="priceDisplay"
|
|
@menu-view-detail="
|
|
() => {
|
|
currentIdProduct = row.id;
|
|
assignFormDataProduct(row);
|
|
dialogProductEdit = true;
|
|
}
|
|
"
|
|
@menu-edit="
|
|
() => {
|
|
currentIdProduct = row.id;
|
|
infoProductEdit = true;
|
|
assignFormDataProduct(row);
|
|
dialogProductEdit = true;
|
|
}
|
|
"
|
|
@view-detail="
|
|
() => {
|
|
currentIdProduct = row.id;
|
|
infoProductEdit = false;
|
|
assignFormDataProduct(row);
|
|
dialogProductEdit = true;
|
|
}
|
|
"
|
|
@select="
|
|
(data) => {
|
|
const tempValue = selectProduct.find((v) => v.id === row.id);
|
|
if (tempValue) {
|
|
selectProduct = selectProduct.filter(
|
|
(v) => v.id !== row.id,
|
|
);
|
|
} else {
|
|
selectProduct.push(data);
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
</TableProduct>
|
|
</div>
|
|
</div>
|
|
</DialogForm>
|
|
|
|
<!-- add Product -->
|
|
<DialogForm
|
|
hide-footer
|
|
v-model:modal="dialogProduct"
|
|
:title="$t('productService.product.addTitle')"
|
|
:submit="
|
|
() => {
|
|
submitProduct();
|
|
}
|
|
"
|
|
:close="
|
|
() => {
|
|
dialogProduct = false;
|
|
formProductDocument = [];
|
|
onCreateImageList = { selectedImage: '', list: [] };
|
|
flowStore.rotate();
|
|
}
|
|
"
|
|
>
|
|
<div
|
|
:class="{
|
|
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
|
'q-mx-md q-my-sm': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<ProfileBanner
|
|
prefix="dialog"
|
|
hide-fade
|
|
use-toggle
|
|
hide-active
|
|
:toggle-title="$t('status.title')"
|
|
:img="profileUrl || '/images/product-avatar-add.png'"
|
|
fallback-cover="/images/product-banner.png"
|
|
:bg-color="`hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`"
|
|
@view="
|
|
() => {
|
|
imageDialog = true;
|
|
isImageEdit = false;
|
|
}
|
|
"
|
|
@edit="imageDialog = isImageEdit = true"
|
|
v-model:toggle-status="formProduct.status"
|
|
@update:toggle-status="
|
|
() => {
|
|
formProduct.status =
|
|
formProduct.status === 'CREATED' ? 'INACTIVE' : 'CREATED';
|
|
}
|
|
"
|
|
:tabs-list="
|
|
$q.screen.gt.sm
|
|
? false
|
|
: [
|
|
{
|
|
name: 1,
|
|
label: $t(`form.field.basicInformation`),
|
|
},
|
|
{
|
|
name: 2,
|
|
label: $t('productService.product.priceInformation'),
|
|
},
|
|
{
|
|
name: 3,
|
|
label: $t('general.attachment'),
|
|
},
|
|
]
|
|
"
|
|
v-model:current-tab="productTab"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="full-width full-height scroll"
|
|
:class="{
|
|
'q-pb-lg q-px-lg ': $q.screen.gt.sm,
|
|
'q-pb-sm q-px-md': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<div
|
|
class="col surface-1 rounded bordered scroll row relative-position full-height"
|
|
id="product-form"
|
|
>
|
|
<div
|
|
class="col"
|
|
style="height: 100%; max-height: 100; overflow-y: auto"
|
|
v-if="$q.screen.gt.sm"
|
|
>
|
|
<div class="q-py-md q-pl-md q-pr-sm">
|
|
<q-item
|
|
v-for="v in 3"
|
|
:key="v"
|
|
dense
|
|
clickable
|
|
class="no-padding items-center rounded full-width"
|
|
:class="{ 'q-mt-xs': v > 1 }"
|
|
active-class="product-form-active"
|
|
:active="productTab === v"
|
|
@click="productTab = v"
|
|
>
|
|
<span class="full-width q-py-sm" style="padding-inline: 20px">
|
|
{{
|
|
v === 1
|
|
? $t('form.field.basicInformation')
|
|
: v === 2
|
|
? $t('productService.product.priceInformation')
|
|
: $t('general.information', {
|
|
msg: $t('general.attachment'),
|
|
})
|
|
}}
|
|
</span>
|
|
</q-item>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="col-12 col-md-10"
|
|
id="customer-form-content"
|
|
:class="{
|
|
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
|
'q-pa-sm': !$q.screen.gt.sm,
|
|
}"
|
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
|
>
|
|
<div
|
|
:class="{
|
|
'q-my-md q-mx-lg': $q.screen.gt.sm,
|
|
'q-ma-sm': $q.screen.lt.md,
|
|
}"
|
|
style="position: absolute; z-index: 99999; top: 0; right: 0"
|
|
>
|
|
<div class="surface-1 row rounded">
|
|
<SaveButton id="btn-info-basic-save" icon-only type="submit" />
|
|
</div>
|
|
</div>
|
|
<BasicInfoProduct
|
|
v-if="productTab === 1"
|
|
v-model:detail="formProduct.detail"
|
|
v-model:remark="formProduct.remark"
|
|
v-model:name="formProduct.name"
|
|
v-model:code="formProduct.code"
|
|
v-model:process="formProduct.process"
|
|
v-model:expense-type="formProduct.expenseType"
|
|
v-model:shared="formProduct.shared"
|
|
dense
|
|
separator
|
|
/>
|
|
<PriceDataComponent
|
|
v-if="productTab === 2"
|
|
v-model:price="formProduct.price"
|
|
v-model:agent-price="formProduct.agentPrice"
|
|
v-model:service-charge="formProduct.serviceCharge"
|
|
v-model:vat-included="formProduct.vatIncluded"
|
|
v-model:calc-vat="formProduct.calcVat"
|
|
v-model:agent-price-vat-included="formProduct.agentPriceVatIncluded"
|
|
v-model:agent-price-calc-vat="formProduct.agentPriceCalcVat"
|
|
v-model:service-charge-vat-included="
|
|
formProduct.serviceChargeVatIncluded
|
|
"
|
|
v-model:service-charge-calc-vat="formProduct.serviceChargeCalcVat"
|
|
/>
|
|
<FormDocument
|
|
v-if="productTab === 3"
|
|
v-model:attachment="formProductDocument"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</DialogForm>
|
|
|
|
<!-- edit product -->
|
|
<!-- :edit="!(formDataProduct.status === 'INACTIVE')" -->
|
|
<DialogForm
|
|
v-model:modal="dialogProductEdit"
|
|
noAddress
|
|
:title="$t('productService.product.title')"
|
|
:submit="() => submitProduct()"
|
|
:close="
|
|
() => {
|
|
infoProductEdit = false;
|
|
dialogProductEdit = false;
|
|
formProductDocument = [];
|
|
flowStore.rotate();
|
|
}
|
|
"
|
|
hide-footer
|
|
>
|
|
<div
|
|
:class="{
|
|
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
|
'q-mx-md q-my-sm': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<ProfileBanner
|
|
prefix="dialog"
|
|
hideFade
|
|
:use-toggle="actionDisplay"
|
|
:active="formProduct.status !== 'INACTIVE'"
|
|
:title="formProduct.name"
|
|
:caption="formProduct.code"
|
|
icon="mdi-shopping-outline"
|
|
fallback-img="/images/product-avatar.png"
|
|
color="var(--teal-10)"
|
|
:toggle-title="$t('status.title')"
|
|
:img="
|
|
`${baseUrl}/product/${currentIdProduct}/image/${formProduct.selectedImage}`.concat(
|
|
refreshImageState ? `?ts=${Date.now()}` : '',
|
|
) || '/images/product-avatar.png'
|
|
"
|
|
fallback-cover="/images/product-banner.png"
|
|
:bg-color="`hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`"
|
|
v-model:toggle-status="formProduct.status"
|
|
@view="
|
|
() => {
|
|
imageDialog = true;
|
|
isImageEdit = false;
|
|
}
|
|
"
|
|
@edit="imageDialog = isImageEdit = true"
|
|
@update:toggle-status="
|
|
async () => {
|
|
if (formProduct.status)
|
|
await triggerChangeStatus(
|
|
currentIdProduct,
|
|
formProduct.status,
|
|
'product',
|
|
);
|
|
}
|
|
"
|
|
:tabs-list="
|
|
$q.screen.gt.sm
|
|
? false
|
|
: [
|
|
{
|
|
name: 1,
|
|
label: $t(`form.field.basicInformation`),
|
|
},
|
|
{
|
|
name: 2,
|
|
label: $t('productService.product.priceInformation'),
|
|
},
|
|
{
|
|
name: 3,
|
|
label: $t('general.attachment'),
|
|
},
|
|
]
|
|
"
|
|
v-model:currentTab="productTab"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="full-width full-height scroll"
|
|
:class="{
|
|
'q-pb-lg q-px-lg ': $q.screen.gt.sm,
|
|
'q-pb-sm q-px-md': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<div
|
|
class="col surface-1 rounded bordered scroll row relative-position full-height"
|
|
id="product-form"
|
|
>
|
|
<div
|
|
class="surface-1 rounded row"
|
|
:class="{
|
|
'q-my-md q-mx-lg': $q.screen.gt.sm,
|
|
'q-ma-sm': $q.screen.lt.md,
|
|
}"
|
|
style="position: absolute; z-index: 999; top: 0; right: 0"
|
|
v-if="actionDisplay && !currentNoAction"
|
|
>
|
|
<UndoButton
|
|
v-if="infoProductEdit"
|
|
id="btn-info-basic-undo"
|
|
icon-only
|
|
@click="
|
|
() => {
|
|
formProduct = { ...prevProduct };
|
|
if (prevProduct.document)
|
|
formProductDocument = prevProduct.document;
|
|
infoProductEdit = false;
|
|
}
|
|
"
|
|
type="button"
|
|
/>
|
|
<SaveButton
|
|
v-if="infoProductEdit"
|
|
id="btn-info-basic-save"
|
|
icon-only
|
|
type="submit"
|
|
/>
|
|
<EditButton
|
|
v-if="!infoProductEdit"
|
|
id="btn-info-basic-edit"
|
|
icon-only
|
|
@click="infoProductEdit = true"
|
|
type="button"
|
|
/>
|
|
<DeleteButton
|
|
v-if="!infoProductEdit"
|
|
id="btn-info-basic-delete"
|
|
icon-only
|
|
@click="() => deleteProductConfirm()"
|
|
type="button"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="col"
|
|
style="height: 100%; max-height: 100; overflow-y: auto"
|
|
v-if="$q.screen.gt.sm"
|
|
>
|
|
<div class="q-py-md q-pl-md q-pr-sm">
|
|
<q-item
|
|
v-for="v in 3"
|
|
:key="v"
|
|
dense
|
|
clickable
|
|
class="no-padding items-center rounded full-width"
|
|
:class="{ 'q-mt-xs': v > 1 }"
|
|
active-class="product-form-active"
|
|
:active="productTab === v"
|
|
@click="productTab = v"
|
|
>
|
|
<span class="full-width q-py-sm" style="padding-inline: 20px">
|
|
{{
|
|
v === 1
|
|
? $t('form.field.basicInformation')
|
|
: v === 2
|
|
? $t('productService.product.priceInformation')
|
|
: $t('general.information', {
|
|
msg: $t('general.attachment'),
|
|
})
|
|
}}
|
|
</span>
|
|
</q-item>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="col-12 col-md-10"
|
|
:class="{
|
|
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
|
'q-pa-sm': !$q.screen.gt.sm,
|
|
}"
|
|
id="customer-form-content"
|
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
|
>
|
|
<BasicInfoProduct
|
|
v-if="productTab === 1"
|
|
:readonly="!infoProductEdit"
|
|
v-model:detail="formProduct.detail"
|
|
v-model:remark="formProduct.remark"
|
|
v-model:name="formProduct.name"
|
|
v-model:code="formProduct.code"
|
|
v-model:process="formProduct.process"
|
|
v-model:expense-type="formProduct.expenseType"
|
|
v-model:shared="formProduct.shared"
|
|
disableCode
|
|
dense
|
|
separator
|
|
/>
|
|
<PriceDataComponent
|
|
v-if="productTab === 2"
|
|
:readonly="!infoProductEdit"
|
|
v-model:price="formProduct.price"
|
|
v-model:agent-price="formProduct.agentPrice"
|
|
v-model:service-charge="formProduct.serviceCharge"
|
|
v-model:vat-included="formProduct.vatIncluded"
|
|
v-model:calc-vat="formProduct.calcVat"
|
|
v-model:agent-price-vat-included="formProduct.agentPriceVatIncluded"
|
|
v-model:agent-price-calc-vat="formProduct.agentPriceCalcVat"
|
|
v-model:service-charge-vat-included="
|
|
formProduct.serviceChargeVatIncluded
|
|
"
|
|
v-model:service-charge-calc-vat="formProduct.serviceChargeCalcVat"
|
|
:priceDisplay="priceDisplay"
|
|
/>
|
|
<FormDocument
|
|
v-if="productTab === 3"
|
|
:readonly="!infoProductEdit"
|
|
v-model:attachment="formProductDocument"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</DialogForm>
|
|
|
|
<!-- add service -->
|
|
<DialogForm
|
|
hide-footer
|
|
no-address
|
|
no-app-box
|
|
height="95vh"
|
|
:title="$t('productService.service.addTitle')"
|
|
v-model:modal="dialogService"
|
|
:submit="
|
|
() => {
|
|
submitService();
|
|
}
|
|
"
|
|
:before-close="
|
|
() => {
|
|
if (workItems.length > 0 || !sameFormService()) {
|
|
dialogWarningClose($t, {
|
|
message: t('dialog.message.warningClose'),
|
|
action: () => {
|
|
clearFormService();
|
|
dialogService = false;
|
|
serviceTreeView = false;
|
|
onCreateImageList = { selectedImage: '', list: [] };
|
|
flowStore.rotate();
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
return true;
|
|
} else return false;
|
|
}
|
|
"
|
|
>
|
|
<div
|
|
:class="{
|
|
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
|
'q-mx-md q-my-sm': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<ProfileBanner
|
|
prefix="dialog"
|
|
hide-fade
|
|
use-toggle
|
|
hide-active
|
|
:toggle-title="$t('status.title')"
|
|
:img="profileUrl || '/images/service-avatar-add.png'"
|
|
fallback-cover="/images/service-banner.png"
|
|
:bg-color="`hsla(var(--orange-${$q.dark.isActive ? '6' : '5'}-hsl)/0.15)`"
|
|
@view="
|
|
() => {
|
|
imageDialog = true;
|
|
isImageEdit = false;
|
|
}
|
|
"
|
|
@edit="imageDialog = isImageEdit = true"
|
|
v-model:toggle-status="formService.status"
|
|
@update:toggle-status="
|
|
() => {
|
|
formService.status =
|
|
formService.status === 'CREATED' ? 'INACTIVE' : 'CREATED';
|
|
}
|
|
"
|
|
:tabs-list="
|
|
$q.screen.gt.sm
|
|
? false
|
|
: [
|
|
{
|
|
name: 1,
|
|
label: $t('productService.service.information'),
|
|
},
|
|
{
|
|
name: 2,
|
|
label: $t('productService.service.workInformation'),
|
|
},
|
|
]
|
|
"
|
|
v-model:current-tab="serviceTab"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="col surface-1 rounded bordered scroll row relative-position"
|
|
:class="{
|
|
'q-mb-md q-mx-lg ': $q.screen.gt.sm,
|
|
'q-mb-sm q-mx-md': !$q.screen.gt.sm,
|
|
}"
|
|
id="service-form"
|
|
>
|
|
<div
|
|
class="col column justify-between no-wrap"
|
|
style="height: 100%; max-height: 100; overflow-y: auto"
|
|
v-if="$q.screen.gt.sm"
|
|
>
|
|
<div class="q-py-md q-pl-md q-pr-sm scroll">
|
|
<q-item
|
|
v-for="v in 2"
|
|
:key="v"
|
|
dense
|
|
clickable
|
|
class="no-padding items-center rounded full-width"
|
|
:class="{ 'q-mt-xs': v > 1 }"
|
|
active-class="product-form-active"
|
|
:active="serviceTab === v"
|
|
@click="serviceTab = v"
|
|
>
|
|
<span
|
|
class="full-width row items-center q-py-sm no-wrap"
|
|
style="padding-inline: 20px"
|
|
>
|
|
{{
|
|
v === 1
|
|
? $t('productService.service.information')
|
|
: $t('productService.service.workInformation')
|
|
}}
|
|
<q-btn
|
|
v-if="v === 2"
|
|
id="btn-add-work"
|
|
dense
|
|
flat
|
|
icon="mdi-plus"
|
|
size="sm"
|
|
rounded
|
|
padding="0px 0px"
|
|
class="q-ml-auto"
|
|
style="color: var(--stone-9)"
|
|
@click.stop="
|
|
() => serviceTab === v && refAddServiceWork.addWork()
|
|
"
|
|
/>
|
|
</span>
|
|
</q-item>
|
|
<SideMenu
|
|
:menu="
|
|
workItems.map((w, index) => ({
|
|
name: `${$t('productService.service.work')} ${index + 1} `,
|
|
anchor: `work-${index}`,
|
|
sub: true,
|
|
}))
|
|
"
|
|
/>
|
|
</div>
|
|
<span
|
|
class="row items-center justify-center q-py-md q-pr-sm q-pl-md text-caption no-wrap"
|
|
>
|
|
{{ $t('productService.service.splitPay') }}
|
|
<q-input
|
|
style="width: 54px"
|
|
dense
|
|
outlined
|
|
min="1"
|
|
class="split-pay q-mx-sm"
|
|
input-class="text-caption text-right"
|
|
type="number"
|
|
for="dialog-input-installments"
|
|
v-model="formService.installments"
|
|
/>
|
|
{{ $t('quotation.receiptDialog.installments') }}
|
|
</span>
|
|
</div>
|
|
<div
|
|
class="col-12 col-md-10"
|
|
id="customer-form-content"
|
|
:class="{
|
|
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
|
'q-pa-sm': !$q.screen.gt.sm,
|
|
}"
|
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
|
>
|
|
<div
|
|
class="surface-1 rounded items-center justify-end row"
|
|
:class="{
|
|
'q-my-md q-mx-lg': $q.screen.gt.sm,
|
|
'q-ma-sm': $q.screen.lt.md,
|
|
}"
|
|
style="
|
|
position: absolute;
|
|
z-index: 999;
|
|
top: 0;
|
|
right: 0;
|
|
flex-wrap: wrap-reverse;
|
|
"
|
|
:style="$q.screen.lt.sm && 'width: 80px'"
|
|
v-if="actionDisplay && !currentNoAction"
|
|
>
|
|
<div
|
|
class="bordered rounded col-md row"
|
|
v-if="serviceTab === 2"
|
|
:style="$q.screen.lt.sm && 'flex-basis: 100%; '"
|
|
>
|
|
<q-btn
|
|
class="col"
|
|
icon="mdi-file-tree-outline"
|
|
flat
|
|
square
|
|
:class="{
|
|
'surface-3': serviceTreeView,
|
|
'app-text-muted-2': serviceTreeView,
|
|
'app-text-muted': !serviceTreeView,
|
|
}"
|
|
size="sm"
|
|
padding="6px 10px"
|
|
title="Tree"
|
|
style="
|
|
border-top-left-radius: var(--radius-2);
|
|
border-bottom-left-radius: var(--radius-2);
|
|
"
|
|
@click="serviceTreeView = true"
|
|
/>
|
|
<q-btn
|
|
class="col"
|
|
icon="mdi-view-list-outline"
|
|
flat
|
|
square
|
|
:class="{
|
|
' surface-3': !serviceTreeView,
|
|
'app-text-muted-2': !serviceTreeView,
|
|
'app-text-muted': serviceTreeView,
|
|
}"
|
|
size="sm"
|
|
padding="6px 10px"
|
|
title="List"
|
|
style="
|
|
border-top-right-radius: var(--radius-2);
|
|
border-bottom-right-radius: var(--radius-2);
|
|
"
|
|
@click="serviceTreeView = false"
|
|
/>
|
|
</div>
|
|
<PasteButton
|
|
id="btn-info-basic-paste"
|
|
icon-only
|
|
@click="() => paste()"
|
|
/>
|
|
<SaveButton id="btn-info-basic-save" icon-only type="submit" />
|
|
</div>
|
|
|
|
<BasicInformation
|
|
v-if="serviceTab === 1"
|
|
dense
|
|
service
|
|
v-model:service-code="formService.code"
|
|
v-model:service-description="formService.detail"
|
|
v-model:service-name-th="formService.name"
|
|
/>
|
|
|
|
<FormServiceWork
|
|
v-if="serviceTab === 2"
|
|
ref="refAddServiceWork"
|
|
v-model:work-items="workItems"
|
|
v-model:workflow="currWorkflow"
|
|
:tree-view="serviceTreeView"
|
|
:service="formService"
|
|
:installments="formService.installments"
|
|
dense
|
|
@add-product="
|
|
async (index) => {
|
|
await fetchListOfProductIsAdd(currentIdGroup);
|
|
currentWorkIndex = index;
|
|
selectProduct = JSON.parse(
|
|
JSON.stringify(workItems[currentWorkIndex].product),
|
|
);
|
|
dialogTotalProduct = true;
|
|
modeViewIsAdd = false;
|
|
}
|
|
"
|
|
@manage-work-name="
|
|
() => {
|
|
manageWorkNameDialog = true;
|
|
}
|
|
"
|
|
@work-properties="
|
|
(index) => {
|
|
currentWorkIndex = index;
|
|
tempValueProperties = JSON.parse(
|
|
JSON.stringify(workItems[index].attributes),
|
|
);
|
|
openPropertiesDialog('work');
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<!-- <div
|
|
class="col-2 surface-1 rounded bordered row"
|
|
:class="{
|
|
'q-mb-lg q-mx-lg ': $q.screen.gt.sm,
|
|
'q-mb-sm q-mx-md': !$q.screen.gt.sm,
|
|
}"
|
|
v-if="serviceTab === 1"
|
|
style="overflow: hidden"
|
|
>
|
|
<FormServiceProperties
|
|
v-model:service-attributes="formDataProductService.attributes"
|
|
@service-properties="
|
|
() => {
|
|
tempValueProperties = JSON.parse(
|
|
JSON.stringify(formDataProductService.attributes),
|
|
);
|
|
openPropertiesDialog('service');
|
|
}
|
|
"
|
|
/>
|
|
</div> -->
|
|
</DialogForm>
|
|
|
|
<!-- service properties, work properties -->
|
|
<DialogProperties
|
|
v-if="workItems[currentWorkIndex]"
|
|
select-flow
|
|
:on-edit="dialogServiceEdit"
|
|
v-model:data-step="workItems[currentWorkIndex].attributes.workflowStep"
|
|
v-model:workflow-id="formService.attributes.workflowId"
|
|
v-model="propertiesDialog"
|
|
@show="
|
|
() => {
|
|
tempWorkItems[currentWorkIndex] = JSON.parse(
|
|
JSON.stringify(workItems[currentWorkIndex]),
|
|
);
|
|
}
|
|
"
|
|
@submit="
|
|
(v) => {
|
|
if (v.id === currWorkflow?.id) {
|
|
handleSubmitSameWorkflow();
|
|
return;
|
|
}
|
|
currWorkflow = v;
|
|
handleSubmitWorkflow(v.id);
|
|
}
|
|
"
|
|
></DialogProperties>
|
|
|
|
<!-- manage work name -->
|
|
<DialogForm
|
|
hideFooter
|
|
height="65vh"
|
|
width="65%"
|
|
v-model:modal="manageWorkNameDialog"
|
|
:title="$t('general.manage')"
|
|
:before-close="
|
|
() => {
|
|
const isWorkNameEdit =
|
|
workNameRef && workNameRef.isWorkNameEdit
|
|
? workNameRef.isWorkNameEdit()
|
|
: false;
|
|
if (isWorkNameEdit) {
|
|
triggerConfirmCloseWork();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
"
|
|
>
|
|
<div class="q-pa-lg full-height">
|
|
<WorkNameManagement
|
|
ref="workNameRef"
|
|
v-model:name-list="workNameItems"
|
|
@delete="confirmDeleteWork"
|
|
@edit="editWork"
|
|
@add="createWork"
|
|
/>
|
|
</div>
|
|
</DialogForm>
|
|
|
|
<!-- edit service -->
|
|
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
|
|
<DialogForm
|
|
hide-footer
|
|
no-address
|
|
height="95vh"
|
|
:title="$t('productService.service.title')"
|
|
v-model:modal="dialogServiceEdit"
|
|
:submit="
|
|
() => {
|
|
submitService();
|
|
}
|
|
"
|
|
:before-close="
|
|
() => {
|
|
if (!sameFormService()) {
|
|
dialogWarningClose($t, {
|
|
message: t('dialog.message.warningClose'),
|
|
action: () => {
|
|
clearFormService();
|
|
dialogService = false;
|
|
serviceTreeView = false;
|
|
onCreateImageList = { selectedImage: '', list: [] };
|
|
flowStore.rotate();
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
return true;
|
|
} else return false;
|
|
}
|
|
"
|
|
>
|
|
<div
|
|
:class="{
|
|
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
|
'q-mx-md q-my-sm': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<ProfileBanner
|
|
prefix="dialog"
|
|
hide-fade
|
|
:use-toggle="actionDisplay"
|
|
:title="formService.name"
|
|
:caption="formService.code"
|
|
:active="formService.status !== 'INACTIVE'"
|
|
:toggle-title="$t('status.title')"
|
|
:img="
|
|
`${baseUrl}/service/${currentIdService}/image/${formService.selectedImage}`.concat(
|
|
refreshImageState ? `?ts=${Date.now()}` : '',
|
|
) || '/images/service-avatar.png'
|
|
"
|
|
fallback-img="/images/service-avatar.png"
|
|
fallback-cover="/images/service-banner.png"
|
|
:bg-color="`hsla(var(--orange-${$q.dark.isActive ? '6' : '5'}-hsl)/0.15)`"
|
|
v-model:toggle-status="formService.status"
|
|
@view="
|
|
() => {
|
|
imageDialog = true;
|
|
isImageEdit = false;
|
|
}
|
|
"
|
|
@edit="imageDialog = isImageEdit = true"
|
|
@update:toggle-status="
|
|
async () => {
|
|
if (formService.status)
|
|
await triggerChangeStatus(
|
|
currentIdService,
|
|
formService.status,
|
|
'service',
|
|
);
|
|
}
|
|
"
|
|
:tabs-list="
|
|
$q.screen.gt.sm
|
|
? false
|
|
: [
|
|
{
|
|
name: 1,
|
|
label: $t('productService.service.information'),
|
|
},
|
|
{
|
|
name: 2,
|
|
label: $t('productService.service.workInformation'),
|
|
},
|
|
]
|
|
"
|
|
v-model:currentTab="serviceTab"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="col surface-1 rounded bordered scroll row relative-position"
|
|
id="group-form"
|
|
:class="{
|
|
'q-mb-lg q-mx-lg ': $q.screen.gt.sm,
|
|
'q-mb-sm q-mx-md': !$q.screen.gt.sm,
|
|
}"
|
|
>
|
|
<!-- row: $q.screen.gt.sm, -->
|
|
<div
|
|
class="surface-1 rounded items-center justify-end row"
|
|
:class="{
|
|
'q-my-md q-mx-lg': $q.screen.gt.sm,
|
|
'q-ma-sm': $q.screen.lt.md,
|
|
}"
|
|
style="
|
|
position: absolute;
|
|
z-index: 999;
|
|
top: 0;
|
|
right: 0;
|
|
flex-wrap: wrap-reverse;
|
|
"
|
|
v-if="actionDisplay && !currentNoAction"
|
|
>
|
|
<div
|
|
class="bordered rounded col-md row"
|
|
v-if="serviceTab === 2 && !infoServiceEdit"
|
|
:style="$q.screen.lt.sm && 'flex-basis: 100%; width: 1px'"
|
|
>
|
|
<q-btn
|
|
class="col"
|
|
icon="mdi-file-tree-outline"
|
|
flat
|
|
square
|
|
:class="{
|
|
'surface-3': serviceTreeView,
|
|
'app-text-muted-2': serviceTreeView,
|
|
'app-text-muted': !serviceTreeView,
|
|
}"
|
|
size="sm"
|
|
padding="6px 10px"
|
|
title="Tree"
|
|
style="
|
|
border-top-left-radius: var(--radius-2);
|
|
border-bottom-left-radius: var(--radius-2);
|
|
"
|
|
@click="serviceTreeView = true"
|
|
/>
|
|
<q-btn
|
|
class="col"
|
|
icon="mdi-view-list-outline"
|
|
flat
|
|
square
|
|
:class="{
|
|
' surface-3': !serviceTreeView,
|
|
'app-text-muted-2': !serviceTreeView,
|
|
'app-text-muted': serviceTreeView,
|
|
}"
|
|
size="sm"
|
|
padding="6px 10px"
|
|
title="List"
|
|
style="
|
|
border-top-right-radius: var(--radius-2);
|
|
border-bottom-right-radius: var(--radius-2);
|
|
"
|
|
@click="serviceTreeView = false"
|
|
/>
|
|
</div>
|
|
|
|
<UndoButton
|
|
v-if="infoServiceEdit"
|
|
id="btn-info-basic-undo"
|
|
icon-only
|
|
@click="
|
|
() => {
|
|
infoServiceEdit = false;
|
|
cloneServiceData();
|
|
statusToggle = prevService.status === 'INACTIVE' ? false : true;
|
|
flowStore.rotate();
|
|
}
|
|
"
|
|
type="button"
|
|
/>
|
|
<SaveButton
|
|
v-if="infoServiceEdit"
|
|
id="btn-info-basic-save"
|
|
icon-only
|
|
type="submit"
|
|
/>
|
|
<EditButton
|
|
v-if="!infoServiceEdit"
|
|
id="btn-info-basic-edit"
|
|
icon-only
|
|
@click="
|
|
() => {
|
|
infoServiceEdit = true;
|
|
serviceTreeView = false;
|
|
}
|
|
"
|
|
type="button"
|
|
/>
|
|
<DeleteButton
|
|
v-if="!infoServiceEdit"
|
|
id="btn-info-basic-delete"
|
|
icon-only
|
|
@click="() => deleteServiceConfirm()"
|
|
type="button"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="col column justify-between no-wrap"
|
|
style="height: 100%; max-height: 100; overflow-y: auto"
|
|
v-if="$q.screen.gt.sm"
|
|
>
|
|
<div class="q-py-md q-pl-md q-pr-sm scroll">
|
|
<q-item
|
|
v-for="v in 2"
|
|
:key="v"
|
|
dense
|
|
clickable
|
|
class="no-padding items-center rounded full-width"
|
|
:class="{ 'q-mt-xs': v > 1 }"
|
|
active-class="product-form-active"
|
|
:active="serviceTab === v"
|
|
@click="serviceTab = v"
|
|
>
|
|
<span
|
|
class="full-width row items-center q-py-sm no-wrap"
|
|
style="padding-inline: 20px"
|
|
>
|
|
{{
|
|
v === 1
|
|
? $t('productService.service.information')
|
|
: $t('productService.service.workInformation')
|
|
}}
|
|
<q-btn
|
|
v-if="v === 2 && infoServiceEdit"
|
|
id="btn-add-work"
|
|
dense
|
|
flat
|
|
icon="mdi-plus"
|
|
size="sm"
|
|
rounded
|
|
padding="0px 0px"
|
|
class="q-ml-auto"
|
|
style="color: var(--stone-9)"
|
|
@click.stop="
|
|
() => serviceTab === v && refEditServiceWork.addWork()
|
|
"
|
|
/>
|
|
</span>
|
|
</q-item>
|
|
<SideMenu
|
|
:menu="
|
|
workItems.map((w, index) => ({
|
|
name: `${$t('productService.service.work')} ${index + 1} `,
|
|
anchor: `work-${index}`,
|
|
sub: true,
|
|
}))
|
|
"
|
|
:active="{
|
|
background: 'hsla(var(--blue-6-hsl) / .2)',
|
|
foreground: 'var(--blue-6)',
|
|
}"
|
|
/>
|
|
</div>
|
|
<span
|
|
class="row items-center justify-center q-py-md q-pr-sm q-pl-md text-caption no-wrap"
|
|
>
|
|
{{ $t('productService.service.splitPay') }}
|
|
<q-input
|
|
style="width: 54px"
|
|
:readonly="!infoServiceEdit"
|
|
dense
|
|
outlined
|
|
min="0"
|
|
class="split-pay q-mx-sm"
|
|
input-class="text-caption text-right"
|
|
type="number"
|
|
v-model="formService.installments"
|
|
/>
|
|
{{ $t('quotation.receiptDialog.installments') }}
|
|
</span>
|
|
</div>
|
|
<div
|
|
class="col-12 col-md-10"
|
|
id="customer-form-content"
|
|
:class="{
|
|
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
|
'q-pa-sm': !$q.screen.gt.sm,
|
|
}"
|
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
|
v-if="dialogServiceEdit"
|
|
>
|
|
<BasicInformation
|
|
v-if="serviceTab === 1"
|
|
:readonly="!infoServiceEdit"
|
|
dense
|
|
service
|
|
disableCode
|
|
v-model:service-code="formService.code"
|
|
v-model:service-description="formService.detail"
|
|
v-model:service-name-th="formService.name"
|
|
/>
|
|
|
|
<FormServiceWork
|
|
v-if="serviceTab === 2"
|
|
ref="refEditServiceWork"
|
|
v-model:work-items="workItems"
|
|
v-model:workflow="currWorkflow"
|
|
:service="formService"
|
|
:tree-view="serviceTreeView"
|
|
:readonly="!infoServiceEdit"
|
|
:installments="formService.installments"
|
|
:price-display="priceDisplay"
|
|
dense
|
|
@add-product="
|
|
async (index) => {
|
|
await fetchListOfProductIsAdd(currentIdGroup);
|
|
currentWorkIndex = index;
|
|
selectProduct = JSON.parse(
|
|
JSON.stringify(workItems[currentWorkIndex].product),
|
|
);
|
|
dialogTotalProduct = true;
|
|
modeViewIsAdd = false;
|
|
}
|
|
"
|
|
@manage-work-name="
|
|
() => {
|
|
manageWorkNameDialog = true;
|
|
}
|
|
"
|
|
@work-properties="
|
|
(index) => {
|
|
currentWorkIndex = index;
|
|
tempValueProperties = JSON.parse(
|
|
JSON.stringify(workItems[index].attributes),
|
|
);
|
|
openPropertiesDialog('work');
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<!-- <div
|
|
class="col-2 surface-1 rounded bordered row"
|
|
:class="{
|
|
'q-mb-lg q-mx-lg ': $q.screen.gt.sm,
|
|
'q-mb-sm q-mx-md': !$q.screen.gt.sm,
|
|
}"
|
|
v-if="serviceTab === 1"
|
|
style="overflow: hidden"
|
|
>
|
|
<FormServiceProperties
|
|
:readonly="!infoServiceEdit"
|
|
v-model:service-attributes="formDataProductService.attributes"
|
|
@service-properties="
|
|
() => {
|
|
tempValueProperties = JSON.parse(
|
|
JSON.stringify(formDataProductService.attributes),
|
|
);
|
|
openPropertiesDialog('service');
|
|
}
|
|
"
|
|
/>
|
|
</div> -->
|
|
</DialogForm>
|
|
|
|
<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="
|
|
async () => {
|
|
if (!currentNode) return;
|
|
|
|
if (currentNode.type === 'group') {
|
|
editByTree = 'group';
|
|
currentStatusProduct = currentNode.status === 'INACTIVE';
|
|
clearFormGroup();
|
|
await assignFormDataGroup(currentNode);
|
|
isEdit = false;
|
|
currentIdGroup = 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('general.viewDetail') }}</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item
|
|
clickable
|
|
v-ripple
|
|
v-close-popup
|
|
@click.stop="
|
|
async () => {
|
|
if (!currentNode) return;
|
|
editByTree = currentNode.type as 'type' | 'group';
|
|
if (currentNode.type === 'group') {
|
|
clearFormGroup();
|
|
await assignFormDataGroup(currentNode);
|
|
isEdit = true;
|
|
currentIdGroup = 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('general.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') {
|
|
deleteGroupById(currentNode.id);
|
|
}
|
|
if (currentNode.type === 'group') {
|
|
deleteGroupById(currentNode.id);
|
|
}
|
|
}
|
|
"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-icon name="mdi-trash-can-outline" class="app-text-negative" />
|
|
</q-item-section>
|
|
<q-item-section>{{ $t('general.delete') }}</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item clickable v-ripple>
|
|
<q-item-section avatar>
|
|
<ToggleButton
|
|
two-way
|
|
:id="`view-detail-btn-${currentNode.name}-status`"
|
|
:model-value="currentNode.status !== 'INACTIVE'"
|
|
@click="
|
|
async () => {
|
|
if (!currentNode) return;
|
|
if (currentNode.type === 'group') {
|
|
triggerChangeStatus(
|
|
currentNode.id,
|
|
currentNode.status,
|
|
currentNode.type,
|
|
);
|
|
currentNode.status === 'ACTIVE'
|
|
? (currentNode.status = 'INACTIVE')
|
|
: (currentNode.status = 'ACTIVE');
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
{{
|
|
currentNode.status !== 'INACTIVE'
|
|
? $t('general.open')
|
|
: $t('general.close')
|
|
}}
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</div>
|
|
</q-dialog>
|
|
|
|
<ImageUploadDialog
|
|
ref="refImageUpload"
|
|
v-model:dialog-state="imageDialog"
|
|
v-model:file="profileFileImg"
|
|
v-model:image-url="profileUrl as string"
|
|
v-model:data-list="imageList"
|
|
v-model:on-create-data-list="onCreateImageList"
|
|
:hidden-footer="!isImageEdit"
|
|
:on-create="dialogProduct || dialogService"
|
|
:change-disabled="!actionDisplay"
|
|
@add-image="
|
|
async (v) => {
|
|
if (!v) return;
|
|
if (!currentIdProduct && !currentIdService) return;
|
|
const res = await productServiceStore.addImageList(
|
|
v,
|
|
dialogProductEdit ? currentIdProduct : currentIdService,
|
|
Date.now().toString(),
|
|
dialogProductEdit ? 'product' : 'service',
|
|
);
|
|
await fetchImageList(
|
|
dialogProductEdit ? currentIdProduct : currentIdService,
|
|
res,
|
|
dialogProductEdit ? 'product' : 'service',
|
|
);
|
|
}
|
|
"
|
|
@remove-image="
|
|
async (v) => {
|
|
if (!v) return;
|
|
if (!currentIdProduct && !currentIdService) return;
|
|
|
|
const name = v.split('/').pop() || '';
|
|
const type = dialogProductEdit ? 'product' : 'service';
|
|
await productServiceStore.deleteImageByName(
|
|
dialogProductEdit ? currentIdProduct : currentIdService,
|
|
name,
|
|
type,
|
|
);
|
|
await fetchImageList(
|
|
dialogProductEdit ? currentIdProduct : currentIdService,
|
|
dialogProductEdit
|
|
? formProduct.selectedImage || ''
|
|
: formService.selectedImage || '',
|
|
type,
|
|
);
|
|
}
|
|
"
|
|
@submit="
|
|
async (v) => {
|
|
if (dialogProduct || dialogService) {
|
|
profileUrl = v;
|
|
imageDialog = false;
|
|
} else {
|
|
const type = dialogProductEdit ? 'product' : 'service';
|
|
const id = dialogProductEdit ? currentIdProduct : currentIdService;
|
|
refreshImageState = true;
|
|
type === 'product'
|
|
? (formProduct.selectedImage = v)
|
|
: (formService.selectedImage = v);
|
|
imageList ? (imageList.selectedImage = v) : '';
|
|
profileUrl = `${baseUrl}/${type}/${id}/image/${v}`;
|
|
if (type === 'product') {
|
|
const { selectedImage, ...data } = prevProduct;
|
|
formProduct = {
|
|
selectedImage: formProduct.selectedImage,
|
|
...data,
|
|
};
|
|
await submitProduct(true);
|
|
} else {
|
|
cloneServiceData();
|
|
await submitService(true);
|
|
}
|
|
imageDialog = false;
|
|
refreshImageState = false;
|
|
infoProductEdit = false;
|
|
infoServiceEdit = false;
|
|
}
|
|
}
|
|
"
|
|
>
|
|
<template #title>
|
|
<span
|
|
v-if="!dialogProduct || !dialogService"
|
|
class="justify-center flex text-bold"
|
|
>
|
|
{{ $t('general.image') }}
|
|
{{ dialogProductEdit ? formProduct.name : formService.name }}
|
|
</span>
|
|
</template>
|
|
<template #error>
|
|
<div class="full-height full-width" style="background: var(--surface-1)">
|
|
<div
|
|
class="full-height full-width flex justify-center items-center"
|
|
:style="`background: ${
|
|
dialogProduct || dialogProductEdit
|
|
? `hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`
|
|
: `hsla(var(--orange-${$q.dark.isActive ? '6' : '5'}-hsl)/0.15)`
|
|
}`"
|
|
>
|
|
<q-img
|
|
:src="`/images/${dialogProduct || dialogProductEdit ? 'product' : 'service'}-avatar.png`"
|
|
fit="contain"
|
|
style="height: 100%"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</ImageUploadDialog>
|
|
</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(--stone-5-hsl);
|
|
--_branch-badge-bg: var(--stone-5-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);
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
.tags-color-green {
|
|
--_color-tag: var(--teal-10-hsl);
|
|
}
|
|
|
|
.dark .tags-color-green {
|
|
--_color-tag: var(--teal-8-hsl);
|
|
}
|
|
|
|
.tags-color-orange {
|
|
--_color-tag: var(--orange-5-hsl);
|
|
}
|
|
|
|
.dark .tags-color-orange {
|
|
--_color-tag: var(--orange-6-hsl);
|
|
}
|
|
|
|
.tags-color-purple {
|
|
--_color-tag: var(--violet-11-hsl);
|
|
}
|
|
|
|
.dark .tags-color-purple {
|
|
--_color-tag: var(--violet-10-hsl);
|
|
}
|
|
|
|
.tags-color-pink {
|
|
--_color-tag: var(--pink-6-hsl);
|
|
}
|
|
|
|
.table__icon {
|
|
background-color: hsla(var(--_color) / 0.15);
|
|
color: hsla(var(--_color) / 1);
|
|
|
|
border-radius: 50%;
|
|
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);
|
|
}
|
|
|
|
&:deep(.q-img) {
|
|
transform: rotate(-45deg);
|
|
|
|
&:deep(.q-icon) {
|
|
transform: rotate(0deg);
|
|
}
|
|
}
|
|
}
|
|
|
|
.tags {
|
|
display: inline-block;
|
|
color: hsla(var(--_color-tag) / 1);
|
|
background: hsla(var(--_color-tag) / 0.075);
|
|
border-radius: var(--radius-2);
|
|
padding-inline: var(--size-2);
|
|
|
|
&.disable {
|
|
filter: grayscale(100%);
|
|
opacity: 80%;
|
|
}
|
|
}
|
|
|
|
* :deep(.q-icon.mdi-play) {
|
|
display: none;
|
|
}
|
|
|
|
.product-form-active {
|
|
background-color: hsla(var(--info-bg) / 0.2);
|
|
color: hsl(var(--info-bg));
|
|
font-weight: 600;
|
|
}
|
|
|
|
:deep(.split-pay .q-field__control) {
|
|
height: 23px;
|
|
}
|
|
</style>
|