feat: add basic form customer

This commit is contained in:
Methapon2001 2024-08-05 15:09:36 +07:00
parent 0a745885ee
commit 6bb4256dec
8 changed files with 408 additions and 80 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

View file

@ -25,6 +25,7 @@ defineProps<{
editData?: (...args: unknown[]) => void;
deleteData?: (...args: unknown[]) => void;
show?: (...args: unknown[]) => void;
submit?: (...args: unknown[]) => void;
close?: (...args: unknown[]) => void;
undo?: (...args: unknown[]) => void;
@ -34,7 +35,7 @@ const modal = defineModel('modal', { default: false });
const currentTab = defineModel<string>('currentTab');
</script>
<template>
<q-dialog v-model="modal" @hide="close">
<q-dialog v-model="modal" @hide="close" @before-show="show">
<div
class="surface-1"
style="padding: 0; border-radius: var(--radius-2); height: 100%"
@ -199,7 +200,7 @@ const currentTab = defineModel<string>('currentTab');
<q-btn
id="cancelBtn"
unelevated
class="col btn-cancel-dialog"
class="col btn-cancel-dialog rounded"
color="grey-4"
text-color="grey-10"
@click="close"
@ -212,7 +213,7 @@ const currentTab = defineModel<string>('currentTab');
id="submitBtn"
type="submit"
color="primary"
class="q-px-md"
class="q-px-md rounded"
:label="$t('save')"
/>
</div>

View file

@ -12,6 +12,7 @@ import otherDocument from './other-document';
import productService from './product-service';
import alertDialog from './alert-dialog';
import bank from './bank';
export default {
ok: 'Confirm',
agree: 'Ok',
@ -87,4 +88,28 @@ export default {
...productService,
...alertDialog,
...bank,
form: {
title: {
create: 'Create {name}',
edit: 'Edit {name}',
},
error: {
required: 'This field is required.',
invalid: 'Invalid value.',
invalidCustomeMessage: 'Invalid value. {msg}',
},
},
customer: {
form: {
group: {
basicInfo: 'Basic Information',
},
registeredBranch: 'Registered Branch',
customerName: 'Company Name',
customerNameEN: 'Company Name (EN)',
personName: 'Customer Name',
taxIdentificationNumber: 'Tax Identification Number',
},
},
};

View file

@ -2,6 +2,7 @@
import { ref, watch, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useRoute, useRouter } from 'vue-router';
import { getUserId, getRole } from 'src/services/keycloak';
import { calculateAge, dateFormat } from 'src/utils/datetime';
@ -22,13 +23,15 @@ import TooltipComponent from 'components/TooltipComponent.vue';
import AddButton from 'components/AddButton.vue';
import NoData from 'components/NoData.vue';
import PaginationComponent from 'components/PaginationComponent.vue';
import SideMenu from 'components/SideMenu.vue';
import DialogForm from 'components/DialogForm.vue';
import CustomerInfoComponent from './components/CustomerBranch.vue';
import FormBasicInfo from './components/FormBasicInfo.vue';
import { columnsCustomer, columnsEmployee } from './constant';
import { useRoute, useRouter } from 'vue-router';
import { useCustomerForm } from './form';
import { storeToRefs } from 'pinia';
import ProfileBanner from 'src/components/ProfileBanner.vue';
import ImageUploadDialog from 'src/components/ImageUploadDialog.vue';
const { t, locale } = useI18n();
const $q = useQuasar();
@ -41,7 +44,8 @@ const userBranchStore = useMyBranchStore();
const employeeStore = useEmployeeStore();
const customerFormStore = useCustomerForm();
const { state: customerFormState } = storeToRefs(customerFormStore);
const { state: customerFormState, currentFormData: customerFormData } =
storeToRefs(customerFormStore);
async function init() {
utilsStore.currentTitle.title = 'customerManagement';
@ -105,6 +109,7 @@ const maxPageCustomer = ref<number>(1);
const currentPageEmployee = ref<number>(1);
const maxPageEmployee = ref<number>(1);
const pageSize = ref<number>(10);
const dialogCustomerImageUpload = ref<InstanceType<typeof ImageUploadDialog>>();
watch(() => route.name, init);
watch(
@ -373,6 +378,12 @@ async function openHistory(id: string) {
employeeHistoryDialog.value = true;
}
async function editCustomerForm(id: string) {
await customerFormStore.assignFormData(id);
customerFormState.value.dialogType = 'edit';
customerFormState.value.dialogModal = true;
}
// TODO: When in employee form, if select address same as customer then auto fill
</script>
@ -383,7 +394,11 @@ async function openHistory(id: string) {
v-if="currentTab === 'employer'"
id="add-customer-legal-entity"
style="color: white; background-color: hsla(var(--violet-11-hsl))"
@click=""
@click="
(customerFormState.dialogModal = true),
(customerFormState.dialogType = 'create'),
(customerFormData.customerType = 'CORP')
"
padding="xs"
icon="mdi-office-building"
:label="$t('add') + ' ' + $t('customerLegalEntity')"
@ -396,7 +411,11 @@ async function openHistory(id: string) {
:label="$t('add') + ' ' + $t('customerNaturalPerson')"
external-label
label-position="left"
@click=""
@click="
(customerFormState.dialogModal = true),
(customerFormState.dialogType = 'create'),
(customerFormData.customerType = 'CORP')
"
style="color: white; background-color: hsla(var(--teal-10-hsl))"
padding="xs"
icon="mdi-account-plus"
@ -870,13 +889,7 @@ async function openHistory(id: string) {
dense
round
flat
@click.stop="
async () => {
await fetchListOfOptionBranch();
const { branch, ...payload } = props.row;
currentCustomer = payload;
}
"
@click.stop="editCustomerForm(props.row.id)"
/>
<q-btn
@ -1623,9 +1636,111 @@ async function openHistory(id: string) {
<DialogForm
v-model:modal="customerFormState.dialogModal"
:title="$t('')"
@submit="customerFormStore.submitForm"
></DialogForm>
:title="$t('form.title.create', { name: 'Employer' })"
:show="
async () =>
await fetchListOfOptionBranch().then(() =>
customerFormStore.resetForm(),
)
"
:submit="
() => {
customerFormStore.submitForm();
}
"
:close="() => (customerFormState.dialogModal = false)"
no-footer
>
<div class="q-mx-lg q-mt-lg">
<ProfileBanner
active
v-model:cover-url="customerFormState.customerImageUrl"
:hide-fade="
customerFormState.customerImageUrl === '' ||
customerFormState.customerImageUrl === null
"
fallback-cover="/images/customer-legal-banner-bg.jpg"
:img="
customerFormState.customerImageUrl ||
'/images/customer-legal-avartar.png'
"
color="hsla(var(--pink-6-hsl)/1)"
bg-color="hsla(var(--pink-6-hsl)/0.15)"
@view="customerFormState.imageDialog = true"
@edit="dialogCustomerImageUpload?.browse()"
/>
</div>
<div
class="col surface-1 q-ma-lg rounded bordered scroll row relative-position"
id="customer-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">
<SideMenu
:menu="[
{
name: $t('customer.form.group.basicInfo'),
anchor: 'form-basic-info-customer',
},
]"
background="transparent"
:active="{
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#customer-form-content"
/>
</div>
</div>
<div
class="col-12 col-md-10 q-pa-md"
id="customer-form-content"
style="height: 100%; max-height: 100%; overflow-y: auto"
>
<FormBasicInfo
id="form-basic-info-customer"
@save="customerFormState.saveMode = 'customer'"
:customer-type="customerFormData.customerType"
v-model:tax-no="customerFormData.taxNo"
v-model:customer-name="customerFormData.customerName"
v-model:customer-name-en="customerFormData.customerNameEN"
v-model:person-name="customerFormData.personName"
v-model:registered-branch-id="customerFormData.registeredBranchId"
v-model:branch-options="registerAbleBranchOption"
/>
</div>
</div>
<!-- <q-btn -->
<!-- @click="customerFormState.saveMode = 'customer'" -->
<!-- type="submit" -->
<!-- label="Save" -->
<!-- /> -->
<!-- <q-btn -->
<!-- @click="customerFormState.saveMode = 'branch'" -->
<!-- type="submit" -->
<!-- label="Save" -->
<!-- /> -->
</DialogForm>
<ImageUploadDialog
ref="dialogCustomerImageUpload"
v-model:dialog-state="customerFormState.imageDialog"
v-model:file="customerFormData.image"
v-model:image-url="customerFormState.customerImageUrl"
:hidden-footer="
!customerFormData.image ||
!customerFormState.customerImageUrl ||
customerFormState.dialogType === 'edit'
"
fallback-url="/images/customer-legal-avartar.png"
clear-button
@save="() => {}"
></ImageUploadDialog>
</template>
<style scoped>

View file

@ -0,0 +1,169 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { QSelect } from 'quasar';
import { selectFilterOptionRefMod } from 'stores/utils';
import { getRole } from 'src/services/keycloak';
import { onMounted } from 'vue';
defineProps<{
prefixId?: string;
outlined?: boolean;
readonly?: boolean;
saveEnabled?: boolean;
customerType?: 'CORP' | 'PERS';
}>();
const personName = defineModel<string>('personName', { required: true });
const customerName = defineModel<string>('customerName', { required: true });
const customerNameEN = defineModel<string>('customerNameEn', {
required: true,
});
const taxNo = defineModel<string | null>('taxNo');
const registeredBranchId = defineModel<string>('registeredBranchId', {
required: true,
});
const branchOptions = defineModel<{ id: string; name: string }[]>(
'branchOptions',
{ default: [] },
);
const filteredBranchOptions = ref<Record<string, unknown>[]>([]);
let branchFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
onMounted(() => {
branchFilter = selectFilterOptionRefMod(
branchOptions,
filteredBranchOptions,
'name',
);
});
watch(
() => branchOptions.value,
() => {
branchFilter = selectFilterOptionRefMod(
branchOptions,
filteredBranchOptions,
'name',
);
},
);
</script>
<template>
<div class="row q-col-gutter-md">
<div class="col-12 text-weight-bold text-body1 row items-center">
<q-icon
flat
size="xs"
class="q-pa-sm rounded q-mr-xs"
color="info"
name="mdi-office-building-outline"
style="background-color: var(--surface-3)"
/>
<span>{{ $t('customer.form.group.basicInfo') }}</span>
<q-btn
type="submit"
dense
unelevated
color="primary"
:label="$t('save')"
@click="$emit('save')"
class="q-px-md q-ml-auto rounded"
/>
</div>
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
dense
class="col-12 col-md-4"
option-value="id"
input-debounce="0"
option-label="name"
lazy-rules="ondemand"
v-model="registeredBranchId"
:readonly="readonly"
:options="filteredBranchOptions"
:hide-dropdown-icon="readonly"
:label="$t('registeredBranch')"
:for="`${prefixId}-input-source-nationality`"
:rules="[
(val) => {
const roles = getRole() || [];
return (
['admin', 'system', 'head_of_admin'].some((v) =>
roles.includes(v),
) ||
!!val ||
$t('form.error.required')
);
},
]"
@filter="branchFilter"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('noResults') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-input
lazy-rules="ondemand"
dense
outlined
hide-bottom-space
:label="$t('customer.form.customerName')"
v-model="customerName"
class="col-12 col-md-4"
:readonly="readonly"
:for="`${prefixId}-input-company-name`"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
:label="$t('customer.form.customerNameEN')"
v-model="customerNameEN"
:for="`${prefixId}-input-company-name-en`"
class="col-12 col-md-4"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
v-model="personName"
:label="$t('customer.form.personName')"
:for="`${prefixId}-input-person-name`"
:rules="[(v: string) => !!v || $t('form.error.required')]"
class="col-12 col-md-4"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
v-if="customerType === 'PERS'"
:readonly="readonly"
hide-bottom-space
:label="$t('customer.form.taxIdentificationNumber')"
v-model="taxNo"
:for="`${prefixId}-input-tax-no`"
class="col-12 col-md-4"
/>
</div>
</template>

View file

@ -16,40 +16,10 @@ export const useCustomerForm = defineStore('form-customer', () => {
customerNameEN: '',
taxNo: '',
registeredBranchId: branchStore.currentMyBranch?.id || '',
customerBranch: [
// {
// code: '',
// branchNo: 1,
// address: '',
// addressEN: '',
// provinceId: '',
// districtId: '',
// subDistrictId: '',
// zipCode: '',
// email: '',
// telephoneNo: '',
// name: '',
// status: 'CREATED',
// taxNo: '',
// nameEN: '',
// legalPersonNo: '',
// registerName: '',
// registerDate: new Date(),
// authorizedCapital: '',
// employmentOffice: '',
// bussinessType: '',
// bussinessTypeEN: '',
// jobPosition: '',
// jobPositionEN: '',
// jobDescription: '',
// saleEmployee: '',
// payDate: new Date(),
// wageRate: 0,
// },
],
customerBranch: [],
image: null,
};
let resetCustomerData = structuredClone(defaultFormData);
let resetFormData = structuredClone(defaultFormData);
const currentFormData = ref<CustomerCreate>(structuredClone(defaultFormData));
const state = ref<{
@ -57,31 +27,37 @@ export const useCustomerForm = defineStore('form-customer', () => {
dialogOpen: boolean;
dialogModal: boolean;
branchIndex: number;
customerType: 'CORP' | 'PERS';
saveMode: 'customer' | 'branch';
customerImageUrl: string;
imageDialog: boolean;
imageEdit: boolean;
editCustomerId?: string;
editCustomerBranchId?: string;
}>({
dialogType: 'info',
dialogOpen: false,
dialogModal: false,
imageDialog: false,
branchIndex: 0,
customerType: 'CORP',
imageEdit: false,
saveMode: 'customer',
customerImageUrl: '',
editCustomerId: '',
editCustomerBranchId: '',
});
function isFormDataDifferent() {
return (
JSON.stringify(resetCustomerData) !==
JSON.stringify(currentFormData.value)
JSON.stringify(resetFormData) !== JSON.stringify(currentFormData.value)
);
}
function resetFormData(cb?: (...args: any[]) => unknown) {
currentFormData.value = structuredClone(resetCustomerData);
cb?.();
function resetForm() {
if (!resetFormData.registeredBranchId) {
resetFormData.registeredBranchId = branchStore.currentMyBranch?.id || '';
}
currentFormData.value = structuredClone(resetFormData);
}
async function assignFormData(id: string) {
@ -89,15 +65,15 @@ export const useCustomerForm = defineStore('form-customer', () => {
if (!data) return;
resetCustomerData.registeredBranchId = data.registeredBranchId;
resetCustomerData.status = data.status;
resetCustomerData.customerType = data.customerType;
resetCustomerData.customerName = data.customerName;
resetCustomerData.customerNameEN = data.customerNameEN;
resetCustomerData.personName = data.personName;
resetCustomerData.taxNo = data.taxNo;
resetCustomerData.image = null;
resetCustomerData.customerBranch = data.branch.map((v) => ({
resetFormData.registeredBranchId = data.registeredBranchId;
resetFormData.status = data.status;
resetFormData.customerType = data.customerType;
resetFormData.customerName = data.customerName;
resetFormData.customerNameEN = data.customerNameEN;
resetFormData.personName = data.personName;
resetFormData.taxNo = data.taxNo;
resetFormData.image = null;
resetFormData.customerBranch = data.branch.map((v) => ({
id: v.id,
code: v.code,
branchNo: v.branchNo,
@ -128,33 +104,75 @@ export const useCustomerForm = defineStore('form-customer', () => {
wageRate: v.wageRate,
}));
currentFormData.value = structuredClone(resetCustomerData);
currentFormData.value = structuredClone(resetFormData);
}
function addCurrentCustomerBranch() {
currentFormData.value.customerBranch?.push({
id: '',
code: '',
branchNo: 1,
address: '',
addressEN: '',
provinceId: '',
districtId: '',
subDistrictId: '',
zipCode: '',
email: '',
telephoneNo: '',
name: '',
status: 'CREATED',
taxNo: '',
nameEN: '',
legalPersonNo: '',
registerName: '',
registerDate: new Date(),
authorizedCapital: '',
employmentOffice: '',
bussinessType: '',
bussinessTypeEN: '',
jobPosition: '',
jobPositionEN: '',
jobDescription: '',
saleEmployee: '',
payDate: new Date(),
wageRate: 0,
});
}
async function submitForm() {
console.log(state.value.saveMode);
if (state.value.dialogType === 'edit' && !state.value.editCustomerId) {
throw new Error('Form mode is set to edit but no ID is provided.');
}
if (state.value.dialogType === 'edit') {
return submitEdit();
}
return submitCreate();
if (state.value.saveMode === 'customer') await submitFormCustomer();
}
async function submitCreate() {
// customerStore.create(currentFormData.value);
}
async function submitFormCustomer() {
if (state.value.dialogType === 'info') return;
async function submitEdit() {}
if (state.value.dialogType === 'create') {
return await customerStore.create(currentFormData.value);
}
if (!state.value.editCustomerId) {
throw new Error(
'Form mode is set to edit but no ID is provided. Make sure to set customer ID.',
);
}
await customerStore.editById(state.value.editCustomerId, {
...currentFormData.value,
image: currentFormData.value.image || undefined,
customerBranch: undefined,
});
}
return {
state,
resetFormData,
currentFormData,
isFormDataDifferent,
resetFormData,
resetForm,
assignFormData,
submitForm,
addCurrentCustomerBranch,
};
});