diff --git a/src/components/ImageUploadDialog.vue b/src/components/ImageUploadDialog.vue index 0d72d366..bba1d632 100644 --- a/src/components/ImageUploadDialog.vue +++ b/src/components/ImageUploadDialog.vue @@ -1,17 +1,23 @@ - - + + + + - + + + + + + + { + tempImage = ''; + } + " /> + + + + + { + inputFile?.click(); + } + " + style="color: hsla(var(--stone-0-hsl) / 0.7)" + > + + + + + + + + + + + + + { + if (onCreate) { + const v = onCreateData.list.splice(n, 1); + if (v[0].url === selectedImg) { + if (onCreateData.list.length === 0 || n === 0) { + selectImg(''); + } else { + selectImg(onCreateData.list[n - 1].url); + } + } + } else { + dialog({ + color: 'negative', + icon: 'mdi-alert', + title: $t('dialog.title.confirmDelete'), + actionText: $t('general.delete'), + message: $t('dialog.message.confirmDelete'), + action: async () => { + $emit('removeImage', img); + const index = dataList.list.indexOf(img); + if (img.split('/').pop() === selectedImg) { + if (dataList.list.length === 0 || index === 0) { + selectImg(''); + } else { + selectImg(dataList.list[index - 1]); + } + } + }, + cancel: () => {}, + }); + } + } + " + > + + + + + + - - - - - - - - - - + + - + /> --> + @@ -171,24 +393,24 @@ async function downloadImage(url: string) { padding: 0; border-radius: var(--radius-2); flex-wrap: nowrap; - width: 100%; - max-width: 80%; } @media (min-width: 768px) { .image-dialog-content { - max-width: 60%; + max-width: 70%; } } .image-dialog-body { - overflow-y: auto; - background-color: var(--surface-2) !important; + /* overflow-y: auto; + background-color: var(--surface-1) !important; position: relative; display: flex; flex-direction: column; - flex: 1; - height: calc(100vh - 200px); + flex: 1; */ + overflow: hidden; + width: 30em; + height: 30em; } .upload-image-btn { @@ -227,4 +449,21 @@ async function downloadImage(url: string) { border: 1px solid hsl(var(--negative-bg)); } } + +.selected-img { + position: relative; + border: 2px solid hsl(var(--info-bg)); +} + +.selected-img:before { + content: ' '; + position: absolute; + z-index: 1; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + border: 2px solid var(--surface-1); + border-radius: 5px; +} diff --git a/src/i18n/eng/index.ts b/src/i18n/eng/index.ts index 07c0ae21..0783c561 100644 --- a/src/i18n/eng/index.ts +++ b/src/i18n/eng/index.ts @@ -60,6 +60,8 @@ export default { success: 'Success', taxNo: 'Legal Person', contactName: 'Contact Name', + image: 'Image of ', + apply: 'Apply', }, menu: { diff --git a/src/i18n/tha/index.ts b/src/i18n/tha/index.ts index 4de33459..1c3e0fcf 100644 --- a/src/i18n/tha/index.ts +++ b/src/i18n/tha/index.ts @@ -60,6 +60,8 @@ export default { success: 'สำเร็จ', taxNo: 'ทะเบียนนิติบุคคล', contactName: 'ติดต่อ', + image: 'รูปภาพ', + apply: 'นำไปใช้', }, menu: { diff --git a/src/pages/01_branch-management/MainPage.vue b/src/pages/01_branch-management/MainPage.vue index 19838466..6507676b 100644 --- a/src/pages/01_branch-management/MainPage.vue +++ b/src/pages/01_branch-management/MainPage.vue @@ -47,7 +47,6 @@ import { UndoButton, } from 'components/button'; -const apiBaseUrl = import.meta.env.VITE_API_BASE_URL; const $q = useQuasar(); const { t } = useI18n(); const utilsStore = useUtilsStore(); @@ -160,12 +159,18 @@ const refQrCodeUpload = ref(); const refImageUpload = ref(); const isImageEdit = ref(false); const isQrCodeEdit = ref(false); -const profileFile = ref(undefined); +const profileFile = ref(null); const qrCodeFile = ref(undefined); const imageUrl = ref(''); const prevImageUrl = ref(''); const currentNode = ref(); 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(''); @@ -335,6 +340,7 @@ const defaultFormData = { lineId: '', webUrl: '', virtual: false, + selectedImage: '', }; const formDialogRef = ref(); @@ -350,11 +356,11 @@ const currentEdit = ref<{ id: string; code: string }>({ code: '', }); const formData = ref< - Omit + Omit >(structuredClone(defaultFormData)); const prevFormData = ref< - Omit + Omit >(structuredClone(defaultFormData)); const modalDrawer = ref(false); @@ -383,8 +389,8 @@ async function selectedSubBranche(id: string) { async function fetchBranchById(id: string) { const res = await branchStore.fetchById(id, { includeContact: true }); if (res) { - qrCodeimageUrl.value = `${apiBaseUrl}/branch/${res.id}/line-image?ts=${Date.now()}`; - imageUrl.value = `${apiBaseUrl}/branch/${res.id}/branch-image`; + qrCodeimageUrl.value = `${baseUrl}/branch/${res.id}/image?ts=${Date.now()}`; + imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`; prevImageUrl.value = res.imageUrl; formBankBook.value = res.bank; @@ -417,6 +423,7 @@ async function fetchBranchById(id: string) { status: res.status, webUrl: res.webUrl, virtual: res.virtual, + selectedImage: res.selectedImage, }; } } @@ -447,8 +454,9 @@ async function undo() { isQrCodeEdit.value = false; isImageEdit.value = false; formType.value = 'view'; - imageUrl.value = prevImageUrl.value; - formData.value = prevFormData.value; + const tempSelectedImage = formData.value.selectedImage; + formData.value = JSON.parse(JSON.stringify(prevFormData.value)); + formData.value.selectedImage = tempSelectedImage; } watch(expandedTree, async () => { @@ -501,9 +509,7 @@ function drawerEdit() { isQrCodeEdit.value = true; isImageEdit.value = true; formType.value = 'edit'; - prevFormData.value = { - ...formData.value, - }; + prevFormData.value = JSON.parse(JSON.stringify(formData.value)); } const currentBranchAdmin = ref(null); @@ -514,6 +520,8 @@ async function triggerEdit( code?: string, ) { await fetchBranchById(id); + await fetchImageList(id, formData.value.selectedImage || ''); + if (openFormType === 'form') { formType.value = 'edit'; openDialog(); @@ -675,12 +683,12 @@ async function triggerChangeStatus( }); } -async function onSubmit() { - if (formType.value === 'edit') { +async function onSubmit(submitSelectedItem?: boolean) { + if (formType.value === 'edit' || submitSelectedItem) { delete formData.value['codeHeadOffice']; delete formData.value['code']; - await branchStore.editById( + const res = await branchStore.editById( currentEdit.value.id, { ...formData.value, @@ -695,8 +703,12 @@ async function onSubmit() { }, ); + formData.value.codeHeadOffice = formData.value.code = res.code; + imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`; + await fetchList({ pageSize: 99999 }); - if (!imageDialog.value) modalDrawer.value = false; + + if (!imageDialog.value) modalDrawer.value = submitSelectedItem || false; modal.value = false; } @@ -709,6 +721,7 @@ async function onSubmit() { imageUrl: profileFile.value, }, formBankBook.value, + onCreateImageList.value, ); await fetchList({ pageSize: 99999 }); @@ -801,6 +814,15 @@ function handleHold(node: BranchWithChildren) { // }; } +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, () => { @@ -1291,7 +1313,11 @@ watch(currentHq, () => { :ratio="1" :src=" baseUrl + - `/branch/${props.row.id}/branch-image?ts=${Date.now()}` + `/branch/${props.row.id}/image/${props.row.selectedImage}`.concat( + refreshImageState + ? `?ts=${Date.now()}` + : '', + ) " > @@ -1392,6 +1418,7 @@ watch(currentHq, () => { 'subBranch', ); } + await drawerEdit(); formType = 'edit'; } " @@ -1465,7 +1492,7 @@ watch(currentHq, () => { $i18n.locale === 'eng' ? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}` : `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`, - branchImgUrl: `/branch/${props.row.id}/branch-image`, + branchImgUrl: `/branch/${props.row.id}/branch`, }" :field-selected="fieldSelected" :badge-field="['branchLabelStatus']" @@ -1517,6 +1544,7 @@ watch(currentHq, () => { 'subBranch', ); } + await drawerEdit(); formType = 'edit'; } " @@ -1556,13 +1584,15 @@ watch(currentHq, () => { } " :close=" - () => ( - (formLastSubBranch = 0), - (modal = false), - (profileFile = undefined), - (isImageEdit = false), - (isSubCreate = false) - ) + () => { + formLastSubBranch = 0; + modal = false; + profileFile = null; + isImageEdit = false; + isSubCreate = false; + imageList = { selectedImage: '', list: [] }; + onCreateImageList = { selectedImage: '', list: [] }; + } " > @@ -1604,8 +1634,13 @@ watch(currentHq, () => { : '--violet-11' }-hsl)/${imageUrl ? '0' : '0.15'})`" :menu="formMenuIcon" - @view="imageDialog = true" - @edit="refImageUpload && refImageUpload.browse()" + @view=" + () => { + imageDialog = true; + isImageEdit = false; + } + " + @edit="imageDialog = isImageEdit = true" @update:toggle-status=" () => { formData.status = @@ -1769,21 +1804,18 @@ watch(currentHq, () => { { v-model:cover-url="imageUrl" :toggleTitle="$t('status.title')" :hideFade="imageUrl === '' || imageUrl === null" - :img="imageUrl || null" - :cover="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" @@ -1820,10 +1855,15 @@ watch(currentHq, () => { : $q.dark.isActive ? '--violet-10' : '--violet-11' - }-hsl)/${imageUrl ? '0' : '0.15'})`" + }-hsl)/${imageUrl ? '0' : '0.1'})`" v-model:toggle-status="formData.status" - @view="imageDialog = true" - @edit="refImageUpload && refImageUpload.browse()" + @view=" + () => { + imageDialog = true; + isImageEdit = false; + } + " + @edit="imageDialog = isImageEdit = true" @update:toggle-status=" async (v) => { const res = await triggerChangeStatus(currentId, v); @@ -1836,7 +1876,6 @@ watch(currentHq, () => { await fetchList({ pageSize: 99999 }); } " - :readonly="formType === 'view'" :menu="formMenuIcon" /> @@ -2200,12 +2239,53 @@ watch(currentHq, () => { { + if (!v) return; + if (!currentId) return; + await branchStore.addImageList(v, currentId, Date.now()); + 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, name); + } + " + @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; + } + } + " > + + + {{ $t('general.image') }} + {{ changeTitle(formType, formTypeBranch) }} + {{ $i18n.locale === 'eng' ? formData.nameEN : formData.name }} + + import { ref, onMounted, watch, computed } from 'vue'; -import useUtilsStore, { dialog } from 'stores/utils'; +import useUtilsStore, { dialog, baseUrl } from 'stores/utils'; import { calculateAge } from 'src/utils/datetime'; import { useQuasar, type QTableProps } from 'quasar'; import { storeToRefs } from 'pinia'; @@ -59,7 +59,6 @@ const adrressStore = useAddressStore(); const useMyBranchStore = useMyBranch(); const { data: userData } = storeToRefs(userStore); const { myBranch } = storeToRefs(useMyBranchStore); -const apiBaseUrl = import.meta.env.VITE_API_BASE_URL; const defaultFormData = { provinceId: null, @@ -87,7 +86,6 @@ const defaultFormData = { firstName: '', userRole: '', userType: '', - profileImage: null, birthDate: null, responsibleArea: null, username: '', @@ -178,7 +176,6 @@ const userCode = ref(); const currentUser = ref(); const infoDrawerEdit = ref(false); const infoPersonId = ref(''); -const infoPersonCard = ref(); const infoDrawer = ref(false); const profileSubmit = ref(false); const urlProfile = ref(); @@ -198,6 +195,7 @@ const typeStats = ref(); const agencyFile = ref([]); const agencyFileList = ref<{ name: string; url: string }[]>([]); const formData = ref({ + selectedImage: '', branchId: '', provinceId: null, districtId: null, @@ -227,7 +225,6 @@ const formData = ref({ namePrefix: null, userRole: '', userType: '', - profileImage: null, birthDate: null, responsibleArea: null, checkpoint: null, @@ -240,12 +237,18 @@ const isImageEdit = ref(false); const imageUrl = ref(''); const profileFileImg = ref(null); 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 inputFile = document.createElement('input'); // inputFile.type = 'file'; // inputFile.accept = 'image/*'; -const reader = new FileReader(); +// const reader = new FileReader(); const columns = [ { @@ -357,23 +360,6 @@ async function openDialog( infoDrawerEdit.value = isPersonEdit ? true : false; infoDrawer.value = true; - const user = userData.value.result.find((x) => x.id === id); - infoPersonCard.value = user - ? [ - { - id: user.id, - img: `${user.profileImageUrl}`, - name: - locale.value === 'eng' - ? `${user.firstNameEN} ${user.lastNameEN}` - : `${user.firstName} ${user.lastName}`, - male: user.gender === 'male', - female: user.gender === 'female', - badge: user.code, - disabled: user.status === 'INACTIVE', - }, - ] - : []; } statusToggle.value = true; @@ -387,7 +373,11 @@ function undo() { assignFormData(infoPersonId.value); } -function onClose() { +function onClose(excludeDialog?: boolean) { + if (excludeDialog) { + infoDrawer.value = excludeDialog || false; + return; + } hqId.value = ''; brId.value = ''; userId.value = ''; @@ -398,17 +388,17 @@ function onClose() { agencyFile.value = []; modal.value = false; isEdit.value = false; - infoDrawer.value = false; profileSubmit.value = false; statusToggle.value = true; isImageEdit.value = false; + currentUser.value = undefined; Object.assign(formData.value, defaultFormData); mapUserType(selectorLabel.value); + imageList.value = { selectedImage: '', list: [] }; + onCreateImageList.value = { selectedImage: '', list: [] }; flowStore.rotate(); } -async function onSubmit() { - formData.value.profileImage = profileFileImg.value as File; - +async function onSubmit(excludeDialog?: boolean) { if (isEdit.value && userId.value) { if (!userId.value) return; @@ -441,7 +431,7 @@ async function onSubmit() { if (res) { userStats.value = res; } - onClose(); + onClose(excludeDialog); } else { if (!hqId.value) return; @@ -456,7 +446,10 @@ async function onSubmit() { : hqId.value ? hqId.value : ''; - const result = await userStore.create(formData.value); + const result = await userStore.create( + formData.value, + onCreateImageList.value, + ); if (result && formData.value.userType === 'AGENCY') { if (!agencyFile.value) return; @@ -558,7 +551,6 @@ async function assignFormData(idEdit: string) { currentUser.value = foundUser; if (currentUser.value) { infoPersonId.value = currentUser.value.id; - imageUrl.value = currentUser.value.profileImageUrl; } formData.value = { branchId: foundUser.branch[0]?.id, @@ -591,6 +583,7 @@ async function assignFormData(idEdit: string) { checkpointEN: foundUser.checkpointEN, responsibleArea: foundUser.responsibleArea, status: foundUser.status, + selectedImage: foundUser.selectedImage, licenseExpireDate: (foundUser.licenseExpireDate && new Date(foundUser.licenseExpireDate)) || @@ -619,14 +612,14 @@ async function assignFormData(idEdit: string) { } userCode.value = foundUser.code; - urlProfile.value = `${apiBaseUrl}/user/${foundUser.id}/image`; + urlProfile.value = `${baseUrl}/user/${foundUser.id}/profile-image/${foundUser.selectedImage}`; isEdit.value = true; profileSubmit.value = true; hqId.value && (await userStore.fetchBrOption(hqId.value)); - if (infoPersonCard.value) { - infoPersonCard.value[0].img = foundUser.profileImageUrl; - } + + await fetchImageList(foundUser.id, foundUser.selectedImage); + if (formData.value.districtId) { await adrressStore.fetchSubDistrictByProvinceId( formData.value.districtId, @@ -635,10 +628,6 @@ async function assignFormData(idEdit: string) { } } -function openImageDialog() { - imageDialog.value = true; -} - onMounted(async () => { utilsStore.currentTitle.title = 'personnel.title'; utilsStore.currentTitle.path = [{ text: 'personnel.caption', i18n: true }]; @@ -667,6 +656,15 @@ function mapRole(value: string) { return option ? option.label : '-'; } +async function fetchImageList(id: string, selectedName: string) { + const res = await userStore.fetchImageListById(id); + imageList.value = { + selectedImage: selectedName, + list: res.map((n: string) => `user/${id}/profile-image/${n}`), + }; + return res; +} + watch( () => selectorLabel.value, async (label) => { @@ -1145,7 +1143,7 @@ watch( { + imageDialog = true; + isImageEdit = false; + } + " + @edit="imageDialog = isImageEdit = true" @update:toggle-status=" async (v) => { await triggerChangeStatus(infoPersonId, v); @@ -1779,8 +1782,13 @@ watch( }[formData.gender] " hideFade - @view="imageDialog = true" - @edit="refImageUpload && refImageUpload.browse()" + @view=" + () => { + imageDialog = true; + isImageEdit = false; + } + " + @edit="imageDialog = isImageEdit = true" @update:toggle-status=" () => { formData.status = @@ -1914,10 +1922,59 @@ watch( v-model:dialogState="imageDialog" v-model:file="profileFileImg" v-model:image-url="urlProfile" - :hiddenFooter="!isImageEdit && !modal" - @save="imageDialog = false" - clearButton + 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 (!currentUser) return; + const res = await userStore.addImageList( + v, + currentUser?.id, + Date.now(), + ); + await fetchImageList(currentUser?.id, res); + } + " + @remove-image=" + async (v) => { + if (!v) return; + if (!currentUser) return; + + const name = v.split('/').pop() || ''; + await userStore.deleteImageByName(currentUser?.id, name); + await fetchImageList(currentUser?.id, name); + } + " + @submit=" + async (v) => { + if (modal) { + urlProfile = v; + imageDialog = false; + } else { + refreshImageState = true; + formData.selectedImage = v; + imageList ? (imageList.selectedImage = v) : ''; + urlProfile = `${baseUrl}/user/${currentUser && currentUser.id}/profile-image/${v}`; + await onSubmit(true); + imageDialog = false; + refreshImageState = false; + } + } + " > + + + {{ $t('general.image') }} + {{ + $i18n.locale === 'eng' + ? `${formData.firstNameEN} ${formData.lastNameEN}` + : `${formData.firstName} ${formData.lastName}` + }} + + (); +const onCreateImageList = ref<{ + selectedImage: string; + list: { url: string; imgFile: File | null; name: string }[]; +}>({ selectedImage: '', list: [] }); + watch(() => route.name, init); watch( [currentTab, currentStatus, inputSearch, customerTypeSelected], @@ -475,6 +483,7 @@ async function openHistory(id: string) { async function editCustomerForm(id: string) { await customerFormStore.assignFormData(id); await fetchListOfOptionBranch(); + await fetchImageList(id, customerFormData.value.selectedImage || ''); customerFormState.value.dialogType = 'edit'; customerFormState.value.drawerModal = true; customerFormState.value.editCustomerId = id; @@ -563,6 +572,15 @@ function returnCountryCode(country: string) { return tempValue?.value; } +async function fetchImageList(id: string, selectedName: string) { + const res = await customerStore.fetchImageListById(id); + imageList.value = { + selectedImage: selectedName, + list: res.map((n: string) => `customer/${id}/image/${n}`), + }; + return res; +} + // TODO: When in employee form, if select address same as customer then auto fill watch( @@ -705,6 +723,14 @@ watch( }, ); +watch( + () => customerFormData.value.image, + () => { + if (customerFormData.value.image !== null) + customerFormState.value.isImageEdit = true; + }, +); + const emptyCreateDialog = ref(false); @@ -1161,7 +1187,7 @@ const emptyCreateDialog = ref(false); @@ -1371,7 +1397,7 @@ const emptyCreateDialog = ref(false); $i18n.locale === 'eng' ? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim() : `${props.row.firstName} ${props.row.lastName} `.trim(), - img: `${baseUrl}/customer/${props.row.id}/image`, + img: `${baseUrl}/customer/${props.row.id}/image/${props.row.selectedImage}`, fallbackImg: `/images/customer-${props.row.customerType}-avartar-${props.row.gender}.png`, male: undefined, female: undefined, @@ -1750,8 +1776,15 @@ const emptyCreateDialog = ref(false); ? 'mdi-account-plus-outline' : 'mdi-office-building-outline' " - @view="customerFormState.imageDialog = true" - @edit="dialogCustomerImageUpload?.browse()" + @view=" + () => { + customerFormState.imageDialog = true; + customerFormState.isImageEdit = false; + } + " + @edit=" + customerFormState.imageDialog = customerFormState.isImageEdit = true + " /> { - await customerFormStore.submitFormCustomer(); + await customerFormStore.submitFormCustomer(onCreateImageList); customerFormState.readonly = true; notify('create', $t('general.success')); await fetchListCustomer(); @@ -2629,18 +2662,69 @@ const emptyCreateDialog = ref(false); v-model:dialog-state="customerFormState.imageDialog" v-model:file="customerFormData.image" v-model:image-url="customerFormState.customerImageUrl" + v-model:data-list="imageList" + v-model:on-create-data-list="onCreateImageList" + :on-create="customerFormState.dialogModal" :default-url="customerFormState.defaultCustomerImageUrl" - clear-button - @save=" + :hiddenFooter="!customerFormState.isImageEdit" + @add-image=" async (v) => { - if (v && customerFormState.editCustomerId) { - await customerStore.setImage(customerFormState.editCustomerId, v); - await fetchListCustomer(); + if (!v) return; + if (!customerFormState.editCustomerId) return; + await customerStore.addImageList( + v, + customerFormState.editCustomerId, + Date.now(), + ); + await fetchImageList( + customerFormState.editCustomerId, + customerFormData.selectedImage || '', + ); + } + " + @remove-image=" + async (v) => { + if (!v) return; + if (!customerFormState.editCustomerId) return; + const name = v.split('/').pop() || ''; + await customerStore.deleteImageByName( + customerFormState.editCustomerId, + name, + ); + await fetchImageList(customerFormState.editCustomerId, name); + } + " + @submit=" + async (v) => { + if (customerFormState.dialogModal) { + customerFormState.customerImageUrl = v; customerFormState.imageDialog = false; + } else { + refreshImageState = true; + customerFormData.selectedImage = v; + imageList ? (imageList.selectedImage = v) : ''; + customerFormState.customerImageUrl = `${baseUrl}/customer/${customerFormState.editCustomerId && customerFormState.editCustomerId}/image/${v}`; + customerFormStore.resetForm(); + await customerFormStore.submitFormCustomer(); + customerFormState.imageDialog = false; + refreshImageState = false; } } " > + + + {{ $t('general.image') }} + {{ + $i18n.locale === 'eng' + ? `${customerFormData.firstNameEN} ${customerFormData.lastNameEN}` + : `${customerFormData.firstName} ${customerFormData.lastName}` + }} + + { + customerFormState.imageDialog = true; + customerFormState.isImageEdit = false; + } + " + @edit=" + customerFormState.imageDialog = customerFormState.isImageEdit = true + " /> diff --git a/src/pages/03_customer-management/form.ts b/src/pages/03_customer-management/form.ts index 51ef0777..557fc924 100644 --- a/src/pages/03_customer-management/form.ts +++ b/src/pages/03_customer-management/form.ts @@ -30,6 +30,7 @@ export const useCustomerForm = defineStore('form-customer', () => { registeredBranchId: branchStore.currentMyBranch?.id || '', customerBranch: [], image: null, + selectedImage: '', }; let resetFormData = structuredClone(defaultFormData); @@ -50,6 +51,7 @@ export const useCustomerForm = defineStore('form-customer', () => { editCustomerBranchId?: string; treeFile: { label: string; file: { label: string }[] }[]; formDataOcr: Record; + isImageEdit: boolean; }>({ dialogType: 'info', dialogOpen: false, @@ -65,6 +67,7 @@ export const useCustomerForm = defineStore('form-customer', () => { defaultCustomerImageUrl: '', treeFile: [], formDataOcr: {}, + isImageEdit: false, }); watch( @@ -110,8 +113,9 @@ export const useCustomerForm = defineStore('form-customer', () => { if (state.value.dialogType === 'create') { state.value.editCustomerId = ''; } - + const currentImg = currentFormData.value.selectedImage; currentFormData.value = structuredClone(resetFormData); + currentFormData.value.selectedImage = currentImg; } async function assignFormData(id?: string) { @@ -129,8 +133,8 @@ export const useCustomerForm = defineStore('form-customer', () => { state.value.dialogType = 'edit'; state.value.editCustomerId = id; state.value.editCustomerCode = data.code; - state.value.customerImageUrl = `${apiBaseUrl}/customer/${id}/image`; - state.value.defaultCustomerImageUrl = `${apiBaseUrl}/customer/${id}/image`; + state.value.customerImageUrl = `${apiBaseUrl}/customer/${id}/image/${data.selectedImage}`; + state.value.defaultCustomerImageUrl = `${apiBaseUrl}/customer/${id}/image/${data.selectedImage}`; resetFormData.registeredBranchId = data.registeredBranchId; resetFormData.code = data.code || ''; @@ -144,6 +148,7 @@ export const useCustomerForm = defineStore('form-customer', () => { resetFormData.gender = data.gender; resetFormData.birthDate = new Date(data.birthDate); resetFormData.image = null; + resetFormData.selectedImage = data.selectedImage; resetFormData.customerBranch = await Promise.all( data.branch.map(async (v) => ({ @@ -259,11 +264,14 @@ export const useCustomerForm = defineStore('form-customer', () => { (currentFormData.value.customerBranch?.length || 0) - 1; } - async function submitFormCustomer() { + async function submitFormCustomer(imgList?: { + selectedImage: string; + list: { url: string; imgFile: File | null; name: string }[]; + }) { if (state.value.dialogType === 'info') return; if (state.value.dialogType === 'create') { - const _data = await customerStore.create(currentFormData.value); + const _data = await customerStore.create(currentFormData.value, imgList); if (_data) await assignFormData(_data.id); diff --git a/src/stores/branch/index.ts b/src/stores/branch/index.ts index 03df29ef..b9a53328 100644 --- a/src/stores/branch/index.ts +++ b/src/stores/branch/index.ts @@ -87,15 +87,80 @@ const useBranchStore = defineStore('api-branch', () => { return false; } - async function create(branch: BranchCreate, bank?: BankBook[]) { - const { qrCodeImage, imageUrl, zipCode, ...payload } = branch; + async function fetchImageListById( + id: string, + flow?: { + sessionId?: string; + refTransactionId?: string; + transactionId?: string; + }, + ) { + const res = await api.get(`/branch/${id}/image`, { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, + }, + }); + + if (!res) return false; + if (res.status === 200) return res.data; + if (res.status === 204) return null; + + return false; + } + + async function addImageList(file: File, branchId: string, name: string) { + await api + .put(`/branch/${branchId}/image/${name}`, file, { + headers: { 'Content-Type': file.type }, + onUploadProgress: (e) => console.log(e), + }) + .catch((e) => console.error(e)); + + return name; + } + + async function deleteImageByName( + id: string, + name: string, + flow?: { + sessionId?: string; + refTransactionId?: string; + transactionId?: string; + }, + ) { + const res = await api.delete(`/branch/${id}/image/${name}`, { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, + }, + }); + + if (!res) return false; + } + + async function create( + branch: BranchCreate, + bank?: BankBook[], + imgList?: { + selectedImage: string; + list: { url: string; imgFile: File | null; name: string }[]; + }, + ) { + const { qrCodeImage, zipCode, ...payload } = branch; const bankPayload = bank?.map(({ bankUrl, bankQr, ...rest }) => ({ ...rest, })); const res = await api.post( '/branch', - { ...payload, bank: bankPayload }, + { + ...payload, + selectedImage: (imgList && imgList.selectedImage) || '', + bank: bankPayload, + }, { headers: { 'X-Rtid': flowStore.rtid } }, ); @@ -108,15 +173,6 @@ const useBranchStore = defineStore('api-branch', () => { .catch((e) => console.error(e)); } - if (imageUrl) { - await api - .put(`/branch/${res.data.id}/branch-image`, imageUrl, { - headers: { 'Content-Type': imageUrl.type }, - onUploadProgress: (e) => console.log(e), - }) - .catch((e) => console.error(e)); - } - if (res.data.bank && bank) { for (let i = 0; i < bank?.length; i++) { if (bank[i].bankQr) { @@ -134,6 +190,14 @@ const useBranchStore = defineStore('api-branch', () => { } } + if (imgList && imgList.list.length > 0 && res.data.id) { + for (let index = 0; index < imgList.list.length; index++) { + const imgFile = imgList.list[index].imgFile; + if (imgFile) + await addImageList(imgFile, res.data.id, imgList.list[index].name); + } + } + if (!res) return false; return res.data; @@ -143,7 +207,6 @@ const useBranchStore = defineStore('api-branch', () => { id: string, data: Partial, qrCodeImage?: File | undefined, - imageHq?: File | undefined, bank?: BankBook[], opts?: { deleteQrCodeImage?: boolean; @@ -178,15 +241,6 @@ const useBranchStore = defineStore('api-branch', () => { .catch((e) => console.error(e)); } - if (imageHq) { - await api - .put(`/branch/${res.data.id}/branch-image`, imageHq, { - headers: { 'Content-Type': imageHq.type }, - onUploadProgress: (e) => console.log(e), - }) - .catch((e) => console.error(e)); - } - if (!!res.data.bank && !!bank) { for (let i = 0; i < bank?.length; i++) { if (bank?.[i].bankQr) { @@ -339,6 +393,10 @@ const useBranchStore = defineStore('api-branch', () => { fetchList, fetchById, + fetchImageListById, + addImageList, + deleteImageByName, + create, editById, deleteById, diff --git a/src/stores/branch/types.ts b/src/stores/branch/types.ts index 0baf4bef..4e264a92 100644 --- a/src/stores/branch/types.ts +++ b/src/stores/branch/types.ts @@ -16,6 +16,7 @@ export type BankBook = { }; export type Branch = { + selectedImage?: string; subDistrict: SubDistrict | null; district: District | null; province: Province | null; @@ -30,7 +31,6 @@ export type Branch = { latitude: string; contactName: string; qrCodeImageUrl: string; - imageUrl: string; email: string; zipCode: string; subDistrictId: string | null; @@ -57,6 +57,7 @@ export type Branch = { export type BranchWithChildren = Branch & { branch: Branch[] }; export type BranchCreate = { + selectedImage?: string; code?: string; taxNo: string; nameEN: string; @@ -76,7 +77,6 @@ export type BranchCreate = { contactName: string; contact: string; qrCodeImage?: File; - imageUrl?: File; lineId: string; webUrl?: string; virtual: boolean; diff --git a/src/stores/customer/index.ts b/src/stores/customer/index.ts index 939460ed..4b5c0787 100644 --- a/src/stores/customer/index.ts +++ b/src/stores/customer/index.ts @@ -256,7 +256,7 @@ const useCustomerStore = defineStore('api-customer', () => { fetch(res.data) .then(async (res) => await res.blob()) .then((blob) => { - let a = document.createElement('a'); + const a = document.createElement('a'); a.download = filename; a.href = window.URL.createObjectURL(blob); a.click(); @@ -267,8 +267,66 @@ const useCustomerStore = defineStore('api-customer', () => { return res.data; } + async function fetchImageListById( + id: string, + flow?: { + sessionId?: string; + refTransactionId?: string; + transactionId?: string; + }, + ) { + const res = await api.get(`/customer/${id}/image`, { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, + }, + }); + + if (!res) return false; + if (res.status === 200) return res.data; + if (res.status === 204) return null; + + return false; + } + + async function addImageList(file: File, customerId: string, name: string) { + await api + .put(`/customer/${customerId}/image/${name}`, file, { + headers: { 'Content-Type': file.type }, + onUploadProgress: (e) => console.log(e), + }) + .catch((e) => console.error(e)); + + return name; + } + + async function deleteImageByName( + id: string, + name: string, + flow?: { + sessionId?: string; + refTransactionId?: string; + transactionId?: string; + }, + ) { + const res = await api.delete(`/customer/${id}/image/${name}`, { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, + }, + }); + + if (!res) return false; + } + async function create( data: CustomerCreate, + imgList: { + selectedImage: string; + list: { url: string; imgFile: File | null; name: string }[]; + }, flow?: { sessionId?: string; refTransactionId?: string; @@ -291,13 +349,17 @@ const useCustomerStore = defineStore('api-customer', () => { imageUrl: string; imageUploadUrl: string; } - >('/customer', payload, { - headers: { - 'X-Session-Id': flow?.sessionId, - 'X-Rtid': flow?.refTransactionId || flowStore.rtid, - 'X-Tid': flow?.transactionId, + >( + '/customer', + { ...payload, selectedImage: imgList.selectedImage }, + { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, + }, }, - }); + ); // await Promise.allSettled([ // ...res.data.branch.map(async (v, i) => { @@ -305,15 +367,23 @@ const useCustomerStore = defineStore('api-customer', () => { // if (fileList) // return await addBranchAttachment(v.id, { file: fileList }); // }), - image && - (await api - .put(`/customer/${res.data.id}/image`, image, { - headers: { 'Content-Type': image?.type }, - onUploadProgress: (e) => console.log(e), - }) - .catch((e) => console.error(e))); + // image && + // (await api + // .put(`/customer/${res.data.id}/image`, image, { + // headers: { 'Content-Type': image?.type }, + // onUploadProgress: (e) => console.log(e), + // }) + // .catch((e) => console.error(e))); // ]); + if (imgList.list.length > 0 && res.data.id) { + for (let index = 0; index < imgList.list.length; index++) { + const imgFile = imgList.list[index].imgFile; + if (imgFile) + await addImageList(imgFile, res.data.id, imgList.list[index].name); + } + } + if (!res) return false; return res.data; @@ -362,13 +432,13 @@ const useCustomerStore = defineStore('api-customer', () => { // if (fileList) // return await addBranchAttachment(v.id, { file: fileList }); // }), - image && - (await axios - .put(res.data.imageUploadUrl, image, { - headers: { 'Content-Type': image.type }, - onUploadProgress: (e) => console.log(e), - }) - .catch((e) => console.error(e))); + // image && + // (await axios + // .put(res.data.imageUploadUrl, image, { + // headers: { 'Content-Type': image.type }, + // onUploadProgress: (e) => console.log(e), + // }) + // .catch((e) => console.error(e))); // ]); if (!res) return false; @@ -563,6 +633,11 @@ const useCustomerStore = defineStore('api-customer', () => { fetchListCustomeBranch, fetchById, fetchList, + + fetchImageListById, + addImageList, + deleteImageByName, + create, editById, deleteById, diff --git a/src/stores/customer/types.ts b/src/stores/customer/types.ts index ef01311c..8eb6de6d 100644 --- a/src/stores/customer/types.ts +++ b/src/stores/customer/types.ts @@ -150,6 +150,7 @@ export type CustomerBranchCreate = { }; export type CustomerCreate = { + selectedImage?: string; code: string; customerBranch?: (CustomerBranchCreate & { id?: string })[]; customerType: CustomerType; @@ -166,6 +167,7 @@ export type CustomerCreate = { }; export type CustomerUpdate = { + selectedImage?: string; status?: Status; customerType?: CustomerType; customerBranch?: (CustomerBranchCreate & { id?: string })[]; diff --git a/src/stores/user/index.ts b/src/stores/user/index.ts index 94fb05e4..63fd4664 100644 --- a/src/stores/user/index.ts +++ b/src/stores/user/index.ts @@ -2,6 +2,7 @@ import { ref } from 'vue'; import { defineStore } from 'pinia'; import { Pagination, Status } from '../types'; import { api } from 'src/boot/axios'; + import { RoleData, User, @@ -266,18 +267,83 @@ const useUserStore = defineStore('api-user', () => { return false; } - async function create( - data: UserCreate, + async function fetchImageListById( + id: string, flow?: { sessionId?: string; refTransactionId?: string; transactionId?: string; }, ) { +<<<<<<< HEAD const { profileImage, zipCode, ...payload } = data; const res = await api.post( +======= + const res = await api.get(`/user/${id}/profile-image`, { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, + }, + }); + + if (!res) return false; + if (res.status === 200) return res.data; + if (res.status === 204) return null; + + return false; + } + + async function addImageList(file: File, userId: string, name: string) { + await api + .put(`/user/${userId}/profile-image/${name}`, file, { + headers: { 'Content-Type': file.type }, + onUploadProgress: (e) => console.log(e), + }) + .catch((e) => console.error(e)); + + return name; + } + + async function deleteImageByName( + id: string, + name: string, + flow?: { + sessionId?: string; + refTransactionId?: string; + transactionId?: string; + }, + ) { + const res = await api.delete(`/user/${id}/profile-image/${name}`, { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, + }, + }); + + if (!res) return false; + } + + async function create( + data: UserCreate, + imgList: { + selectedImage: string; + list: { url: string; imgFile: File | null; name: string }[]; + }, + flow?: { + sessionId?: string; + refTransactionId?: string; + transactionId?: string; + }, + ) { + const { zipCode, ...payload } = data; + const res = await api.post( '/user', - payload, + { + ...payload, + selectedImage: imgList.selectedImage || '', + }, { headers: { 'X-Session-Id': flow?.sessionId, @@ -287,13 +353,12 @@ const useUserStore = defineStore('api-user', () => { }, ); - if (profileImage && res.data.id) { - await api - .put(`/user/${res.data.id}/image`, profileImage, { - headers: { 'Content-Type': profileImage.type }, - onUploadProgress: (e) => console.log(e), - }) - .catch((e) => console.error(e)); + if (imgList.list.length > 0 && res.data.id) { + for (let index = 0; index < imgList.list.length; index++) { + const imgFile = imgList.list[index].imgFile; + if (imgFile) + await addImageList(imgFile, res.data.id, imgList.list[index].name); + } } if (!res) return false; @@ -310,26 +375,13 @@ const useUserStore = defineStore('api-user', () => { transactionId?: string; }, ) { - const { profileImage, ...payload } = data; - const res = await api.put( - `/user/${id}`, - payload, - { - headers: { - 'X-Session-Id': flow?.sessionId, - 'X-Rtid': flow?.refTransactionId || flowStore.rtid, - 'X-Tid': flow?.transactionId, - }, + const res = await api.put(`/user/${id}`, data, { + headers: { + 'X-Session-Id': flow?.sessionId, + 'X-Rtid': flow?.refTransactionId || flowStore.rtid, + 'X-Tid': flow?.transactionId, }, - ); - - if (profileImage) - await axios - .put(res.data.profileImageUploadUrl, profileImage, { - headers: { 'Content-Type': profileImage.type }, - onUploadProgress: (e) => console.log(e), - }) - .catch((e) => console.error(e)); + }); if (!res) return false; @@ -460,6 +512,10 @@ const useUserStore = defineStore('api-user', () => { fetchList, fetchById, + fetchImageListById, + addImageList, + deleteImageByName, + create, editById, deleteById, diff --git a/src/stores/user/types.ts b/src/stores/user/types.ts index 51cb47da..03291648 100644 --- a/src/stores/user/types.ts +++ b/src/stores/user/types.ts @@ -3,6 +3,7 @@ import { Branch } from '../branch/types'; import { Status } from '../types'; export type User = { + selectedImage?: string; subDistrict: SubDistrict | null; district: District | null; province: Province | null; @@ -41,7 +42,6 @@ export type User = { firstName: string; namePrefix?: string | null; id: string; - profileImageUrl: string; code: string; birthDate?: Date | null; responsibleArea: string; @@ -51,6 +51,7 @@ export type User = { }; export type UserCreate = { + selectedImage?: string; branchId: string; provinceId?: string | null; districtId?: string | null; @@ -82,7 +83,6 @@ export type UserCreate = { userType: string; username: string; status?: Status; - profileImage?: File | null; // required but not strict birthDate?: Date | null; responsibleArea?: string | null; checkpoint?: string | null;