jws-frontend/src/pages/01_branch-management/MainPage.vue
2024-09-27 13:50:17 +07:00

2755 lines
85 KiB
Vue

<script setup lang="ts">
import { UploadFile } from 'components/upload-file';
import { storeToRefs } from 'pinia';
import { ref, onMounted, computed, watch } from 'vue';
import { Icon } from '@iconify/vue';
import { BranchContact } from 'stores/branch-contact/types';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import type { QTableProps } from 'quasar';
import { resetScrollBar } from 'src/stores/utils';
import useBranchStore from 'stores/branch';
import useFlowStore from 'stores/flow';
import {
BranchWithChildren,
BranchCreate,
Branch,
BankBook,
} from 'stores/branch/types';
import QrCodeUploadDialog from 'src/components/QrCodeUploadDialog.vue';
import ItemCard from 'src/components/ItemCard.vue';
import useUtilsStore, { dialog, baseUrl } from 'stores/utils';
import EmptyAddButton from 'components/AddButton.vue';
import TooltipComponent from 'components/TooltipComponent.vue';
import StatCard from 'components/StatCardComponent.vue';
import BranchCard from 'components/01_branch-management/BranchCard.vue';
import { AddressForm } from 'components/form';
import DialogForm from 'components/DialogForm.vue';
import FormBranchInformation from 'components/01_branch-management/FormBranchInformation.vue';
import FormLocation from 'components/01_branch-management/FormLocation.vue';
import FormQr from 'components/01_branch-management/FormQr.vue';
import FormBranchContact from 'components/01_branch-management/FormBranchContact.vue';
import DrawerInfo from 'components/DrawerInfo.vue';
import InfoForm from 'components/02_personnel-management/InfoForm.vue';
import TreeComponent from 'components/TreeComponent.vue';
import ProfileBanner from 'components/ProfileBanner.vue';
import SideMenu from 'components/SideMenu.vue';
import ImageUploadDialog from 'components/ImageUploadDialog.vue';
import FormBank from 'components/01_branch-management/FormBank.vue';
import ToggleButton from 'src/components/button/ToggleButton.vue';
import FormBranchAdmin from 'src/components/01_branch-management/FormBranchAdmin.vue';
import KebabAction from 'src/components/shared/KebabAction.vue';
import RemarkComponent from 'src/components/RemarkComponent.vue';
import { User } from 'src/stores/user/types';
import {
EditButton,
DeleteButton,
SaveButton,
UndoButton,
} from 'components/button';
const $q = useQuasar();
const { t } = useI18n();
const utilsStore = useUtilsStore();
const modelCreateTypeBranch = ref<boolean>(false);
const typeBranchItem = [
{
icon: 'material-symbols:home-work-outline',
text: 'branch.card.branchLabel',
iconColor: '--purple-8',
color: 'var(--purple-6-hsl)',
},
{
icon: 'mdi-home-group',
text: 'branch.card.branchVirtual',
iconColor: '--blue-8',
color: 'var(--blue-6-hsl)',
},
];
const holdDialog = ref(false);
const isSubCreate = ref(false);
const columns = [
{
name: 'orderNumber',
align: 'center',
label: 'general.orderNumber',
field: 'branchNo',
},
{
name: 'office',
align: 'left',
label: 'branch.office',
field: 'name',
sortable: true,
},
{
name: 'taxNo',
align: 'left',
label: 'general.taxNo',
field: 'taxNo',
},
{
name: 'branchLabelTel',
align: 'left',
label: 'branch.card.branchLabelTel',
field: 'telephoneNo',
},
{
name: 'contactName',
align: 'left',
label: 'general.contactName',
field: 'contactName',
},
{
name: 'branchLabelAddress',
align: 'left',
label: 'general.address',
field: 'address',
sortable: true,
},
] satisfies QTableProps['columns'];
const currentTab = ref<string>('main');
const modal = ref<boolean>(false);
const hideStat = ref(false);
const currentId = ref<string>('');
const expandedTree = ref<string[]>([]);
const formMenuIcon = ref<{ icon: string; color: string; bgColor: string }[]>([
{
icon: 'mdi-phone-outline',
color: 'hsl(var(--info-bg))',
bgColor: 'var(--surface-1)',
},
{
icon: 'mdi-map-marker-radius-outline',
color: 'hsl(var(--info-bg))',
bgColor: 'var(--surface-1)',
},
{
icon: 'mdi-map-legend',
color: 'hsl(var(--info-bg))',
bgColor: 'var(--surface-1)',
},
]);
const formBankBook = ref<BankBook[]>([
{
bankName: '',
accountNumber: '',
bankBranch: '',
accountName: '',
accountType: '',
currentlyUse: true,
bankUrl: '',
},
]);
const attachmentList = ref<File[]>([]);
const currentAttachmentList = ref<
{
name?: string;
group?: string;
url?: string;
file?: File;
}[]
>([]);
const statusQrCodeFile = ref<File | undefined>(undefined);
const statusQrCodeUrl = ref<string>('');
const statusDeletsQrCode = ref<boolean>(false);
const deletsStatusQrCodeImag = ref(false);
const deletsStatusQrCodeBankImag = ref<number[]>([]);
const currentIndexQrCodeBank = ref<number>(-1);
const refQrCodeUpload = ref();
const refImageUpload = ref();
const isImageEdit = ref(false);
const isQrCodeEdit = ref(false);
const profileFile = ref<File | null>(null);
const qrCodeFile = ref<File | undefined>(undefined);
const imageUrl = ref<string>('');
const currentNode = ref<BranchWithChildren>();
const imageDialog = ref(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 qrCodeDialog = ref(false);
const qrCodeimageUrl = ref<string>('');
const formLastSubBranch = ref<number>(0);
const branchStore = useBranchStore();
const flowStore = useFlowStore();
const { locale } = useI18n();
const { data: branchData } = storeToRefs(branchStore);
const currentSubBranch = ref<Branch[] | undefined>(undefined);
const treeData = computed(() => {
const map: Record<string, BranchWithChildren> = {};
branchData.value?.result.forEach((v) => {
const name = locale.value === 'eng' ? v.nameEN : v.name;
map[v.id] = { ...v, name, branch: v.branch || [] };
});
return Object.values(map);
});
async function calculateStats(headOfficeId?: string) {
const _stats = await branchStore.stats({ headOfficeId });
if (!_stats) return;
const baseStats: {
icon: string;
count: number;
label: string;
color: 'pink' | 'purple' | 'blue';
}[] = [
{
icon: 'mdi-office-building-outline',
count: _stats.hq,
label: 'branch.card.branchHQLabel',
color: 'pink',
},
{
icon: 'mdi-home-group',
count: _stats.br,
label: 'branch.card.branchLabel',
color: 'purple',
},
{
icon: 'mdi-home-group',
count: _stats.virtual,
label: 'branch.card.branchVirtual',
color: 'blue',
},
];
stats.value = baseStats.filter((v) => {
if (headOfficeId !== undefined && v.label.includes('HQ')) {
return false;
}
return true;
});
}
onMounted(async () => {
utilsStore.currentTitle.title = 'menu.branch';
utilsStore.currentTitle.path = [
{
text: 'branch.page.captionManage',
i18n: true,
handler: () => {
fieldSelectedBranch.value.value = 'branchHQLabel';
currentHq.value = {
id: '',
code: '',
};
},
},
// track of the currently selected HQ branch, so when we reset it to an empty
// object, we are effectively unselecting any HQ branch.
];
await fetchList({ pageSize: 99999, tree: true, withHead: true });
await calculateStats();
modeView.value = $q.screen.lt.md ? true : false;
flowStore.rotate();
});
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
const beforeBranch = ref<{ id: string; code: string }>({
id: '',
code: '',
});
const modeView = ref<boolean>(false);
const inputSearch = ref<string>('');
const fieldDisplay = computed(() => {
return columns
.filter((v) => {
return !(
modeView.value === true &&
(v.name.includes('office') || v.name.includes('orderNumber'))
);
})
.map((v) => v.name);
});
const fieldSelected = ref<string[]>([...fieldDisplay.value]);
watch(modeView, () => {
fieldSelected.value = [...fieldDisplay.value];
});
const fieldSelectedBranch = ref<{
label: string;
value: string;
}>({
label: t('branch.form.title.branchHQLabel'),
value: 'branchHQLabel',
});
const stats = ref<
{
icon: string;
count: number;
label: string;
color: 'pink' | 'purple' | 'blue';
}[]
>([]);
const splitterModel = ref(25);
const defaultFormData = {
headOfficeId: null,
remark: '',
code: '',
taxNo: '',
nameEN: '',
name: '',
addressEN: '',
address: '',
zipCode: '',
email: '',
contactName: '',
contact: '',
telephoneNo: '',
longitude: '',
latitude: '',
subDistrictId: '',
districtId: '',
provinceId: '',
moo: '',
mooEN: '',
soi: '',
soiEN: '',
street: '',
streetEN: '',
lineId: '',
webUrl: '',
virtual: false,
selectedImage: '',
permitExpireDate: new Date(),
permitIssueDate: new Date(),
permitNo: '',
};
const defaultFormBankBook = [
{
bankName: '',
accountNumber: '',
bankBranch: '',
accountName: '',
accountType: '',
currentlyUse: true,
bankUrl: '',
},
];
const formDialogRef = ref();
const formType = ref<'create' | 'edit' | 'delete' | 'view'>('create');
const formTypeBranch = ref<'headOffice' | 'subBranch' | 'branchVirtual'>(
'headOffice',
);
const currentHq = ref<{ id: string; code: string }>({
id: '',
code: '',
});
const currentEdit = ref<{ id: string; code: string }>({
id: '',
code: '',
});
const formData = ref<
Omit<BranchCreate & { codeHeadOffice?: string }, 'qrCodeImage'>
>(structuredClone(defaultFormData));
const prevFormData = ref<
Omit<BranchCreate & { codeHeadOffice?: string }, 'qrCodeImage'>
>(structuredClone(defaultFormData));
const prevFormBankBook = ref<BankBook[]>(defaultFormBankBook);
const modalDrawer = ref<boolean>(false);
function openDrawer() {
prevFormData.value = JSON.parse(JSON.stringify(formData.value));
prevFormBankBook.value = JSON.parse(JSON.stringify(formBankBook.value));
modalDrawer.value = true;
}
function openDialog() {
modal.value = true;
}
watch(treeData, () => {
if (currentSubBranch.value !== undefined) {
selectedSubBranche(currentHq.value.id);
}
});
async function selectedSubBranche(id: string) {
const res = await branchStore.fetchById(id, {
includeSubBranch: true,
includeContact: true,
});
if (res) {
currentSubBranch.value = res.branch;
} else {
currentSubBranch.value = undefined;
}
}
async function fetchBranchById(id: string) {
const res = await branchStore.fetchById(id, { includeContact: true });
if (res) {
qrCodeimageUrl.value = `${baseUrl}/branch/${res.id}/line-image?ts=${Date.now()}`;
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
formBankBook.value = res.bank;
formBankBook.value = formBankBook.value.map((v) => {
return {
...v,
bankUrl: `${baseUrl}/branch/${res.id}/bank-qr/${v.id}?ts=${Date.now()}`,
};
});
const resAttachment = await branchStore.fetchListAttachment(res.id);
if (resAttachment) {
currentAttachmentList.value = await Promise.all(
resAttachment.map(async (v) => {
return {
name: v,
url: (await branchStore.fetchByIdAttachment(res.id, v)) || '',
};
}),
);
}
formData.value = {
remark: res.remark || '',
code: res.code,
headOfficeId: res.headOfficeId,
taxNo: res.taxNo,
nameEN: res.nameEN,
name: res.name,
addressEN: res.addressEN,
address: res.address,
zipCode: res.zipCode,
email: res.email,
contactName: res.contactName,
contact: res.contact.length > 0 ? res.contact[0].telephoneNo : '',
telephoneNo: res.telephoneNo,
longitude: res.longitude,
latitude: res.latitude,
subDistrictId: res.subDistrictId,
districtId: res.districtId,
provinceId: res.provinceId,
lineId: res.lineId,
status: res.status,
webUrl: res.webUrl,
virtual: res.virtual,
selectedImage: res.selectedImage,
moo: res.moo,
mooEN: res.mooEN,
soi: res.soi,
soiEN: res.soiEN,
street: res.street,
streetEN: res.streetEN,
permitExpireDate: new Date(res.permitExpireDate),
permitIssueDate: new Date(res.permitIssueDate),
permitNo: res.permitNo,
};
return res;
}
}
function clearData() {
formData.value = structuredClone(defaultFormData);
imageUrl.value = '';
qrCodeimageUrl.value = '';
currentEdit.value = {
id: '',
code: '',
};
qrCodeFile.value = undefined;
currentAttachmentList.value = [];
currentTab.value = 'main';
formBankBook.value = [
{
bankName: '',
accountNumber: '',
bankBranch: '',
accountName: '',
accountType: '',
currentlyUse: true,
},
];
}
async function undo() {
isQrCodeEdit.value = false;
isImageEdit.value = false;
formType.value = 'view';
const tempSelectedImage = formData.value.selectedImage;
formData.value = JSON.parse(JSON.stringify(prevFormData.value));
formBankBook.value = JSON.parse(JSON.stringify(prevFormBankBook.value));
formData.value.selectedImage = tempSelectedImage;
}
watch(expandedTree, async () => {
calculateStats(expandedTree.value[0]);
});
watch(modal, () => {
if (!modal.value) {
clearData();
}
});
watch(modalDrawer, () => {
if (!modalDrawer.value) {
clearData();
}
});
function triggerCreate(
type: 'headOffice' | 'subBranch' | 'branchVirtual',
id?: string,
code?: string,
) {
formTypeBranch.value = type;
if ((type === 'subBranch' || type === 'branchVirtual') && id && code) {
isSubCreate.value = true;
formData.value.headOfficeId = id;
formData.value.code = code;
formData.value.codeHeadOffice = code;
formLastSubBranch.value = findLastSub(code);
}
formType.value = 'create';
currentId.value = '';
openDialog();
}
function findLastSub(code: string) {
const abbrev = code.slice(0, -5);
const filteredArray = branchData.value.result.filter((v) =>
v.code.includes(abbrev),
);
const maxCodeNum = filteredArray.reduce((max, v) => {
const codeNum = parseInt(v.code.slice(-5), 10);
return codeNum > max ? codeNum : max;
}, 0);
return maxCodeNum;
}
function drawerEdit() {
isQrCodeEdit.value = true;
isImageEdit.value = true;
formType.value = 'edit';
}
const currentBranchAdmin = ref<User | null>(null);
async function triggerEdit(
openFormType: string,
id: string,
typeBranch: 'headOffice' | 'subBranch',
code?: string,
) {
const currentRecord = await fetchBranchById(id);
await fetchImageList(id, formData.value.selectedImage || '');
formTypeBranch.value = typeBranch;
if (openFormType === 'form') {
formType.value = 'edit';
openDialog();
}
if (openFormType === 'drawer') {
formType.value = 'view';
openDrawer();
}
if (typeBranch === 'headOffice') {
formData.value.codeHeadOffice = code;
}
currentId.value = id;
if (!currentRecord) return;
currentEdit.value = {
id: currentRecord.id,
code: currentRecord.code,
};
if (typeBranch === 'subBranch') {
const currentRecordHead = branchData.value.result.find(
(x) => x.id === currentRecord.headOfficeId,
);
formData.value.codeHeadOffice = currentRecordHead?.code;
if (currentRecordHead) {
currentHq.value.id = currentRecordHead.id;
currentHq.value.code = currentRecordHead.code;
} else {
currentHq.value = currentEdit.value;
}
}
const branchAdmin = await branchStore.getAdmin(id);
if (!branchAdmin) {
currentBranchAdmin.value = null;
return;
}
currentBranchAdmin.value = branchAdmin;
}
async function triggerDelete(id: string) {
if (id) {
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 branchStore.deleteById(id);
if (res) {
await fetchList({ tree: true, pageSize: 99999, withHead: true });
modalDrawer.value = false;
modal.value = false;
await calculateStats();
const branchLength = treeData.value.find(
(node) => node.id === expandedTree.value[0],
)?.branch.length;
if (branchLength === 0) {
expandedTree.value = [];
fieldSelectedBranch.value.value = 'branchHQLabel';
currentHq.value = {
id: '',
code: '',
};
}
flowStore.rotate();
}
},
cancel: () => {},
});
}
}
function triggerEditQrCodeLine(opts?: { save?: boolean }) {
if (opts?.save === undefined) {
qrCodeDialog.value = true;
statusDeletsQrCode.value = false;
deletsStatusQrCodeImag.value = false;
statusQrCodeUrl.value = qrCodeimageUrl.value;
} else {
qrCodeimageUrl.value = statusQrCodeUrl.value;
qrCodeFile.value = statusQrCodeFile.value;
deletsStatusQrCodeImag.value = statusDeletsQrCode.value;
}
}
function triggerEditQrCodeBank(opts?: { save?: boolean }) {
if (opts?.save === undefined) {
qrCodeDialog.value = true;
statusDeletsQrCode.value = false;
statusQrCodeUrl.value =
formBankBook.value[currentIndexQrCodeBank.value].bankUrl || '';
statusDeletsQrCode.value = false;
} else {
formBankBook.value[currentIndexQrCodeBank.value].bankUrl =
statusQrCodeUrl.value;
formBankBook.value[currentIndexQrCodeBank.value].bankQr =
statusQrCodeFile.value;
if (statusDeletsQrCode.value) {
deletsStatusQrCodeBankImag.value.push(currentIndexQrCodeBank.value);
}
currentIndexQrCodeBank.value = -1;
}
}
async function fetchList(opts: {
page?: number;
pageSize?: number;
zipCode?: string;
query?: string;
tree?: boolean;
withHead?: boolean;
filter?: 'head' | 'sub';
}) {
await branchStore.fetchList(opts);
}
watch(inputSearch, () => {
fetchList({ tree: true, query: inputSearch.value, withHead: true });
currentSubBranch.value = undefined;
});
async function triggerChangeStatus(
id: string,
status: string,
): Promise<Branch> {
return await new Promise((resolve) => {
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 () => {
const res = await branchStore.editById(id, {
status: status !== 'INACTIVE' ? 'INACTIVE' : 'ACTIVE',
});
if (res) resolve(res);
await fetchList({ tree: true, pageSize: 99999, withHead: true });
},
cancel: () => {},
});
});
}
async function onSubmit(submitSelectedItem?: boolean) {
delete formData.value['codeHeadOffice'];
if (formType.value === 'edit' || submitSelectedItem) {
delete formData.value['code'];
const res = await branchStore.editById(
currentEdit.value.id,
{
...formData.value,
status: undefined,
},
qrCodeFile.value,
formBankBook.value,
{
deleteQrCodeImage: deletsStatusQrCodeImag.value,
indexDeleteQrCodeBank: deletsStatusQrCodeBankImag.value,
},
);
if (!res) return;
formData.value.codeHeadOffice = formData.value.code = res.code;
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
await fetchList({ tree: true, pageSize: 99999, withHead: true });
if (!imageDialog.value) modalDrawer.value = submitSelectedItem || false;
}
if (formType.value === 'create') {
const createBranch = async () => {
const res = await branchStore.create(
{
...formData.value,
qrCodeImage: qrCodeFile.value,
},
formBankBook.value,
onCreateImageList.value,
currentAttachmentList.value,
);
if (res) {
currentId.value = res.id;
formData.value.codeHeadOffice = res.code;
formData.value.code = res.code;
prevFormData.value = JSON.parse(JSON.stringify(formData.value));
currentEdit.value = {
id: res.id,
code: res.code,
};
formType.value = 'view';
}
await fetchList({ tree: true, pageSize: 99999, withHead: true });
};
if (
formTypeBranch.value === 'subBranch' ||
formTypeBranch.value === 'branchVirtual'
) {
const currentRecord = branchData.value.result.find(
(x) => x.id === formData.value.headOfficeId,
);
formData.value.headOfficeId = currentRecord?.id;
formData.value.code = formData.value.code?.slice(0, -5);
delete formData.value['codeHeadOffice'];
await createBranch();
fieldSelectedBranch.value.value = '';
inputSearch.value = '';
currentHq.value = {
id: currentRecord?.id || '',
code: currentRecord?.code || '',
};
beforeBranch.value = {
id: '',
code: '',
};
expandedTree.value = [];
expandedTree.value.push(currentRecord?.id || '');
} else {
dialog({
color: 'info',
icon: 'mdi-message-processing-outline',
message: t('form.info.cantChange', {
field: t('branch.form.abbrev'),
name: formData.value.code,
}),
actionText: t('dialog.action.ok'),
persistent: true,
title: t('form.warning.title'),
cancel: () => {},
action: async () => {
await createBranch();
},
});
}
}
const _stats = await branchStore.stats();
if (_stats) {
stats.value = [
{
icon: 'mdi-home',
count: _stats.hq,
label: 'branch.card.branchHQLabel',
color: 'pink',
},
{
icon: 'mdi-domain',
count: _stats.br,
label: 'branch.card.branchLabel',
color: 'purple',
},
];
}
flowStore.rotate();
}
function changeTitle(
formType: 'edit' | 'create' | 'delete' | 'view',
typeBranch: 'headOffice' | 'subBranch' | 'branchVirtual',
) {
const _type =
{
headOffice: 'branch.card.branchHQLabel',
subBranch: 'branch.card.branchLabel',
branchVirtual: 'branch.card.branchVirtual',
}[typeBranch] || '';
return formType === 'create'
? t('form.title.create', { name: t(_type) })
: formType === 'view'
? t(_type)
: t('general.edit');
}
function handleHold(node: BranchWithChildren) {
if ($q.screen.gt.xs) return;
holdDialog.value = true;
currentNode.value = node;
// return function () {
// };
}
async function fetchImageList(id: string, selectedName: string) {
const res = await branchStore.fetchImageListById(id);
imageList.value = {
selectedImage: selectedName,
list: res.map((n: string) => `branch/${id}/image/${n}`),
};
return res;
}
watch(
() => profileFile.value,
() => {
if (profileFile.value !== null) isImageEdit.value = true;
},
);
watch(
() => qrCodeFile.value,
() => {
if (qrCodeFile.value !== null) isQrCodeEdit.value = true;
},
);
watch(locale, () => {
fieldSelectedBranch.value = {
label: t(`${fieldSelectedBranch.value.label}`),
value: fieldSelectedBranch.value.value,
};
});
watch(
() => $q.screen.lt.md,
(v) => {
if (v) modeView.value = true;
},
);
watch(currentHq, () => {
const tmp: typeof utilsStore.currentTitle.path = [
{
text: 'branch.page.captionManage',
i18n: true,
handler: () => {
expandedTree.value = expandedTree.value.filter(
(i) => currentHq.value.id !== i,
);
fieldSelectedBranch.value.value = 'branchHQLabel';
currentHq.value = {
id: '',
code: '',
};
},
},
];
if (currentHq.value.id !== '') {
tmp.push({
text: currentHq.value.code,
});
}
selectedSubBranche(currentHq.value.id);
utilsStore.currentTitle.path = tmp;
});
</script>
<template>
<div class="column full-height no-wrap">
<div class="text-body-2 q-mb-xs flex items-center">
{{ $t('general.dataSum') }}
<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">
<StatCard
v-if="!hideStat"
class="q-pb-md"
label-i18n
:branch="stats"
:dark="$q.dark.isActive"
/>
</transition>
<div class="col surface-2 rounded" :no-padding="!!branchData.total">
<template v-if="!branchData.total">
<div class="full-width full-height column">
<div class="self-end q-ma-md">
<TooltipComponent
class="self-end"
title="general.noData"
caption="general.clickToCreate"
imgSrc="personnel-table-"
/>
</div>
<div class="col flex items-center justify-center">
<EmptyAddButton
label="general.add"
:i18nArgs="{ text: $t('branch.form.title.branchHQLabel') }"
@trigger="
() => {
triggerCreate('headOffice');
}
"
/>
</div>
</div>
</template>
<template v-else>
<div class="full-height column" style="flex-grow: 1; flex-wrap: nowrap">
<q-splitter
v-model="splitterModel"
:limits="[0, 100]"
class="col"
before-class="overflow-hidden"
after-class="overflow-hidden"
>
<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' : '100.8px'}`"
>
<div class="col ellipsis-2-lines">
{{ $t('branch.allBranch') }}
</div>
<q-btn
round
flat
size="md"
dense
id="hq-add-btn"
class="q-mr-sm"
@click="
() => {
triggerCreate('headOffice');
}
"
>
<Icon
icon="pixelarticons:plus"
class="cursor-pointer"
style="color: var(--foreground); scale: 1.5"
/>
</q-btn>
</div>
<div class="col full-width scroll">
<div class="q-pa-md">
<TreeComponent
v-model:nodes="treeData"
v-model:expanded-tree="expandedTree"
node-key="id"
label-key="name"
children-key="branch"
type-tree="branch"
@handle-hold="(v) => handleHold(v)"
@select="
(v) => {
if (
v.isHeadOffice &&
v._count.branch !== 0 &&
currentHq.id === v.id
) {
expandedTree = expandedTree.filter(
(i) => v.id !== i,
);
fieldSelectedBranch.value = 'branchHQLabel';
currentHq = {
id: '',
code: '',
};
return;
}
if (
v.isHeadOffice &&
v._count.branch !== 0 &&
currentHq.id !== v.id
) {
expandedTree = [];
expandedTree.push(v.id);
fieldSelectedBranch.value = '';
inputSearch = '';
currentHq = {
id: v.id,
code: v.code,
};
beforeBranch = {
id: '',
code: '',
};
}
}
"
@create="
(v) => {
currentEdit.id = v.id;
currentEdit.code = v.code;
modelCreateTypeBranch = true;
}
// triggerCreate(v.virtual, 'subBranch', v.id, v.code)
"
@view="
(v) => {
if (v.isHeadOffice) {
triggerEdit('drawer', v.id, 'headOffice', v.code);
} else {
triggerEdit('drawer', v.id, 'subBranch');
}
}
"
@edit="
(v) => {
if (v.isHeadOffice) {
triggerEdit('drawer', v.id, 'headOffice', v.code);
} else {
triggerEdit('drawer', v.id, 'subBranch');
}
formType = 'edit';
}
"
@delete="
(v) => {
triggerDelete(v.id);
}
"
@change-status="
async (v) => {
const res = await triggerChangeStatus(v.id, v.status);
if (res) v.status = res.status;
}
"
/>
</div>
</div>
</div>
</template>
<template v-slot:after>
<div class="column full-height no-wrap">
<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="q-mr-md col-12 col-md-4"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="200"
>
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
<div
class="row col-12 col-md-6"
:class="{ 'q-pt-xs': $q.screen.lt.md }"
>
<q-select
v-model="statusFilter"
outlined
dense
option-value="value"
:hide-dropdown-icon="$q.screen.lt.sm"
option-label="label"
class="col"
map-options
:for="'field-select-status'"
emit-value
:options="[
{ label: $t('general.all'), value: 'all' },
{ label: $t('status.ACTIVE'), value: 'statusACTIVE' },
{
label: $t('status.INACTIVE'),
value: 'statusINACTIVE',
},
]"
></q-select>
<q-select
id="select-field"
for="select-field"
:options="
fieldDisplay.map((v) => ({
label: $t(`branch.card.${v}`),
value: v,
}))
"
:display-value="$t('general.displayField')"
:hide-dropdown-icon="$q.screen.lt.sm"
class="col q-mx-sm"
v-model="fieldSelected"
option-label="label"
option-value="value"
map-options
emit-value
outlined
multiple
dense
/>
<q-btn-toggle
id="btn-mode"
v-model="modeView"
dense
class="no-shadow bordered rounded surface-1"
: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 class="surface-2 q-pa-md scroll full-width">
<q-table
flat
bordered
class="full-width"
:rows-per-page-options="[0]"
:rows="
(
currentSubBranch ||
(inputSearch !== ''
? treeData.flatMap((v) => [v, ...v.branch])
: treeData)
).filter((v) => {
if (
statusFilter === 'statusACTIVE' &&
v.status === 'INACTIVE'
) {
return false;
}
if (
statusFilter === 'statusINACTIVE' &&
v.status !== 'INACTIVE'
) {
return false;
}
return true;
})
"
:columns="columns"
:grid="modeView"
card-container-class="row q-col-gutter-md"
row-key="name"
hide-pagination
:visible-columns="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"
:class="{
'q-pl-sm': col.label === 'orderNumber',
}"
>
{{ $t(col.label) }}
</q-th>
<q-th auto-width />
</q-tr>
</template>
<template v-slot:body="props">
<q-tr
:class="{
'app-text-muted': props.row.status === 'INACTIVE',
'cursor-pointer': props.row._count?.branch !== 0,
}"
:style="
props.rowIndex % 2 !== 0
? $q.dark.isActive
? 'background: hsl(var(--gray-11-hsl)/0.2)'
: `background: #f9fafc`
: ''
"
:props="props"
@click="
() => {
if (
props.row.isHeadOffice &&
props.row._count?.branch !== 0
) {
fieldSelectedBranch.value = '';
inputSearch = '';
currentHq = {
id: props.row.id,
code: props.row.code,
};
beforeBranch = {
id: '',
code: '',
};
expandedTree = [];
expandedTree.push(props.row.id);
}
}
"
>
<q-td
class="text-center"
v-if="fieldSelected.includes('orderNumber')"
>
{{ props.rowIndex + 1 }}
</q-td>
<q-td v-if="fieldSelected.includes('office')">
<div class="row items-center no-wrap">
<div
:class="{
'q-mr-sm': true,
'status-active':
props.row.status !== 'INACTIVE',
'status-inactive':
props.row.status === 'INACTIVE',
'branch-card__hq': props.row.isHeadOffice,
'branch-card__br':
!props.row.isHeadOffice && !props.row.virtual,
'branch-card__br-virtual':
!props.row.isHeadOffice && props.row.virtual,
}"
style="display: flex"
>
<div
:style="`
border-radius: 50%;
border-style: solid;
border-width: 2px;
border-color: hsl(var(${props.row.isHeadOffice ? '--pink-6-hsl' : props.row.virtual ? '--blue-6-hsl' : '--violet-11-hsl'}));
`"
class="q-pa-xs"
>
<q-avatar size="md">
<q-img
class="text-center"
:ratio="1"
:src="
baseUrl +
`/branch/${props.row.id}/image/${props.row.selectedImage}`.concat(
refreshImageState
? `?ts=${Date.now()}`
: '',
)
"
>
<template #error>
<div
class="branch-card__icon no-padding full-width full-height items-center justify-center"
>
<q-icon
size="sm"
name="mdi-office-building-outline"
/>
</div>
</template>
</q-img>
<q-badge
class="absolute-bottom-right no-padding"
style="
border-radius: 50%;
min-width: 8px;
min-height: 8px;
"
:style="{
background: `var(--${props.row.status === 'INACTIVE' ? 'stone-5' : 'green-6'})`,
}"
></q-badge>
</q-avatar>
</div>
</div>
<div class="col">
<div class="col">
{{
$i18n.locale === 'eng'
? props.row.nameEN
: props.row.name
}}
</div>
<div class="col app-text-muted">
{{ props.row.code }}
</div>
</div>
</div>
</q-td>
<q-td v-if="fieldSelected.includes('taxNo')">
{{ props.row.taxNo }}
</q-td>
<q-td v-if="fieldSelected.includes('branchLabelTel')">
{{
props.row.contact !== undefined
? props.row.telephoneNo || '-'
: '-'
}}
</q-td>
<q-td v-if="fieldSelected.includes('contactName')">
{{ props.row.contactName || '-' }}
</q-td>
<q-td
v-if="fieldSelected.includes('branchLabelAddress')"
>
{{
locale === 'eng'
? `${props.row.addressEN}, ${props.row.mooEN && `${$t('form.moo')} ${props.row.mooEN},`} ${props.row.soiEN && `${$t('form.soi')} ${props.row.soiEN},`} ${props.row.moo && `${props.row.streetEN} Rd,`} ${props.row.subDistrict.nameEN}, ${props.row.district.nameEN}, ${props.row.province.nameEN} ${props.row.subDistrict.zipCode}` ||
'-'
: `${props.row.address} ${props.row.moo && `${$t('form.moo')} ${props.row.moo},`} ${props.row.soi && `${$t('form.soi')} ${props.row.soi},`} ${props.row.street && `${$t('form.road')} ${props.row.street},`} ${props.row.subDistrict.name}, ${props.row.district.name}, ${props.row.province.name} ${props.row.subDistrict.zipCode}` ||
'-'
}}
</q-td>
<q-td>
<KebabAction
:status="props.row.status"
:idName="props.row.name"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
'drawer',
props.row.id,
'headOffice',
props.row.code,
);
} else {
triggerEdit(
'drawer',
props.row.id,
'subBranch',
);
}
"
@edit="
async () => {
if (props.row.isHeadOffice) {
await triggerEdit(
'drawer',
props.row.id,
'headOffice',
props.row.code,
);
} else {
await triggerEdit(
'drawer',
props.row.id,
'subBranch',
);
}
drawerEdit();
formType = 'edit';
}
"
@delete="triggerDelete(props.row.id)"
@changeStatus="
async () => {
const res = await triggerChangeStatus(
props.row.id,
props.row.status,
);
if (res) props.row.status = res.status;
}
"
/>
</q-td>
</q-tr>
</template>
<template v-slot:item="props">
<div class="col-12 col-md-6">
<BranchCard
class="surface-1"
:virtual-branch="props.row.virtual"
:id="`branch-card-${props.row.name}`"
:class="{
'cursor-pointer': props.row._count?.branch !== 0,
}"
@click="
() => {
if (
props.row.isHeadOffice &&
props.row._count?.branch !== 0
) {
fieldSelectedBranch.value = '';
inputSearch = '';
currentHq = {
id: props.row.id,
code: props.row.code,
};
beforeBranch = {
id: '',
code: '',
};
expandedTree = [];
expandedTree.push(props.row.id);
}
}
"
:metadata="props.row"
:color="
props.row.isHeadOffice
? 'hq'
: props.row.virtual
? 'br-virtual'
: 'br'
"
:key="props.row.id"
:data="{
branchLabelCode: props.row.code,
branchLabelName:
$i18n.locale === 'eng'
? props.row.nameEN
: props.row.name,
taxNo: props.row.taxNo,
branchLabelTel:
props.row.contact &&
props.row.contact
.map((c: BranchContact) => c.telephoneNo || '-')
.join(','),
contactName: props.row.contactName || '-',
branchLabelAddress:
$i18n.locale === 'eng'
? `${props.row.addressEN}, ${props.row.mooEN && `${$t('form.moo')} ${props.row.mooEN},`} ${props.row.soiEN && `${$t('form.soi')} ${props.row.soiEN},`} ${props.row.moo && `${props.row.streetEN} Rd,`} ${props.row.subDistrict.nameEN}, ${props.row.district.nameEN}, ${props.row.province.nameEN} ${props.row.subDistrict.zipCode}` ||
'-'
: `${props.row.address} ${props.row.moo && `${$t('form.moo')} ${props.row.moo},`} ${props.row.soi && `${$t('form.soi')} ${props.row.soi},`} ${props.row.street && `${$t('form.road')} ${props.row.street},`} ${props.row.subDistrict.name}, ${props.row.district.name}, ${props.row.province.name} ${props.row.subDistrict.zipCode}` ||
'-',
branchImgUrl: `/branch/${props.row.id}/branch`,
}"
:field-selected="fieldSelected"
:badge-field="['branchLabelStatus']"
:inactive="props.row.status === 'INACTIVE'"
@view-detail="
(v) => {
triggerEdit(
'drawer',
v.id,
v.isHeadOffice ? 'headOffice' : 'subBranch',
v.code,
);
}
"
>
<template v-slot:action>
<KebabAction
:status="props.row.status"
:idName="props.row.name"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
'drawer',
props.row.id,
'headOffice',
props.row.code,
);
} else {
triggerEdit(
'drawer',
props.row.id,
'subBranch',
);
}
"
@edit="
async () => {
if (props.row.isHeadOffice) {
await triggerEdit(
'drawer',
props.row.id,
'headOffice',
props.row.code,
);
} else {
await triggerEdit(
'drawer',
props.row.id,
'subBranch',
);
}
drawerEdit();
formType = 'edit';
}
"
@delete="triggerDelete(props.row.id)"
@change-status="
async () => {
const res = await triggerChangeStatus(
props.row.id,
props.row.status,
);
if (res) props.row.status = res.status;
}
"
/>
</template>
</BranchCard>
</div>
</template>
</q-table>
</div>
</div>
</template>
</q-splitter>
</div>
</template>
</div>
</div>
<DialogForm
hide-footer
ref="formDialogRef"
v-model:modal="modal"
:title="changeTitle(formType, formTypeBranch) + ' ' + currentEdit.code"
:submit="
() => {
onSubmit();
}
"
:close="
() => {
formLastSubBranch = 0;
modal = false;
profileFile = null;
isImageEdit = false;
isSubCreate = false;
imageList = { selectedImage: '', list: [] };
onCreateImageList = { selectedImage: '', list: [] };
}
"
>
<div class="q-mx-lg q-mt-lg">
<!-- title="บรทจอบส เวคเกอร เซอร จำก"
caption="Jobs Worker Service Co., Ltd." -->
<ProfileBanner
active
useToggle
:title="formData.name"
:toggleTitle="$t('status.title')"
:caption="
formTypeBranch === 'headOffice'
? `${formData.code}`
: `${formData.code?.slice(0, -5)}${(formLastSubBranch + 1).toString().padStart(5, '0')}`
"
v-model:toggle-status="formData.status"
v-model:cover-url="imageUrl"
:hideFade="imageUrl === '' || imageUrl === null"
:img="imageUrl || null"
icon="mdi-office-building-outline"
:color="`hsla(var(${
formTypeBranch === 'headOffice'
? '--pink-6'
: formData.virtual
? '--blue-6'
: $q.dark.isActive
? '--violet-10'
: '--violet-11'
}-hsl)/1)`"
:bg-color="`hsla(var(${
imageUrl
? '--gray-0'
: formTypeBranch === 'headOffice'
? '--pink-6'
: formData.virtual
? '--blue-6'
: $q.dark.isActive
? '--violet-10'
: '--violet-11'
}-hsl)/${imageUrl ? '0' : '0.15'})`"
:menu="formMenuIcon"
v-model:current-tab="currentTab"
@view="
() => {
imageDialog = true;
isImageEdit = false;
}
"
@edit="imageDialog = isImageEdit = true"
@update:toggle-status="
() => {
formData.status =
formData.status === 'CREATED' ? 'INACTIVE' : 'CREATED';
}
"
/>
</div>
<div class="col q-pa-lg">
<div
style="overflow-y: auto"
class="row full-width full-height surface-1 rounded relative-position"
>
<div
v-if="currentTab === 'main'"
class="q-py-md q-px-lg"
style="position: absolute; z-index: 99999; top: 0; right: 0"
>
<div
v-if="formData.status !== 'INACTIVE'"
class="surface-1 row rounded"
>
<UndoButton
v-if="formType === 'edit' && !!currentId"
icon-only
@click="undo()"
type="button"
/>
<SaveButton
v-if="formType === 'edit' || formType === 'create'"
id="btn-info-basic-save"
icon-only
type="submit"
/>
<EditButton
v-if="formType !== 'edit' && !!currentId"
id="btn-info-basic-edit"
icon-only
@click="drawerEdit()"
type="button"
/>
<DeleteButton
v-if="formType !== 'edit' && !!currentId"
id="btn-info-basic-delete"
icon-only
@click="
() => {
triggerDelete(currentEdit.id);
}
"
type="button"
/>
</div>
</div>
<div
class="col full-height rounded scroll row q-py-md q-pl-md q-pr-sm"
v-if="$q.screen.gt.sm"
>
<SideMenu
:menu="[
{
name: $t('form.field.basicInformation'),
anchor: 'form-information',
},
{
name: $t('branch.form.group.contact'),
anchor: 'form-contact',
},
{
name: $t('form.address'),
anchor: 'form-address',
},
{
name: $t('branch.form.group.location'),
anchor: 'form-location',
},
{
name: 'QR Code',
anchor: 'form-qr',
},
{
name: $t('branch.form.group.bankAccount'),
anchor: 'form-bank',
},
{
name: $t('branch.form.group.admin'),
anchor: 'form-branch-admin-view',
},
{
name: $t('customerBranch.tab.attachment'),
anchor: 'form-attachment',
},
{
name: $t('customerBranch.tab.remark'),
anchor: 'form-remark',
},
]"
background="transparent"
:active="{
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#branch-form"
/>
</div>
<div
class="col-12 col-md-10 q-py-md q-pr-md full-height q-gutter-y-xl"
:class="$q.screen.xs ? 'q-pl-md' : 'q-pl-sm'"
id="branch-form"
style="overflow-y: auto"
>
<FormBranchInformation
id="form-information"
v-model:branchCount="formLastSubBranch"
v-model:abbreviation="formData.code"
v-model:code="formData.codeHeadOffice"
v-model:code-sub-branch="currentEdit.code"
v-model:taxNo="formData.taxNo"
v-model:name="formData.name"
v-model:nameEN="formData.nameEN"
v-model:type-branch="formTypeBranch"
v-model:permit-no="formData.permitNo"
v-model:permit-issue-date="formData.permitIssueDate"
v-model:permit-expire-date="formData.permitExpireDate"
:separator="true"
:dense="true"
:outlined="true"
:readonly="formType === 'view'"
title="form.field.basicInformation"
:view="isSubCreate || !!currentId"
onCreate
/>
<FormBranchContact
id="form-contact"
v-model:type-branch="formTypeBranch"
v-model:telephone-no="formData.telephoneNo"
v-model:contact="formData.contact"
v-model:email="formData.email"
v-model:contact-name="formData.contactName"
v-model:line-id="formData.lineId"
v-model:web-url="formData.webUrl"
:separator="true"
:readonly="formType === 'view'"
title="branch.form.group.contact"
:dense="true"
:outlined="true"
/>
<AddressForm
id="form-address"
dense
outlined
separator
prefix-id="default"
:title="'form.address'"
v-model:address="formData.address"
v-model:addressEN="formData.addressEN"
v-model:province-id="formData.provinceId"
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
v-model:moo="formData.moo"
v-model:mooEN="formData.mooEN"
v-model:soi="formData.soi"
v-model:soiEN="formData.soiEN"
v-model:street="formData.street"
v-model:streetEN="formData.streetEN"
:readonly="formType === 'view'"
/>
<FormLocation
id="form-location"
:readonly="formType === 'view'"
:separator="true"
v-model:latitude="formData.latitude"
v-model:longitude="formData.longitude"
outlined
dense
title="branch.form.group.location"
/>
<FormQr
id="form-qr"
title="QR Code"
:separator="true"
:qr="qrCodeimageUrl"
:readonly="formType === 'view'"
@view-qr="
() => {
triggerEditQrCodeLine();
}
"
@edit-qr="() => refQrCodeUpload && refQrCodeUpload.browse()"
/>
<FormBank
id="form-bank"
:readonly="formType === 'view'"
title="branch.form.group.bankAccount"
dense
v-model:bank-book-list="formBankBook"
@view-qr="
(i) => {
currentIndexQrCodeBank = i;
triggerEditQrCodeBank();
}
"
@edit-qr="
(i) => {
currentIndexQrCodeBank = i;
refQrCodeUpload && refQrCodeUpload.browse();
}
"
/>
<FormBranchAdmin
id="form-branch-admin-view"
:admin="currentBranchAdmin"
class="q-mb-xl"
/>
<UploadFile
id="form-attachment"
class="q-mb-xl"
:auto-save="currentId !== ''"
branch
v-model="currentAttachmentList"
@save="
async (_group, file) => {
if (file) {
attachmentList.push(file);
attachmentList.forEach(async (v) => {
await branchStore.putAttachment(currentId, v);
});
}
}
"
@deleteFile="
async (filename) => {
const res = await branchStore.deleteByIdAttachment(
currentId,
filename,
);
if (res) {
currentAttachmentList = currentAttachmentList.filter((v) => {
if (v.name === filename) {
return false;
}
return true;
});
}
}
"
/>
<RemarkComponent
id="form-remark"
:readonly="formType === 'view'"
v-model:remark="formData.remark"
/>
</div>
</div>
</div>
</DialogForm>
<DrawerInfo
ref="formDialogRef"
v-model:drawer-open="modalDrawer"
:submit="() => onSubmit()"
:category="changeTitle(formType, formTypeBranch)"
:title="$i18n.locale === 'eng' ? formData.nameEN : formData.name"
:close="
() => {
modalDrawer = false;
isImageEdit = false;
imageList = { selectedImage: '', list: [] };
onCreateImageList = { selectedImage: '', list: [] };
flowStore.rotate();
resetScrollBar('branch-info');
}
"
:statusBranch="formData.status"
hide-action
>
<InfoForm>
<div class="q-mx-lg q-mt-lg">
<ProfileBanner
:active="formData.status !== 'INACTIVE'"
useToggle
v-model:cover-url="imageUrl"
:toggleTitle="$t('status.title')"
:hideFade="imageUrl === '' || imageUrl === null"
:img="
`${baseUrl}/branch/${currentId}/image/${formData.selectedImage}`.concat(
refreshImageState ? `?ts=${Date.now()}` : '',
) || null
"
:title="formData.name"
:caption="formData.code"
icon="mdi-office-building-outline"
v-model:current-tab="currentTab"
:color="`hsla(var(${
formTypeBranch === 'headOffice'
? '--pink-6'
: formData.virtual
? '--blue-6'
: $q.dark.isActive
? '--violet-10'
: '--violet-11'
}-hsl)/1)`"
:bg-color="`hsla(var(${
imageUrl
? '--gray-0'
: formTypeBranch === 'headOffice'
? '--pink-6'
: formData.virtual
? '--blue-6'
: $q.dark.isActive
? '--violet-10'
: '--violet-11'
}-hsl)/${imageUrl ? '0' : '0.1'})`"
v-model:toggle-status="formData.status"
@view="
() => {
imageDialog = true;
isImageEdit = false;
}
"
@edit="imageDialog = isImageEdit = true"
@update:toggle-status="
async (v) => {
const res = await triggerChangeStatus(currentId, v);
if (res) {
undo();
formData.status = res.status;
formType = 'view';
}
await fetchList({ tree: true, pageSize: 99999, withHead: true });
}
"
:menu="formMenuIcon"
/>
</div>
<div class="col q-pa-lg">
<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"
>
<div
v-if="formData.status !== 'INACTIVE'"
class="surface-1 row rounded"
>
<UndoButton
v-if="formType === 'edit'"
icon-only
@click="undo()"
type="button"
/>
<SaveButton
v-if="formType === 'edit'"
id="btn-info-basic-save"
icon-only
type="submit"
/>
<EditButton
v-if="formType !== 'edit'"
id="btn-info-basic-edit"
icon-only
@click="drawerEdit()"
type="button"
/>
<DeleteButton
v-if="formType !== 'edit'"
id="btn-info-basic-delete"
icon-only
@click="triggerDelete(currentEdit.id)"
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-information',
},
{
name: $t('branch.form.group.contact'),
anchor: 'info-contact',
},
{
name: $t('form.address'),
anchor: 'info-address',
},
{
name: $t('branch.form.group.location'),
anchor: 'info-location',
},
{
name: 'QR Code',
anchor: 'info-qr',
},
{
name: $t('branch.form.group.bankAccount'),
anchor: 'info-bank',
},
{
name: $t('branch.form.group.admin'),
anchor: 'info-branch-admin-view',
},
{
name: $t('customerBranch.tab.attachment'),
anchor: 'info-attachment',
},
{
name: $t('customerBranch.tab.remark'),
anchor: 'info-remark',
},
]"
background="transparent"
:active="{
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#branch-info"
/>
</div>
<div
class="col-12 col-md-10 q-py-md q-pr-md full-height"
:class="$q.screen.xs ? 'q-pl-md' : 'q-pl-sm'"
id="branch-info"
style="overflow-y: auto"
>
<FormBranchInformation
id="info-information"
v-model:virtual="formData.virtual"
v-model:abbreviation="formData.code"
v-model:code="formData.codeHeadOffice"
v-model:code-sub-branch="currentEdit.code"
v-model:taxNo="formData.taxNo"
v-model:name="formData.name"
v-model:nameEN="formData.nameEN"
v-model:type-branch="formTypeBranch"
v-model:permit-no="formData.permitNo"
v-model:permit-issue-date="formData.permitIssueDate"
v-model:permit-expire-date="formData.permitExpireDate"
:separator="true"
:dense="true"
:outlined="true"
:readonly="formType === 'view'"
view
title="form.field.basicInformation"
class="q-mb-xl"
/>
<FormBranchContact
id="info-contact"
title="branch.form.group.contact"
v-model:telephone-no="formData.telephoneNo"
v-model:contact="formData.contact"
v-model:email="formData.email"
v-model:contact-name="formData.contactName"
v-model:line-id="formData.lineId"
v-model:web-url="formData.webUrl"
:readonly="formType === 'view'"
:view="formType === 'view'"
:separator="true"
:dense="true"
:outlined="true"
class="q-mb-xl"
/>
<AddressForm
id="info-address"
dense
outlined
separator
prefix-id="default"
:title="'form.address'"
:readonly="formType === 'view'"
v-model:address="formData.address"
v-model:addressEN="formData.addressEN"
v-model:province-id="formData.provinceId"
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
v-model:moo="formData.moo"
v-model:mooEN="formData.mooEN"
v-model:soi="formData.soi"
v-model:soiEN="formData.soiEN"
v-model:street="formData.street"
v-model:streetEN="formData.streetEN"
v-model:zip-code="formData.zipCode"
class="q-mb-xl"
/>
<FormLocation
id="info-location"
title="branch.form.group.location"
v-model:latitude="formData.latitude"
v-model:longitude="formData.longitude"
:readonly="formType === 'view'"
:separator="true"
outlined
dense
class="q-mb-xl"
/>
<FormQr
id="info-qr"
:readonly="formType === 'view'"
title="QR Code"
:separator="true"
:qr="qrCodeimageUrl"
@view-qr="
() => {
triggerEditQrCodeLine();
}
"
@edit-qr="() => refQrCodeUpload && refQrCodeUpload.browse()"
class="q-mb-xl"
/>
<FormBank
id="info-bank"
:readonly="formType === 'view'"
title="branch.form.group.bankAccount"
dense
v-model:bank-book-list="formBankBook"
class="q-mb-xl"
@view-qr="
(i) => {
currentIndexQrCodeBank = i;
triggerEditQrCodeBank();
}
"
@edit-qr="
(i) => {
currentIndexQrCodeBank = i;
refQrCodeUpload.browse();
}
"
/>
<FormBranchAdmin
id="info-branch-admin-view"
:admin="currentBranchAdmin"
class="q-mb-xl"
/>
<UploadFile
auto-save
class="q-mb-xl"
id="info-attachment"
branch
:file="currentAttachmentList"
:tree-file="
Object.values(
currentAttachmentList.reduce<
Record<string, { label: string; file: { label: string }[] }>
>((a, b) => {
if (b.name && !a[b.name]) {
a[b.name] = {
label: b.name,
file: [],
};
}
return a;
}, {}) || {},
)
"
@save="
async (_group, file) => {
if (file) {
attachmentList.push(file);
attachmentList.forEach(async (v) => {
await branchStore.putAttachment(currentId, v);
});
}
}
"
@deleteFile="
async (filename) => {
const res = await branchStore.deleteByIdAttachment(
currentId,
filename,
);
if (res) {
currentAttachmentList = currentAttachmentList.filter(
(v) => {
if (v.name === filename) {
return false;
}
return true;
},
);
}
}
"
/>
<RemarkComponent
id="info-remark"
class="q-mb-xl"
:readonly="formType === 'view'"
v-model:remark="formData.remark"
/>
</div>
</div>
</div>
</InfoForm>
</DrawerInfo>
<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="
triggerCreate('subBranch', currentNode.id, currentNode.code)
"
>
<q-item-section avatar>
<q-icon name="mdi-file-plus-outline" class="app-text-muted-2" />
</q-item-section>
<q-item-section>
{{
$t('form.title.create', { name: $t('branch.card.branchLabel') })
}}
</q-item-section>
</q-item>
<q-item
clickable
v-ripple
v-close-popup
@click.stop="
if (currentNode.isHeadOffice) {
triggerEdit(
'drawer',
currentNode.id,
'headOffice',
currentNode.code,
);
} else {
triggerEdit('drawer', currentNode.id, 'subBranch');
}
"
>
<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="
async () => {
if (currentNode && currentNode.isHeadOffice) {
await triggerEdit(
'drawer',
currentNode.id,
'headOffice',
currentNode.code,
);
} else {
currentNode &&
triggerEdit('drawer', currentNode.id, 'subBranch');
}
formType = 'edit';
}
"
>
<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="triggerDelete(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`"
@click="
async () => {
if (!currentNode) return;
const res = await triggerChangeStatus(
currentNode.id,
currentNode.status,
);
if (res) currentNode.status = res.status;
}
"
:model-value="
currentNode.status === 'CREATED' ||
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>
<QrCodeUploadDialog
ref="refQrCodeUpload"
v-model:dialogState="qrCodeDialog"
v-model:file="statusQrCodeFile as File"
v-model:image-url="statusQrCodeUrl"
@save="
(_file) => {
qrCodeDialog = false;
if (currentIndexQrCodeBank === -1) {
triggerEditQrCodeLine({ save: true });
}
if (currentIndexQrCodeBank !== -1) {
triggerEditQrCodeBank({ save: true });
}
}
"
@clear="statusDeletsQrCode = true"
clearButton
>
<template #error>
<div
class="full-width full-height flex items-center justify-center"
style="color: gray"
>
<q-icon size="15rem" name="mdi-qrcode" />
</div>
</template>
</QrCodeUploadDialog>
<ImageUploadDialog
ref="refImageUpload"
v-model:dialogState="imageDialog"
v-model:file="profileFile"
v-model:image-url="imageUrl"
v-model:data-list="imageList"
v-model:on-create-data-list="onCreateImageList"
:on-create="modal"
:hiddenFooter="!isImageEdit"
@add-image="
async (v) => {
if (!v) return;
if (!currentId) return;
await branchStore.addImageList(v, currentId, Date.now().toString());
await fetchImageList(currentId, formData.selectedImage || '');
}
"
@remove-image="
async (v) => {
if (!v) return;
if (!currentId) return;
const name = v.split('/').pop() || '';
await branchStore.deleteImageByName(currentId, name);
await fetchImageList(currentId, formData.selectedImage || '');
}
"
@submit="
async (v) => {
if (modal) {
imageUrl = v;
imageDialog = false;
} else {
refreshImageState = true;
formData.selectedImage = v;
imageList ? (imageList.selectedImage = v) : '';
imageUrl = `${baseUrl}/branch/${currentId && currentId}/image/${v}`;
await onSubmit(true);
imageDialog = false;
refreshImageState = false;
}
}
"
>
<template #title>
<span v-if="!modal" class="justify-center flex text-bold">
{{ $t('general.image') }}
{{ changeTitle(formType, formTypeBranch) }}
{{ $i18n.locale === 'eng' ? formData.nameEN : formData.name }}
</span>
</template>
<template #error>
<div class="full-height full-width" style="background: white">
<div
class="full-height full-width flex justify-center items-center"
:style="`background: hsla(var(${
formTypeBranch === 'headOffice'
? '--pink-6'
: formData.virtual
? '--blue-6'
: $q.dark.isActive
? '--violet-10'
: '--violet-11'
}-hsl)/0.15)`"
>
<q-icon
size="3rem"
name="mdi-office-building-outline"
:style="`color: hsla(var(${
formTypeBranch === 'headOffice'
? '--pink-6'
: formData.virtual
? '--blue-6'
: $q.dark.isActive
? '--violet-10'
: '--violet-11'
}-hsl)/1)`"
></q-icon>
</div>
</div>
</template>
</ImageUploadDialog>
<DialogForm
v-model:modal="modelCreateTypeBranch"
:title="$t('general.typeBranch')"
hide-footer
no-app-box
width="40vw"
height="250px"
:close="() => (modelCreateTypeBranch = false)"
>
<div class="full-height row q-pa-md" style="gap: 16px">
<ItemCard
v-for="(value, index) in typeBranchItem"
class="col full-height"
:key="value.text"
:icon="value.icon"
:text="value.text"
:icon-color="value.iconColor"
:bg-color="value.color"
:index="index"
@trigger="
() => {
modelCreateTypeBranch = false;
if (value.text === 'branch.card.branchVirtual') {
formData.virtual = true;
} else {
formData.virtual = false;
}
triggerCreate(
formData.virtual ? 'branchVirtual' : 'subBranch',
currentEdit.id || currentHq.id,
currentEdit.code || currentHq.code,
);
}
"
/>
</div>
</DialogForm>
</template>
<style scoped>
.color-icon-arrow {
color: var(--gray-3);
}
.color-icon-plus {
color: var(--cyan-6);
}
.tree-container {
width: 100%;
min-width: 300px;
max-width: 25%;
max-height: 100%;
}
.branch-wrapper {
flex-grow: 1;
& > .branch-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: unset;
gap: var(--size-4);
}
}
.branch-card__hq {
--_branch-card-bg: var(--pink-6-hsl);
}
.branch-card__br {
--_branch-card-bg: var(--violet-11-hsl);
&.branch-card__dark {
--_branch-card-bg: var(--violet-10-hsl);
}
}
.branch-card__br-virtual {
--_branch-card-bg: var(--blue-6-hsl);
}
.status-active {
--_branch-status-color: var(--green-6-hsl);
}
.status-inactive {
--_branch-status-color: var(--red-4-hsl);
--_branch-badge-bg: var(--red-4-hsl);
filter: grayscale(1);
opacity: 0.5;
}
.branch-card__icon {
background-color: hsla(var(--_branch-card-bg) / 0.15);
border-radius: 50%;
padding: var(--size-2);
position: relative;
width: 3rem;
transform: rotate(45deg);
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
/* &::after {
content: ' ';
display: block;
block-size: 0.5rem;
aspect-ratio: 1;
position: absolute;
border-radius: 50%;
right: -0.25rem;
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);
}
}
.slide-enter-active {
transition: all 0.1s ease-out;
}
.slide-leave-active {
transition: all 0.1s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-enter-from,
.slide-leave-to {
transform: translateY(-20px);
opacity: 0;
}
* :deep(.q-icon.mdi-play) {
display: none;
}
.badge-virtual {
--_badge-fg: var(--blue-8-hsl);
--_badge-bg: var(--blue-6-hsl);
}
.badge-branch {
--_badge-fg: var(--purple-8-hsl);
--_badge-bg: var(--purple-6-hsl);
}
.badge-color {
color: hsl(var(--_badge-fg));
background-color: hsla(var(--_badge-bg) / 0.1);
}
</style>