jws-frontend/src/pages/07_agencies-management/AgenciesDialog.vue
puriphatt b977f86de9
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
feat: institution attachment
2025-07-16 14:39:41 +07:00

666 lines
19 KiB
Vue

<script setup lang="ts">
import { reactive, ref, watch } from 'vue';
import { InstitutionPayload } from 'src/stores/institution/types';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useInstitution } from 'src/stores/institution';
import { baseUrl } from 'src/stores/utils';
import DrawerInfo from 'src/components/DrawerInfo.vue';
import DialogForm from 'src/components/DialogForm.vue';
import ProfileBanner from 'src/components/ProfileBanner.vue';
import SideMenu from 'src/components/SideMenu.vue';
import FormBank from 'src/components/01_branch-management/FormBank.vue';
import FormBasicInfoAgencies from 'src/components/07_agencies-management/FormBasicInfoAgencies.vue';
import {
UndoButton,
SaveButton,
EditButton,
DeleteButton,
} from 'src/components/button';
import AddressForm from 'src/components/form/AddressForm.vue';
import ImageUploadDialog from 'src/components/ImageUploadDialog.vue';
import { BankBook } from 'src/stores/branch/types';
import QrCodeUploadDialog from 'src/components/QrCodeUploadDialog.vue';
const institutionStore = useInstitution();
const model = defineModel<boolean>({ required: true, default: false });
const drawerModel = defineModel<boolean>('drawerModel', {
required: true,
default: false,
});
const imageListOnCreate = defineModel<{
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}>('imageListOnCreate', { default: { selectedImage: '', list: [] } });
const deletesStatusQrCodeBankImag = defineModel<number[]>(
'deletesStatusQrCodeBankImag',
{ default: [] },
);
const imageState = reactive({
imageDialog: false,
isImageEdit: false,
refreshImageState: false,
imageUrl: '',
});
const imageFile = ref<File | null>(null);
const imageList = ref<{ selectedImage: string; list: string[] }>();
const qrCodeDialog = ref(false);
const qrCodeImageUrl = ref<string>('');
const currentIndexQrCodeBank = ref<number>(-1);
const statusQrCodeFile = ref<File | undefined>(undefined);
const refQrCodeUpload = ref();
const statusQrCodeUrl = ref<string>('');
const statusDeletesQrCode = ref<boolean>(false);
const props = withDefaults(
defineProps<{
readonly?: boolean;
isEdit?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
dataId?: string;
}>(),
{ readonly: false, isEdit: false, dataId: '' },
);
defineExpose({ clearImageState });
const emit = defineEmits<{
(e: 'submit'): void;
(e: 'close'): void;
(e: 'changeStatus'): void;
(e: 'drawerUndo'): void;
(e: 'drawerEdit'): void;
(e: 'drawerDelete'): void;
(e: 'addImage'): void;
(e: 'removeImage'): void;
(e: 'submitImage', name: string): void;
(e: 'deleteAttachment', name: string): void;
}>();
const data = defineModel<InstitutionPayload>('data', {
required: true,
default: {
group: '',
name: '',
nameEN: '',
contactName: '',
contactEmail: '',
contactTel: '',
code: '',
addressEN: '',
address: '',
soi: '',
soiEN: '',
moo: '',
mooEN: '',
street: '',
streetEN: '',
subDistrictId: '',
districtId: '',
provinceId: '',
selectedImage: '',
},
});
const formBankBook = defineModel<BankBook[]>('formBankBook', {
default: [
{
bankName: '',
accountNumber: '',
bankBranch: '',
accountName: '',
accountType: '',
currentlyUse: true,
bankUrl: '',
},
],
});
const attachment = defineModel<File[]>('attachment');
const attachmentList =
defineModel<{ name: string; url: string }[]>('attachmentList');
function viewImage() {
imageState.imageDialog = true;
imageState.isImageEdit = false;
}
function editImage() {
imageState.imageDialog = imageState.isImageEdit = true;
}
async function fetchImageList(id: string, selectedName: string) {
const res = await institutionStore.fetchImageListById(id);
imageList.value = {
selectedImage: selectedName,
list: res.map((n: string) => `institution/${id}/image/${n}`),
};
return res;
}
async function addImage(file: File | null) {
if (!file) return;
if (!props.dataId) return;
await institutionStore.addImageList(
file,
props.dataId,
Date.now().toString(),
);
await fetchImageList(props.dataId, data.value.selectedImage || '');
}
async function removeImage(name: string) {
if (!name) return;
if (!props.dataId) return;
const targetName = name.split('/').pop() || '';
await institutionStore.deleteImageByName(props.dataId, targetName);
await fetchImageList(props.dataId, data.value.selectedImage || '');
}
async function submitImage(name: string) {
if (model.value) {
imageState.imageUrl = name;
imageState.imageDialog = false;
} else {
imageState.refreshImageState = true;
emit('submitImage', name);
}
}
function clearImageState() {
imageState.imageDialog = false;
imageFile.value = null;
imageListOnCreate.value = { selectedImage: '', list: [] };
imageState.refreshImageState = false;
}
function triggerEditQrCodeBank(opts?: { save?: boolean }) {
if (opts?.save === undefined) {
qrCodeDialog.value = true;
statusDeletesQrCode.value = false;
statusQrCodeUrl.value =
formBankBook.value[currentIndexQrCodeBank.value].bankUrl || '';
statusDeletesQrCode.value = false;
} else {
formBankBook.value[currentIndexQrCodeBank.value].bankUrl =
statusQrCodeUrl.value;
formBankBook.value[currentIndexQrCodeBank.value].bankQr =
statusQrCodeFile.value;
if (statusDeletesQrCode.value === true) {
deletesStatusQrCodeBankImag.value.push(currentIndexQrCodeBank.value);
}
if (statusDeletesQrCode.value === false) {
deletesStatusQrCodeBankImag.value =
deletesStatusQrCodeBankImag.value.filter(
(item) => item !== currentIndexQrCodeBank.value,
);
}
currentIndexQrCodeBank.value = -1;
statusDeletesQrCode.value = false;
}
}
watch(
() => imageFile.value,
() => {
if (imageFile.value !== null) imageState.isImageEdit = true;
},
);
watch(
() => data.value.selectedImage,
() => {
imageState.imageUrl = `${baseUrl}/institution/${props.dataId}/image/${data.value.selectedImage}`;
imageList.value
? (imageList.value.selectedImage = data.value.selectedImage || '')
: '';
imageState.refreshImageState = false;
},
);
watch(
() => drawerModel.value,
async () => {
if (drawerModel.value) {
await fetchImageList(props.dataId, data.value.selectedImage || '');
imageState.imageUrl = `${baseUrl}/institution/${props.dataId}/image/${data.value.selectedImage}`;
} else {
imageList.value = { selectedImage: '', list: [] };
}
},
);
</script>
<template>
<DialogForm
hide-footer
:title="$t('general.add', { text: $t('agencies.title') })"
v-model:modal="model"
:submit="() => $emit('submit')"
:close="
() => {
imageState.imageUrl = '';
clearImageState();
$emit('close');
}
"
>
<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"
active
use-toggle
hide-fade
:toggle-title="$t('status.title')"
:icon="'ph-building-office'"
:img="imageState.imageUrl || null"
:title="data.name"
:caption="data.code"
:color="`hsla(var(--green-8-hsl)/1)`"
:bg-color="`hsla(var(--green-8-hsl)/0.1)`"
v-model:toggle-status="data.status"
@view="viewImage"
@edit="editImage"
@update:toggle-status="
() => {
data.status = data.status === '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: 'agencies-form-basic-info',
},
{
name: $t('general.address'),
anchor: 'agencies-form-address-info',
},
{
name: $t('agencies.bankInfo'),
anchor: 'agencies-form-bank-info',
},
]"
background="transparent"
:active="{
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#agencies-form-content"
/>
</div>
</div>
<div
class="col-12 col-md-10"
id="agencies-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="rounded"
:class="{
'q-py-md q-px-lg': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm,
}"
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>
<FormBasicInfoAgencies
id="agencies-form-basic-info"
class="q-mb-xl"
v-model:group="data.group"
v-model:name="data.name"
v-model:name-en="data.nameEN"
v-model:contact-name="data.contactName"
v-model:email="data.contactEmail"
v-model:contact-tel="data.contactTel"
v-model:attachment="attachment"
/>
<AddressForm
id="agencies-form-address-info"
dense
:prefix-id="''"
v-model:address="data.address"
v-model:address-en="data.addressEN"
v-model:moo="data.moo"
v-model:moo-en="data.mooEN"
v-model:soi="data.soi"
v-model:soi-en="data.soiEN"
v-model:street="data.street"
v-model:street-en="data.streetEN"
v-model:province-id="data.provinceId"
v-model:district-id="data.districtId"
v-model:sub-district-id="data.subDistrictId"
/>
<FormBank
id="agencies-form-bank-info"
title="agencies.bankInfo"
class="q-pt-xl"
dense
v-model:bank-book-list="formBankBook"
@view-qr="
(i) => {
currentIndexQrCodeBank = i;
triggerEditQrCodeBank();
}
"
@edit-qr="
(i) => {
currentIndexQrCodeBank = i;
refQrCodeUpload && refQrCodeUpload.browse();
}
"
/>
</div>
</div>
</DialogForm>
<DrawerInfo
bg-on
hide-action
:is-edit="isEdit"
:title="data.name"
v-model:drawerOpen="drawerModel"
:submit="() => $emit('submit')"
:close="
() => {
clearImageState();
$emit('close');
}
"
>
<div class="col column full-height">
<div
:class="{
'q-mx-lg q-my-md': $q.screen.gt.sm,
'q-mx-md q-my-sm': !$q.screen.gt.sm,
}"
>
<ProfileBanner
:prefix="data.name"
hide-fade
use-toggle
:readonly="hideAction"
:active="data.status !== 'INACTIVE'"
:toggle-title="$t('status.title')"
:icon="'ph-building-office'"
:title="data.name"
:caption="data.code"
:color="`hsla(var(${'--green-8'}-hsl)/1)`"
:bg-color="`hsla(var(${'--green-8'}-hsl)/0.1)`"
:img="
`${baseUrl}/institution/${dataId}/image/${data.selectedImage}`.concat(
imageState.refreshImageState ? `?ts=${Date.now()}` : '',
) || null
"
v-model:toggle-status="data.status"
@view="viewImage"
@edit="editImage"
@update:toggle-status="$emit('changeStatus')"
/>
</div>
<div
style="flex: 1; width: 100%; overflow-y: auto"
id="drawer-user-form"
:class="{
'q-px-lg q-pb-lg': $q.screen.gt.sm,
'q-px-md q-pb-sm': !$q.screen.gt.sm,
}"
>
<div
class="col surface-1 full-height rounded bordered scroll row relative-position"
>
<div
class="rounded row"
:class="{
'q-py-md q-px-lg': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm,
}"
style="position: absolute; z-index: 999; top: 0; right: 0"
>
<div
v-if="data.status !== 'INACTIVE' && !hideAction"
class="surface-1 row rounded"
>
<UndoButton
v-if="isEdit"
id="btn-info-basic-undo"
icon-only
@click="
() => {
$emit('drawerUndo');
}
"
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="
() => {
$emit('drawerEdit');
// infoDrawerEdit = true;
// isImageEdit = true;
}
"
type="button"
/>
<DeleteButton
v-if="!isEdit && !hideDelete"
id="btn-info-basic-delete"
icon-only
@click="
() => {
$emit('drawerDelete');
}
"
type="button"
/>
</div>
</div>
<div
class="col"
style="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: 'agencies-basic-info',
},
{
name: $t('general.address'),
anchor: 'agencies-address-info',
},
{
name: $t('agencies.bankInfo'),
anchor: 'agencies-bank-info',
},
]"
background="transparent"
:active="{
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#agencies-view-content"
/>
</div>
</div>
<div
class="col-12 col-md-10 relative-position"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm,
}"
id="agencies-view-content"
style="height: 100%; max-height: 100; overflow-y: auto"
>
<FormBasicInfoAgencies
id="agencies-basic-info"
:readonly
onDrawer
class="q-mb-xl"
v-model:group="data.group"
v-model:name="data.name"
v-model:name-en="data.nameEN"
v-model:contact-name="data.contactName"
v-model:email="data.contactEmail"
v-model:contact-tel="data.contactTel"
v-model:attachment="attachment"
:attachment-list="attachmentList"
@delete-attachment="(name) => $emit('deleteAttachment', name)"
/>
<AddressForm
id="agencies-address-info"
dense
:readonly
:prefix-id="data.code ? data.code : ''"
v-model:address="data.address"
v-model:address-en="data.addressEN"
v-model:moo="data.moo"
v-model:moo-en="data.mooEN"
v-model:soi="data.soi"
v-model:soi-en="data.soiEN"
v-model:street="data.street"
v-model:street-en="data.streetEN"
v-model:province-id="data.provinceId"
v-model:district-id="data.districtId"
v-model:sub-district-id="data.subDistrictId"
/>
<FormBank
id="agencies-bank-info"
title="agencies.bankInfo"
class="q-pt-xl"
dense
:readonly
v-model:bank-book-list="formBankBook"
@view-qr="
(i) => {
currentIndexQrCodeBank = i;
triggerEditQrCodeBank();
}
"
@edit-qr="
(i) => {
currentIndexQrCodeBank = i;
refQrCodeUpload && refQrCodeUpload.browse();
}
"
/>
</div>
</div>
</div>
</div>
</DrawerInfo>
<ImageUploadDialog
v-model:dialog-state="imageState.imageDialog"
v-model:file="imageFile"
v-model:on-create-data-list="imageListOnCreate"
v-model:image-url="imageState.imageUrl"
v-model:data-list="imageList"
:changeDisabled="hideAction"
:on-create="model"
:hiddenFooter="!imageState.isImageEdit"
@add-image="addImage"
@remove-image="removeImage"
@submit="submitImage"
>
<template #title>
<span v-if="!model" class="justify-center flex text-bold">
{{ $t('general.image') }}
{{ $i18n.locale === 'eng' ? data.nameEN : data.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(--green-8-hsl) / 0.1);
color: hsla(var(--green-8-hsl) / 1);
"
>
<Icon width="3rem" icon="ph-building-office" />
</div>
</div>
</template>
</ImageUploadDialog>
<QrCodeUploadDialog
ref="refQrCodeUpload"
v-model:dialog-state="qrCodeDialog"
v-model:file="statusQrCodeFile as File"
v-model:image-url="statusQrCodeUrl"
@save="
(_file) => {
qrCodeDialog = false;
if (currentIndexQrCodeBank !== -1) {
triggerEditQrCodeBank({ save: true });
}
}
"
@clear="statusDeletesQrCode = 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>
</template>
<style scoped></style>