From 79240f53b039f5e37bde3cf0d66dfd0b92bb9458 Mon Sep 17 00:00:00 2001 From: Methapon Metanipat <162551568+Methapon-Frappet@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:04:08 +0700 Subject: [PATCH 01/61] feat: debit note (#172) * feat: new file * feat: function api debit * feat: add route debit * feat: new form page * refactor: show menu debit * refactor: add type debit note status * feat: add i18n * feat: add constants * feat: add stores * feat: layout * feat: add function * refactor: change name value * feat: form select quotation * refactor: change name url * refactor: use form debit * refactor: change src import * refactor: move file form debit * refactor: add i18n * feat: add type debit note * refactor: add columns * refactor: bind value columns * refactor: change name Table * refactor: edit type * refactor: bind type debit note * refactor: bind value debit * refactor: chame name function * fix: calculate page * refactor: delete table * refactor: change name get list * refactor: change i18n * refactor: change name value * refactor: bind navigate and trigger delete * refactor: format number deciml * refactor: add i18n * feat: new page * refactor: add color debit * feat: Debit tab #178 * feat: TableRequest * refactor: edit type pay condition * refactor: add i18n btn submit * refactor: use type enum * feat: edit layout product expansion * refactor: bind function * refactor: show code * feat: add input search and select status * feat: paymentform * refactor: edit type * refactor: add manage file and edit end point * feat: add form.ts * refactor: send mode * refactor: edit v-model of due date * feat: submit create debit * fix: status * refactor: handle data not allow * fix: call updateDebitNote in edit mode and simplify payload handling * refactor: hide edit * refactor: handle pay condition only full * refactor: delete pay split * refactor: add query * refactor: handle is debit note * refactor: handle is quotation * refactor: add props hide * refactor: tap payment and receipt * refactor: add i18n * feat: view document * refactor: handle btn view doc * refactor: use my remark --------- Co-authored-by: Thanaphon Frappet Co-authored-by: nwpptrs Co-authored-by: aif912752 --- src/components/12_debit-note/FormDebit.vue | 39 + src/i18n/eng.ts | 51 +- src/i18n/tha.ts | 49 + src/layouts/DrawerComponent.vue | 2 +- src/pages/05_quotation/PaymentForm.vue | 13 +- src/pages/05_quotation/QuotationForm.vue | 4 +- src/pages/05_quotation/QuotationFormInfo.vue | 6 + .../05_quotation/QuotationFormReceipt.vue | 42 +- src/pages/05_quotation/TableRequest.vue | 3 +- src/pages/12_debit-note/FormPage.vue | 1322 +++++++++++++++++ src/pages/12_debit-note/MainPage.vue | 499 +++++++ src/pages/12_debit-note/TableDebitNote.vue | 95 ++ src/pages/12_debit-note/constants.ts | 90 ++ .../document-view/BankComponents.vue | 113 ++ .../12_debit-note/document-view/MainPage.vue | 654 ++++++++ .../document-view/ViewFooter.vue | 98 ++ .../document-view/ViewHeader.vue | 192 +++ .../12_debit-note/document-view/ViewPdf.vue | 27 + .../expansion/DebitNoteExpansion.vue | 51 + .../expansion/DocumentExpansion.vue | 120 ++ .../expansion/PaymentExpansion.vue | 116 ++ .../expansion/ProductExpansion.vue | 105 ++ .../expansion/RemarkExpansion.vue | 67 + .../expansion/WorkerItemExpansion.vue | 78 + src/pages/12_debit-note/form.ts | 64 + src/router/routes.ts | 21 + src/stores/debit-note/index.ts | 94 ++ src/stores/debit-note/types.ts | 94 ++ src/stores/payment/index.ts | 3 + src/stores/quotations/index.ts | 9 +- tests/institution.spec.ts | 44 + tests/utils/index.ts | 19 + 32 files changed, 4172 insertions(+), 12 deletions(-) create mode 100644 src/components/12_debit-note/FormDebit.vue create mode 100644 src/pages/12_debit-note/FormPage.vue create mode 100644 src/pages/12_debit-note/MainPage.vue create mode 100644 src/pages/12_debit-note/TableDebitNote.vue create mode 100644 src/pages/12_debit-note/constants.ts create mode 100644 src/pages/12_debit-note/document-view/BankComponents.vue create mode 100644 src/pages/12_debit-note/document-view/MainPage.vue create mode 100644 src/pages/12_debit-note/document-view/ViewFooter.vue create mode 100644 src/pages/12_debit-note/document-view/ViewHeader.vue create mode 100644 src/pages/12_debit-note/document-view/ViewPdf.vue create mode 100644 src/pages/12_debit-note/expansion/DebitNoteExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/DocumentExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/PaymentExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/ProductExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/RemarkExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/WorkerItemExpansion.vue create mode 100644 src/pages/12_debit-note/form.ts create mode 100644 src/stores/debit-note/index.ts create mode 100644 src/stores/debit-note/types.ts create mode 100644 tests/institution.spec.ts create mode 100644 tests/utils/index.ts diff --git a/src/components/12_debit-note/FormDebit.vue b/src/components/12_debit-note/FormDebit.vue new file mode 100644 index 00000000..1aa8e421 --- /dev/null +++ b/src/components/12_debit-note/FormDebit.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/i18n/eng.ts b/src/i18n/eng.ts index 6c06fd9f..fdef5fe2 100644 --- a/src/i18n/eng.ts +++ b/src/i18n/eng.ts @@ -1125,6 +1125,8 @@ export default { }, preview: { + dateAt: 'Date {msg}', + seller: 'Seller', taskOrder: 'Work Order', doc: 'View Document', productList: 'Product List', @@ -1135,15 +1137,17 @@ export default { vat: 'VAT', value: 'Value', netValue: 'Net Value', + dueDate: 'Due Date', + paymentMethods: 'Payment Methods', title: { creditNote: 'Credit Note', quotation: 'Quotation', invoice: 'Invoice', payment: 'Payment', receipt: 'Receipt', + debitNote: 'Debit Note', }, }, - address: { subDistrict: 'Sub District', subArea: 'Sub Area', @@ -1225,4 +1229,49 @@ export default { dataSum: 'Summary of all receipts/tax invoices', workSheetName: 'Worksheet name', }, + + debitNote: { + title: 'Debit Note', + caption: 'All Debit Notes', + expire: 'Expired', + payment: 'Payment', + receipt: 'Receipt', + succeed: 'Completed', + downloadReceipt: 'Download Receipt', + downloadTaxInvoice: 'Download Tax Invoice', + + label: { + additionalDetail: 'Additional Details', + specifyReasonForDebit: 'Specify Reason for Debit', + debitNoteInformation: 'Debit Note Information', + codeDebit: 'Debit Note Number', + codeQuotation: 'Quotation Number', + quotationWorkName: 'Work Name', + quotationPayment: 'Payment Method', + value: 'Net Value', + submit: 'Approve Debit Note', + }, + + stats: { + Pending: 'Debit Note', + Expire: 'Expired', + Payment: 'Payment', + Receipt: 'Receipt', + Succeed: 'Completed', + }, + + viewMode: { + payment: 'Payment', + receipt: 'Receipt/Tax Invoice', + processComplete: 'Completed', + }, + + status: { + Pending: 'Debit Note', + Expire: 'Expired', + Payment: 'Payment', + Receipt: 'Receipt', + Succeed: 'Completed', + }, + }, }; diff --git a/src/i18n/tha.ts b/src/i18n/tha.ts index 4654979d..b610bcb5 100644 --- a/src/i18n/tha.ts +++ b/src/i18n/tha.ts @@ -1106,6 +1106,8 @@ export default { }, preview: { + dateAt: 'วันที่{msg}', + seller: 'ผู้ขาย', taskOrder: 'ใบสั่งงาน', doc: 'ดูเอกสาร', productList: 'รายการสินค้า', @@ -1116,12 +1118,15 @@ export default { vat: 'ภาษี', value: 'มูลค่า', netValue: 'มูลค่าสุทธิ', + dueDate: 'วันครบกำหนดชำระ', + paymentMethods: 'ช่องทางชำระเงิน', title: { creditNote: 'ใบลดหนี้', quotation: 'ใบเสนอราคา', invoice: 'ใบแจ้งหนี้', payment: 'ชำระหนี้', receipt: 'ใบเสร็จรับเงิน', + debitNote: 'ใบเพิ่มหนี้', }, }, @@ -1206,4 +1211,48 @@ export default { dataSum: 'สรุปใบเสร็จรับเงิน/กำกับภาษีทั้งหมด', workSheetName: 'ชื่อใบงาน', }, + debitNote: { + title: 'ใบเพิ่มหนี้', + caption: 'ใบเพิ่มหนี้ทั้งหมด', + expire: 'พ้นกำหนด', + payment: 'ชำระเงิน', + receipt: 'ใบเสร็จรับเงิน', + succeed: 'เสร็จสิ้น', + downloadReceipt: 'ดาวน์โหลดใบเสร็จรับเงิน', + downloadTaxInvoice: 'ดาวน์โหลดใบกำกับภาษี', + + label: { + additionalDetail: 'อธิบายเพิ่มเติม', + specifyReasonForDebit: 'ระบุสาเหตุการเพิ่มหนี้', + debitNoteInformation: 'ข้อมูลการเพิ่มหนี้', + codeDebit: 'เลขที่ใบเพิ่มหนี้', + codeQuotation: 'เลขที่ใบเสนอราคา', + quotationWorkName: 'ชื่อใบงาน', + quotationPayment: 'วิธีการชำระ', + value: 'มูลค่าสุทธิ', + submit: 'อนุมัติใบเพิ่มหนี้', + }, + + stats: { + Pending: 'ใบเพิ่มหนี้', + Expire: 'พ้นกำหนด', + Payment: 'ชำระเงิน', + Receipt: 'ใบเสร็จรับเงิน', + Succeed: 'เสร็จสิ้น', + }, + + viewMode: { + payment: 'ชำระเงิน', + receipt: 'ใบเสร็จรับเงิน/ใบกำกับภาษี', + processComplete: 'เสร็จสิ้น', + }, + + status: { + Pending: 'ใบเพิ่มหนี้', + Expire: 'พ้นกำหนด', + Payment: 'ชำระเงิน', + Receipt: 'ใบเสร็จรับเงิน', + Succeed: 'เสร็จสิ้น', + }, + }, }; diff --git a/src/layouts/DrawerComponent.vue b/src/layouts/DrawerComponent.vue index bcdd4cc7..a5012b7d 100644 --- a/src/layouts/DrawerComponent.vue +++ b/src/layouts/DrawerComponent.vue @@ -159,7 +159,7 @@ onMounted(async () => { children: [ { label: 'receipt', route: '/receipt' }, { label: 'creditNote', route: '/credit-note', disabled: true }, - { label: 'debitNote', route: '', disabled: true }, + { label: 'debitNote', route: '/debit-note' }, ], }, diff --git a/src/pages/05_quotation/PaymentForm.vue b/src/pages/05_quotation/PaymentForm.vue index 4dd6cdd3..9258678c 100644 --- a/src/pages/05_quotation/PaymentForm.vue +++ b/src/pages/05_quotation/PaymentForm.vue @@ -21,12 +21,16 @@ import { dateFormatJS } from 'src/utils/datetime'; import { QFile, QMenu } from 'quasar'; import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue'; import { onMounted } from 'vue'; +import { DebitNote } from 'src/stores/debit-note'; const configStore = useConfigStore(); const quotationPayment = useQuotationPayment(); const { data: config } = storeToRefs(configStore); -const prop = defineProps<{ data?: Quotation | QuotationFull }>(); +const prop = defineProps<{ + data?: Quotation | QuotationFull | DebitNote; + isDebitNote?: boolean; +}>(); const refQFile = ref[]>([]); const refQMenu = ref[]>([]); @@ -194,7 +198,12 @@ async function triggerSubmit() { onMounted(async () => { if (!prop.data) return; - const ret = await quotationPayment.getQuotationPayment(prop.data.id); + const ret = await quotationPayment.getQuotationPayment({ + quotationId: prop.isDebitNote === true ? undefined : prop.data.id, + debitNoteId: prop.isDebitNote === true ? prop.data.id : undefined, + quotationOnly: !!prop.isDebitNote ? false : true, + debitNoteOnly: !!prop.isDebitNote ? true : false, + }); if (ret) { paymentData.value = ret.result; slipFile.value = paymentData.value.map((v) => ({ diff --git a/src/pages/05_quotation/QuotationForm.vue b/src/pages/05_quotation/QuotationForm.vue index 5a180905..6af0ee0b 100644 --- a/src/pages/05_quotation/QuotationForm.vue +++ b/src/pages/05_quotation/QuotationForm.vue @@ -443,6 +443,8 @@ async function fetchQuotation() { async function fetchReceipt() { const res = await useReceiptStore.getReceiptList({ quotationId: quotationFormData.value.id, + quotationOnly: true, + debitNoteOnly: false, }); if (res) { @@ -856,7 +858,7 @@ function convertToTable(nodes: Node[]) { } else { quotationFormData.value.paySplit = []; quotationFormData.value.paySplitCount = 0; - quotationFormData.value.payCondition = 'Full'; + quotationFormData.value.payCondition = PayCondition.Full; } tempPaySplit.value = JSON.parse( diff --git a/src/pages/05_quotation/QuotationFormInfo.vue b/src/pages/05_quotation/QuotationFormInfo.vue index d0ae7e2e..08c7266e 100644 --- a/src/pages/05_quotation/QuotationFormInfo.vue +++ b/src/pages/05_quotation/QuotationFormInfo.vue @@ -36,6 +36,7 @@ defineProps<{ }; taskOrder?: boolean; taskOrderComplete?: boolean; + debitNote?: boolean; }>(); const { t } = useI18n(); @@ -216,6 +217,7 @@ watch( 'invoice-color': view === View.Invoice, 'receipt-color': view === View.Receipt, 'task-order-color': taskOrder && !taskOrderComplete, + 'debit-note-color': debitNote, }" >
@@ -530,6 +532,10 @@ watch( --_color: var(--pink-7-hsl); } +.debit-note-color { + --_color: var(--cyan-7-hsl); +} + .bg-color { color: white; background: hsla(var(--_color)); diff --git a/src/pages/05_quotation/QuotationFormReceipt.vue b/src/pages/05_quotation/QuotationFormReceipt.vue index 093661f6..e02d847e 100644 --- a/src/pages/05_quotation/QuotationFormReceipt.vue +++ b/src/pages/05_quotation/QuotationFormReceipt.vue @@ -1,6 +1,7 @@ + + + + diff --git a/src/pages/12_debit-note/MainPage.vue b/src/pages/12_debit-note/MainPage.vue new file mode 100644 index 00000000..2143ba59 --- /dev/null +++ b/src/pages/12_debit-note/MainPage.vue @@ -0,0 +1,499 @@ + + + diff --git a/src/pages/12_debit-note/TableDebitNote.vue b/src/pages/12_debit-note/TableDebitNote.vue new file mode 100644 index 00000000..77d26d18 --- /dev/null +++ b/src/pages/12_debit-note/TableDebitNote.vue @@ -0,0 +1,95 @@ + + diff --git a/src/pages/12_debit-note/constants.ts b/src/pages/12_debit-note/constants.ts new file mode 100644 index 00000000..af2560cd --- /dev/null +++ b/src/pages/12_debit-note/constants.ts @@ -0,0 +1,90 @@ +import { QTableProps } from 'quasar'; +import { DebitNoteStatus, DebitNote } from 'src/stores/debit-note'; +import { formatNumberDecimal } from 'src/stores/utils'; + +export const taskStatusOpts = [ + { + status: DebitNoteStatus.Expire, + name: `debitNote.status.${DebitNoteStatus.Expire}`, + }, + { + status: DebitNoteStatus.Payment, + name: `debitNote.status.${DebitNoteStatus.Payment}`, + }, + { + status: DebitNoteStatus.Receipt, + name: `debitNote.status.${DebitNoteStatus.Receipt}`, + }, + { + status: DebitNoteStatus.Succeed, + name: `debitNote.status.${DebitNoteStatus.Succeed}`, + }, +]; + +export const pageTabs = [ + { label: 'Pending', value: DebitNoteStatus.Pending }, + { label: 'Expire', value: DebitNoteStatus.Expire }, + { label: 'Payment', value: DebitNoteStatus.Payment }, + { label: 'Receipt', value: DebitNoteStatus.Receipt }, + { label: 'Succeed', value: DebitNoteStatus.Succeed }, +]; + +export enum Status { + taskOrder = 'taskOrder', + receiveTaskOrder = 'receiveTaskOrder', + sendTaskOrder = 'sendTaskOrder', + payment = 'payment', + goodReceipt = 'goodReceipt', +} + +export const columns = [ + { + name: 'order', + align: 'center', + label: 'general.order', + field: ( + data: DebitNote & { _index: number; _page: number; _pageSize: number }, + ) => (data._page - 1) * data._pageSize + data._index + 1, + }, + { + name: 'code', + align: 'center', + label: 'debitNote.label.codeDebit', + field: (data: DebitNote) => data.code, + }, + { + name: 'quotationCode', + align: 'center', + label: 'debitNote.label.codeQuotation', + field: (data: DebitNote) => data.debitNoteQuotation?.code, + }, + { + name: 'quotationWorkName', + align: 'center', + label: 'debitNote.label.quotationWorkName', + field: (data: DebitNote) => data.workName, + }, + { + name: 'quotationPayment', + align: 'center', + label: 'debitNote.label.quotationPayment', + field: (data: DebitNote) => data.debitNoteQuotation?.payCondition, + }, + { + name: 'creditNoteValue', + align: 'center', + label: 'debitNote.label.value', + field: (data: DebitNote) => formatNumberDecimal(data.totalPrice), + }, + { + name: '#action', + align: 'center', + label: '', + field: (_) => '#action', + }, +] as const satisfies QTableProps['columns']; + +export const hslaColors: Record = { + Pending: '--blue-6-hsl', + Success: '--red-6-hsl', +}; diff --git a/src/pages/12_debit-note/document-view/BankComponents.vue b/src/pages/12_debit-note/document-view/BankComponents.vue new file mode 100644 index 00000000..3f6396c4 --- /dev/null +++ b/src/pages/12_debit-note/document-view/BankComponents.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/pages/12_debit-note/document-view/MainPage.vue b/src/pages/12_debit-note/document-view/MainPage.vue new file mode 100644 index 00000000..0e4432f4 --- /dev/null +++ b/src/pages/12_debit-note/document-view/MainPage.vue @@ -0,0 +1,654 @@ + + + + + diff --git a/src/pages/12_debit-note/document-view/ViewFooter.vue b/src/pages/12_debit-note/document-view/ViewFooter.vue new file mode 100644 index 00000000..fc9a34a5 --- /dev/null +++ b/src/pages/12_debit-note/document-view/ViewFooter.vue @@ -0,0 +1,98 @@ + + + + diff --git a/src/pages/12_debit-note/document-view/ViewHeader.vue b/src/pages/12_debit-note/document-view/ViewHeader.vue new file mode 100644 index 00000000..e9c4624d --- /dev/null +++ b/src/pages/12_debit-note/document-view/ViewHeader.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/pages/12_debit-note/document-view/ViewPdf.vue b/src/pages/12_debit-note/document-view/ViewPdf.vue new file mode 100644 index 00000000..f800e790 --- /dev/null +++ b/src/pages/12_debit-note/document-view/ViewPdf.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/pages/12_debit-note/expansion/DebitNoteExpansion.vue b/src/pages/12_debit-note/expansion/DebitNoteExpansion.vue new file mode 100644 index 00000000..a7649753 --- /dev/null +++ b/src/pages/12_debit-note/expansion/DebitNoteExpansion.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/pages/12_debit-note/expansion/DocumentExpansion.vue b/src/pages/12_debit-note/expansion/DocumentExpansion.vue new file mode 100644 index 00000000..0d38ac7b --- /dev/null +++ b/src/pages/12_debit-note/expansion/DocumentExpansion.vue @@ -0,0 +1,120 @@ + + + diff --git a/src/pages/12_debit-note/expansion/PaymentExpansion.vue b/src/pages/12_debit-note/expansion/PaymentExpansion.vue new file mode 100644 index 00000000..e1b5a7c5 --- /dev/null +++ b/src/pages/12_debit-note/expansion/PaymentExpansion.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/pages/12_debit-note/expansion/ProductExpansion.vue b/src/pages/12_debit-note/expansion/ProductExpansion.vue new file mode 100644 index 00000000..dca92aa2 --- /dev/null +++ b/src/pages/12_debit-note/expansion/ProductExpansion.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/pages/12_debit-note/expansion/RemarkExpansion.vue b/src/pages/12_debit-note/expansion/RemarkExpansion.vue new file mode 100644 index 00000000..d33fcf73 --- /dev/null +++ b/src/pages/12_debit-note/expansion/RemarkExpansion.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue b/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue new file mode 100644 index 00000000..aa4ee499 --- /dev/null +++ b/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/pages/12_debit-note/form.ts b/src/pages/12_debit-note/form.ts new file mode 100644 index 00000000..9f92c0ff --- /dev/null +++ b/src/pages/12_debit-note/form.ts @@ -0,0 +1,64 @@ +import { dialog } from 'stores/utils'; +import { defineStore } from 'pinia'; +import { useI18n } from 'vue-i18n'; +import { ref } from 'vue'; +import { DebitNotePayload } from 'src/stores/debit-note'; +import { PayCondition } from 'src/stores/quotations'; + +// NOTE: Import types + +// NOTE: Import stores + +const DEFAULT_DATA: DebitNotePayload = { + productServiceList: [], + debitNoteQuotationId: '', + worker: [], + payBillDate: new Date(), + paySplit: [], + paySplitCount: 0, + payCondition: PayCondition.Full, + dueDate: new Date(), + discount: 0, + status: 'CREATED', + remark: '#[quotation-labor]

#[quotation-payment]', + quotationId: '', + agentPrice: false, +}; + +export const useDebitNoteForm = defineStore('form-debit-note', () => { + const { t } = useI18n(); + + let resetFormData = structuredClone(DEFAULT_DATA); + + const currentFormData = ref(structuredClone(resetFormData)); + + const currentFormState = ref<{ + mode: null | 'info' | 'create' | 'edit'; + }>({ + mode: null, + }); + + function isFormDataDifferent() { + const { ...resetData } = resetFormData; + const { ...currData } = currentFormData.value; + + return JSON.stringify(resetData) !== JSON.stringify(currData); + } + + function resetForm(clean = false) { + if (clean) { + currentFormData.value = structuredClone(DEFAULT_DATA); + resetFormData = structuredClone(DEFAULT_DATA); + return; + } + + currentFormData.value = structuredClone(resetFormData); + + currentFormState.value.mode = 'info'; + } + + return { + isFormDataDifferent, + resetForm, + }; +}); diff --git a/src/router/routes.ts b/src/router/routes.ts index dee71a15..785f6601 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -120,6 +120,11 @@ const routes: RouteRecordRaw[] = [ name: 'receipt', component: () => import('pages/13_receipt/MainPage.vue'), }, + { + path: '/debit-note', + name: 'debitNote', + component: () => import('pages/12_debit-note/MainPage.vue'), + }, ], }, @@ -183,6 +188,22 @@ const routes: RouteRecordRaw[] = [ name: 'receiptform', component: () => import('pages/13_receipt/MainPage.vue'), }, + { + path: '/debit-note/add', + name: 'DebitNoteNew', + component: () => import('pages/12_debit-note/FormPage.vue'), + }, + { + path: '/debit-note/:id', + name: 'DebitNoteView', + component: () => import('pages/12_debit-note/FormPage.vue'), + }, + + { + path: '/debit-note/document-view', + name: 'DebitNoteDocumentView', + component: () => import('pages/12_debit-note/document-view/MainPage.vue'), + }, // Always leave this as last one, // but you can also remove it diff --git a/src/stores/debit-note/index.ts b/src/stores/debit-note/index.ts new file mode 100644 index 00000000..7ef3b8b3 --- /dev/null +++ b/src/stores/debit-note/index.ts @@ -0,0 +1,94 @@ +import { + DebitNote as Data, + DebitNoteStatus as Status, + DebitNotePayload as Payload, +} from './types.ts'; +import { ref } from 'vue'; +import { defineStore } from 'pinia'; + +import { api } from 'src/boot/axios.ts'; +import { PaginationResult } from 'src/types.ts'; +import { manageAttachment, manageFile } from '../utils/index.ts'; + +const ENDPOINT = 'debit-note'; + +export * from './types.ts'; + +export async function getDebitNoteStats() { + const res = await api.get>(`/${ENDPOINT}/stats`); + if (res.status < 400) { + return res.data; + } + return null; +} + +export async function getDebitNoteList(params?: { + page?: number; + pageSize?: number; + query?: string; + deebitNoteStatus?: Status; + includeRegisteredBranch?: boolean; +}) { + const res = await api.get>(`/${ENDPOINT}`, { + params, + }); + if (res.status < 400) return res.data; + return null; +} + +export async function getDebitNote(id: string) { + const res = await api.get(`/${ENDPOINT}/${id}`); + if (res.status < 400) return res.data; + return null; +} + +export async function createDebitNote(body: Payload) { + const res = await api.post(`/${ENDPOINT}`, body); + if (res.status < 400) return res.data; + return null; +} + +export async function updateDebitNote(body: Payload) { + const { id, quotationId, ...payload } = body; + const res = await api.put(`/${ENDPOINT}/${id}`, payload); + if (res.status < 400) return res.data; + return null; +} + +export async function deleteDebitNote(id: string) { + const res = await api.delete(`/${ENDPOINT}/${id}`); + if (res.status < 400) return res.data; + return null; +} + +export const useDebitNote = defineStore('debit-note-store', () => { + const data = ref([]); + const page = ref(1); + const pageMax = ref(1); + const pageSize = ref(30); + const stats = ref>({ + [Status.Pending]: 0, + [Status.Expire]: 0, + [Status.Payment]: 0, + [Status.Receipt]: 0, + [Status.Succeed]: 0, + }); + + return { + data, + page, + pageMax, + pageSize, + stats, + + getDebitNoteStats, + getDebitNote, + getDebitNoteList, + createDebitNote, + updateDebitNote, + deleteDebitNote, + + ...manageAttachment(api, ENDPOINT), + ...manageFile<'slip'>(api, ENDPOINT), + }; +}); diff --git a/src/stores/debit-note/types.ts b/src/stores/debit-note/types.ts new file mode 100644 index 00000000..00f6b468 --- /dev/null +++ b/src/stores/debit-note/types.ts @@ -0,0 +1,94 @@ +import { + Quotation, + QuotationFull, + QuotationStatus, + QuotationPayload, + PayCondition, +} from '../quotations'; +import { RequestWork } from '../request-list'; +import { CreatedBy, UpdatedBy } from '../types'; +import { CustomerBranch } from '../customer'; +import { Branch } from '../branch/types'; + +export type DebitNotePayload = Omit< + QuotationPayload & { + id?: string; + quotationId: string; + debitNoteQuotationId?: string; + reason?: string; + detail?: string; + agentPrice: boolean; + }, + | 'customerBranchId' + | 'registeredBranchId' + | 'contactName' + | 'contactTel' + | '_count' + | 'urgent' + | 'workName' + | 'workerMax' + | 'productServiceList' + | 'paySplit' +> & { + productServiceList: { + installmentNo: number; + workerIndex: number[]; + discount: number; + amount: number; + productId: string; + workId: string; + serviceId: string; + }[]; +}; + +export type DebitNote = { + debitNoteQuotation?: QuotationFull; + updatedBy: UpdatedBy; + createdBy: CreatedBy; + productServiceList: QuotationFull['productServiceList']; + worker: QuotationFull['worker']; + paySplit: QuotationFull['paySplit']; + registeredBranch: Branch; + customerBranch: CustomerBranch; + _count: { worker: number }; + + updatedByUserId: string; + updatedAt: string; + createdByUserId: string; + createdAt: string; + debitNoteQuotationId: string; + isDebitNote: boolean; + finalPrice: number; + discount: number; + vatExcluded: number; + vat: number; + totalDiscount: number; + totalPrice: number; + agentPrice: boolean; + urgent: boolean; + workerMax: number; + payBillDate: string; + paySplitCount: number; + payCondition: PayCondition; + date: string; + dueDate: string; + contactTel: string; + contactName: string; + workName: string; + code: string; + remark: string; + quotationStatus: QuotationStatus; + statusOrder: number; + status: string; + customerBranchId: string; + registeredBranchId: string; + id: string; +}; + +export enum DebitNoteStatus { + Pending = 'Pending', + Expire = 'Expire', + Payment = 'Payment', + Receipt = 'Receipt', + Succeed = 'Succeed', +} diff --git a/src/stores/payment/index.ts b/src/stores/payment/index.ts index e85de252..93c210fc 100644 --- a/src/stores/payment/index.ts +++ b/src/stores/payment/index.ts @@ -98,6 +98,9 @@ export const useReceipt = defineStore('receipt-store', () => { pageSize?: number; query?: string; quotationId?: string; + debitNoteId?: string; + debitNoteOnly?: boolean; + quotationOnly?: boolean; }) { const res = await api.get>('/receipt', { params: opts, diff --git a/src/stores/quotations/index.ts b/src/stores/quotations/index.ts index e985f1ce..775dc7ac 100644 --- a/src/stores/quotations/index.ts +++ b/src/stores/quotations/index.ts @@ -192,10 +192,15 @@ export const useQuotationStore = defineStore('quotation-store', () => { * @deprecated Please use payment store instead. */ export const useQuotationPayment = defineStore('quotation-payment', () => { - async function getQuotationPayment(quotationId: string) { + async function getQuotationPayment(params: { + quotationId?: string; + debitNoteId?: string; + debitNoteOnly?: boolean; + quotationOnly?: boolean; + }) { const res = await api.get>( '/payment', - { params: { quotationId } }, + { params }, ); if (res.status < 400) { return res.data; diff --git a/tests/institution.spec.ts b/tests/institution.spec.ts new file mode 100644 index 00000000..906d571c --- /dev/null +++ b/tests/institution.spec.ts @@ -0,0 +1,44 @@ +import { fakerEN, fakerTH } from '@faker-js/faker'; +import test, { expect, Page } from '@playwright/test'; +import { login } from './utils'; + +test.describe.configure({ mode: 'serial' }); + +let page: Page; + +test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test('JWS_INST_001 - Login', async () => { + await login(page); +}); + +test('JWS_INST_002 - Goto Institution', async () => { + await page.click('//span[text()="หน่วยงาน"]'); + await expect(page).toHaveURL(/.*agencies-management/); + await page.waitForTimeout(5000); +}); + +test('JWS_INST_003 - Click New Institution', async () => { + await page.click('i.q-icon.mdi.mdi-plus'); + await expect(page.locator('div.col.text-subtitle1')).toContainText( + 'เพิ่มหน่วยงาน', + ); + await page.waitForTimeout(5000); +}); + +test('JWS_INST_004 - Fill Form', async () => { + await page + .getByRole('textbox', { name: 'ชื่อหน่วยงาน' }) + .fill(fakerTH.company.name()); + await page + .getByRole('textbox', { name: 'Agencies Name' }) + .fill(fakerEN.company.name()); + fakerEN.location.buildingNumber(); + await page.waitForTimeout(5000); +}); diff --git a/tests/utils/index.ts b/tests/utils/index.ts new file mode 100644 index 00000000..3277a9aa --- /dev/null +++ b/tests/utils/index.ts @@ -0,0 +1,19 @@ +import { expect, Page } from '@playwright/test'; + +let isLoggedIn = false; + +export async function login(page: Page) { + if (!process.env.TEST_APP_URL) throw new Error('Expect TEST_APP_URL env.'); + + if (isLoggedIn) return; + + await page.goto('/'); + await expect(page).toHaveTitle(/^Sign in to /); + await page.fill("input[name='username']", 'admin'); + await page.fill("input[name='password']", '1234'); + await page.click('id=kc-login'); + await page.waitForTimeout(2000); + await expect(page).toHaveURL(new RegExp('^' + process.env.TEST_APP_URL)); + + isLoggedIn = true; +} From 79ec99554715a0e819ce2421c3479c980af64ebf Mon Sep 17 00:00:00 2001 From: Thanaphon Frappet Date: Mon, 27 Jan 2025 10:06:39 +0700 Subject: [PATCH 02/61] refactor: handle img employee --- .../03_customer-management/TableEmpoloyee.vue | 4 ++-- src/pages/03_customer-management/MainPage.vue | 6 +++--- src/pages/05_quotation/QuotationForm.vue | 1 - .../05_quotation/QuotationFormWorkerAddDialog.vue | 4 ++-- src/pages/05_quotation/QuotationFormWorkerSelect.vue | 11 +++++++---- .../12_debit-note/expansion/WorkerItemExpansion.vue | 1 - 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/03_customer-management/TableEmpoloyee.vue b/src/components/03_customer-management/TableEmpoloyee.vue index 36bb2d6c..58404160 100644 --- a/src/components/03_customer-management/TableEmpoloyee.vue +++ b/src/components/03_customer-management/TableEmpoloyee.vue @@ -288,8 +288,8 @@ defineEmits<{ : `${props.row.firstName} ${props.row.lastName} `.trim(), img: `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` || - '/images/employee-avatar.png', - fallbackImg: '/images/employee-avatar.png', + `/images/employee-avatar-${props.row.gender}.png`, + fallbackImg: `/images/employee-avatar-${props.row.gender}.png`, male: props.row.gender === 'male', female: props.row.gender === 'female', detail: [ diff --git a/src/pages/03_customer-management/MainPage.vue b/src/pages/03_customer-management/MainPage.vue index dbd22972..ce66e008 100644 --- a/src/pages/03_customer-management/MainPage.vue +++ b/src/pages/03_customer-management/MainPage.vue @@ -2414,7 +2414,7 @@ const emptyCreateDialog = ref(false); refreshImageState ? `?ts=${Date.now()}` : '', ) || null " - :fallbackImg="'/images/employee-avatar.png'" + :fallbackImg="`/images/employee-avatar-${currentFromDataEmployee.gender === 'male' ? 'male' : 'female'}.png`" :tabs-list=" [ { @@ -3795,7 +3795,7 @@ const emptyCreateDialog = ref(false); style="background: #ee4367" > @@ -4318,7 +4318,7 @@ const emptyCreateDialog = ref(false); refreshImageState ? `?ts=${Date.now()}` : '', ) || null " - :fallbackImg="'/images/employee-avatar.png'" + :fallbackImg="`/images/employee-avatar-${currentFromDataEmployee.gender === 'male' ? 'male' : 'female'}.png`" :tabs-list="[ { name: 'personalInfo', diff --git a/src/pages/05_quotation/QuotationForm.vue b/src/pages/05_quotation/QuotationForm.vue index 6af0ee0b..ec29f238 100644 --- a/src/pages/05_quotation/QuotationForm.vue +++ b/src/pages/05_quotation/QuotationForm.vue @@ -1544,7 +1544,6 @@ async function formDownload() { quotationFormData.workerMax || selectedWorker.length " :readonly="readonly" - fallback-img="/images/employee-avatar.png" :rows="selectedWorkerItem" @delete="(i) => deleteItem(selectedWorker, i)" /> diff --git a/src/pages/05_quotation/QuotationFormWorkerAddDialog.vue b/src/pages/05_quotation/QuotationFormWorkerAddDialog.vue index 256439ef..52c32a4b 100644 --- a/src/pages/05_quotation/QuotationFormWorkerAddDialog.vue +++ b/src/pages/05_quotation/QuotationFormWorkerAddDialog.vue @@ -220,7 +220,7 @@ function getEmployeeImageUrl(item: Employee) { return `${API_BASE_URL}/employee/${item.id}/image/${item.selectedImage}`; } // NOTE: static image - return '/images/employee-avatar.png'; + return `/images/employee-avatar-${item.gender}.png`; } async function getWorkerList() { @@ -374,7 +374,7 @@ watch(() => state.search, getWorkerList); female: emp.gender === 'female', male: emp.gender === 'male', img: getEmployeeImageUrl(emp), - fallbackImg: '/images/employee-avatar.png', + fallbackImg: `/images/employee-avatar-${emp.gender}.png`, detail: [ { icon: 'mdi-passport', diff --git a/src/pages/05_quotation/QuotationFormWorkerSelect.vue b/src/pages/05_quotation/QuotationFormWorkerSelect.vue index 59904c0f..564acc7f 100644 --- a/src/pages/05_quotation/QuotationFormWorkerSelect.vue +++ b/src/pages/05_quotation/QuotationFormWorkerSelect.vue @@ -207,7 +207,7 @@ function getEmployeeImageUrl(item: Employee) { return `${API_BASE_URL}/employee/${item.id}/image/${item.selectedImage}`; } // NOTE: static image - return '/images/employee-avatar.png'; + return `/images/employee-avatar-${item.gender}.png`; } function init() { @@ -400,7 +400,7 @@ watch(() => state.search, getWorkerList); : `${emp.firstName} ${emp.lastName}`, female: emp.gender === 'female', male: emp.gender === 'male', - img: '/images/employee-avatar.png', + img: `/images/employee-avatar-${emp.gender}.png`, index: index, detail: [ { @@ -486,7 +486,7 @@ watch(() => state.search, getWorkerList); female: emp.gender === 'female', male: emp.gender === 'male', img: getEmployeeImageUrl(emp), - fallbackImg: '/images/employee-avatar.png', + fallbackImg: `/images/employee-avatar-${emp.gender}.png`, detail: [ { icon: 'mdi-passport', @@ -567,7 +567,10 @@ watch(() => state.search, getWorkerList); v-model:current-tab="employeeFormState.currentTab" v-model:toggle-status="currentFromDataEmployee.status" fallbackCover="/images/employee-banner.png" - :img="employeeFormState.profileUrl || `/images/employee-avatar.png`" + :img=" + employeeFormState.profileUrl || + `/images/employee-avatar-${currentFromDataEmployee.gender}.png` + " :toggleTitle="$t('status.title')" hideFade @view=" diff --git a/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue b/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue index aa4ee499..71c27205 100644 --- a/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue +++ b/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue @@ -67,7 +67,6 @@ const toggleWorker = defineModel('toggleWorker'); @update:employee-amount="(v) => $emit('update:employeeAmount', v)" :employee-amount :readonly="readonly" - fallback-img="/images/employee-avatar.png" :rows="rowWorker" @delete="(i) => $emit('delete', i)" /> From e0c1725001a65673b3f224a8e247b4d415759a3b Mon Sep 17 00:00:00 2001 From: Methapon Metanipat <162551568+Methapon-Frappet@users.noreply.github.com> Date: Mon, 27 Jan 2025 10:39:53 +0700 Subject: [PATCH 03/61] refactor: responsive (#180) * refactor: can open one dropdown whe lt.md * style: update MainLayout background color and fix avatar border class name * feat: add touch position binding for dropdown in ProfileMenu * refactor: enhance icon styling in DrawerComponent * fix: update screen size conditions * feat: add responsive search and filter functionality in MainPage * feat: update styling and functionality in BasicInformation and MainPage components * feat: package view mode improve layout and responsiveness * feat: improve layout and responsiveness of ProfileBanner component * feat: enhance TreeView component with improved icon layout and cursor pointer styling * feat: update DialogForm component to prevent text wrapping in the center column * feat: enhance FormDocument, PriceDataComponent, and BasicInfoProduct components with layout and styling improvements * feat: enhance ProfileBanner dark tab * feat: 02 => responsive & responsibleArea type * fix: layout header bg condition & 02 filter col * feat: 04 flow => add AddButton component and enhance layout in FormFlow and FlowDialog * feat: 07 => enhance layout and responsiveness * refactor: simplify header structure and improve layout consistency * fix: improve text color in ItemCard and adjust responsive breakpoints in product service group * refactor: 05 => enhance layout responsiveness and improve class bindings in quotation components * refactor: enhance styling and improve props flexibility in dialog and select components * refactor: 05 => enhance layout responsiveness in quotation components * refactor: 05 => enhance layout responsiveness * refactor: 05 => formWorkerAdd * refactor: 05 => formWorkerAdd Product table * refactor: 05 => improve layout responsiveness and enhance component structure * refactor: enhance grid view handling and improve component imports * refactor: improve column classes for better layout consistency * refactor: 09 => enhance layout structure and improve responsiveness in task order views * refactor: 10 => enhance invoice main page layout and improve component interactions * refactor: 13 => enhance receipt main page layout and improve component interactions * refactor: 11 => enhance layout and improve responsiveness in credit note pages * refactor: 01 => screen.sm search & filter * refactor: 01 => improve layout responsiveness and fix variable naming in branch management forms --------- Co-authored-by: puriphatt --- .../01_branch-management/FormBank.vue | 16 +- .../FormBranchContact.vue | 6 +- .../FormBranchInformation.vue | 6 +- .../04_flow-management/FormFlow.vue | 14 +- .../04_product-service/BasicInfoProduct.vue | 6 +- .../04_product-service/BasicInformation.vue | 21 +- .../04_product-service/FormDocument.vue | 1 + .../04_product-service/PriceDataComponent.vue | 2 +- src/components/05_quotation/FormAbout.vue | 2 +- src/components/05_quotation/QuotationCard.vue | 8 +- .../FormBasicInfoAgencies.vue | 5 +- src/components/DialogForm.vue | 2 +- src/components/ItemCard.vue | 4 +- src/components/ProfileBanner.vue | 33 +- src/components/dialog/DialogViewFile.vue | 6 +- src/components/shared/SelectInput.vue | 4 +- src/components/shared/SelectZone.vue | 6 +- src/components/shared/TreeView.vue | 27 +- src/layouts/DrawerComponent.vue | 33 +- src/layouts/MainLayout.vue | 5 +- src/layouts/ProfileMenu.vue | 1 + src/pages/01_branch-management/MainPage.vue | 63 ++- .../02_personnel-management/MainPage.vue | 40 +- src/pages/04_flow-managment/FlowDialog.vue | 10 +- src/pages/04_flow-managment/MainPage.vue | 28 +- src/pages/04_product-service/MainPage.vue | 521 +++++++++++------- src/pages/05_quotation/MainPage.vue | 48 +- src/pages/05_quotation/PaymentForm.vue | 74 +-- src/pages/05_quotation/QuotationForm.vue | 84 ++- .../QuotationFormProductSelect.vue | 16 +- .../05_quotation/QuotationFormReceipt.vue | 7 +- .../QuotationFormWorkerAddDialog.vue | 16 +- .../QuotationFormWorkerSelect.vue | 2 +- src/pages/05_quotation/TableRequest.vue | 10 +- .../07_agencies-management/AgenciesDialog.vue | 12 +- src/pages/07_agencies-management/MainPage.vue | 13 +- src/pages/08_request-list/MainPage.vue | 31 +- src/pages/09_task-order/MainPage.vue | 15 +- .../09_task-order/order_view/MainPage.vue | 100 ++-- .../09_task-order/receive_view/MainPage.vue | 186 ++++--- src/pages/10_invoice/MainPage.vue | 33 +- src/pages/11_credit-note/MainPage.vue | 13 +- .../11_credit-note/RefundInformation.vue | 33 +- src/pages/13_receipt/MainPage.vue | 35 +- src/stores/user/types.ts | 4 +- 45 files changed, 993 insertions(+), 609 deletions(-) diff --git a/src/components/01_branch-management/FormBank.vue b/src/components/01_branch-management/FormBank.vue index eef5f3a1..e9cc4483 100644 --- a/src/components/01_branch-management/FormBank.vue +++ b/src/components/01_branch-management/FormBank.vue @@ -44,7 +44,7 @@ defineEmits<{ }>(); const bankBookOptions = ref[]>([]); -let bankBoookFilter: ( +let bankBookFilter: ( value: string, update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void, ) => void; @@ -77,7 +77,7 @@ function change(e: Event) { onMounted(() => { if (optionStore.globalOption) { - bankBoookFilter = selectFilterOptionRefMod( + bankBookFilter = selectFilterOptionRefMod( ref(optionStore.globalOption.bankBook), bankBookOptions, 'label', @@ -94,7 +94,7 @@ onMounted(() => { watch( () => optionStore.globalOption, () => { - bankBoookFilter = selectFilterOptionRefMod( + bankBookFilter = selectFilterOptionRefMod( ref(optionStore.globalOption.bankBook), bankBookOptions, 'label', @@ -131,8 +131,8 @@ watch(
- + diff --git a/src/components/04_product-service/FormDocument.vue b/src/components/04_product-service/FormDocument.vue index f7aa3ab1..f875122c 100644 --- a/src/components/04_product-service/FormDocument.vue +++ b/src/components/04_product-service/FormDocument.vue @@ -106,6 +106,7 @@ function optionSearch(val: string | null) {
-
+
{{ $t(`general.about`) }} -
+
-import { Icon } from '@iconify/vue/dist/iconify.js'; import { formatNumberDecimal } from 'src/stores/utils'; import BadgeComponent from 'components/BadgeComponent.vue'; import KebabAction from '../shared/KebabAction.vue'; -import MainButton from '../button/MainButton.vue'; defineProps<{ title?: string; @@ -48,7 +46,7 @@ const rand = Math.random();
@@ -378,6 +385,7 @@ const smallBanner = ref(false);
@@ -425,7 +433,7 @@ const smallBanner = ref(false); inline-label mobile-arrows v-model="currentTab" - active-class="active-tab text-weight-bold" + :active-class="`active-tab text-weight-bold ${$q.dark.isActive && 'dark'}`" class="app-text-muted full-width" align="left" v-if="typeof tabsList === 'object'" @@ -435,6 +443,7 @@ const smallBanner = ref(false); :id="`${prefix}-tab-${tab.label}`" v-bind:key="tab.name" class="content-tab text-capitalize" + :class="{ 'tab-label': currentTab !== tab.name }" :name="tab.name" :label="tab.label" /> @@ -526,5 +535,13 @@ const smallBanner = ref(false); .active-tab { color: var(--brand-1); + &.dark { + filter: brightness(1.3); + } +} + +.tab-label { + color: var(--foreground); + opacity: 75%; } diff --git a/src/components/dialog/DialogViewFile.vue b/src/components/dialog/DialogViewFile.vue index db574376..871842bf 100644 --- a/src/components/dialog/DialogViewFile.vue +++ b/src/components/dialog/DialogViewFile.vue @@ -57,13 +57,13 @@ async function downloadImage(url: string | null) {
-
+
-
+
- +
+ +
diff --git a/src/layouts/DrawerComponent.vue b/src/layouts/DrawerComponent.vue index a5012b7d..92a4b67d 100644 --- a/src/layouts/DrawerComponent.vue +++ b/src/layouts/DrawerComponent.vue @@ -5,6 +5,7 @@ import { storeToRefs } from 'pinia'; import { Icon } from '@iconify/vue'; import useMyBranch from 'stores/my-branch'; import { getUserId, getRole } from 'src/services/keycloak'; +import { useQuasar } from 'quasar'; type Menu = { label: string; @@ -17,6 +18,8 @@ type Menu = { }; const router = useRouter(); +const $q = useQuasar(); + const userBranch = useMyBranch(); const { currentMyBranch } = storeToRefs(userBranch); @@ -35,17 +38,6 @@ const currentPath = computed(() => { return router.currentRoute.value.path; }); -// const labelMenu = ref< -// { -// label: string; -// icon: string; -// route: string; -// hidden?: boolean; -// disabled?: boolean; -// isax?: boolean; -// }[] -// >([]); - function navigateTo(label: string, destination?: string) { if (!destination) return; router.push(`${destination}`); @@ -57,6 +49,8 @@ function reActiveMenu() { ); const currMenuIndex = menuData.value.findIndex((m) => m === currMenu); + + if ($q.screen.lt.sm) menuActive.value.fill(false); menuActive.value[currMenuIndex] = true; } @@ -204,7 +198,6 @@ onMounted(async () => { :width="mini ? 80 : 256" show-if-above > -
{ :disable="menu.disabled" :header-class="{ row: true, - 'justify-between': !mini, 'no-padding justify-center': mini, 'active-menu text-weight-bold': menuActive[i], 'text-weight-medium': !menu.disabled, @@ -254,7 +246,12 @@ onMounted(async () => { :class="`isax ${menu.icon}`" style="font-size: 24px" /> - + { border-bottom-right-radius: var(--radius-2); } } + +.fix-icon { + color: var(--text-mute-2) !important; +} + +:deep(.q-item.q-item-type.row.no-wrap.q-item--dense.disabled) { + opacity: 30% !important; +} diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 02e0cabe..168b895d 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -208,6 +208,9 @@ onMounted(async () => {
{ } } -.avartar-border { +.avatar-border { margin-top: 24px; border: 5px solid var(--surface-1); border-radius: 50%; diff --git a/src/layouts/ProfileMenu.vue b/src/layouts/ProfileMenu.vue index 1c7451ea..82980933 100644 --- a/src/layouts/ProfileMenu.vue +++ b/src/layouts/ProfileMenu.vue @@ -317,6 +317,7 @@ onMounted(async () => { max-width="200" :offset="[10, 0]" style="width: 160px" + :touch-position="$q.screen.lt.sm" >
diff --git a/src/pages/01_branch-management/MainPage.vue b/src/pages/01_branch-management/MainPage.vue index abaeb972..e00bc118 100644 --- a/src/pages/01_branch-management/MainPage.vue +++ b/src/pages/01_branch-management/MainPage.vue @@ -6,7 +6,7 @@ import { Icon } from '@iconify/vue'; import { BranchContact } from 'stores/branch-contact/types'; import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; -import type { QTableProps, QTableSlots } from 'quasar'; +import type { QSelect, QTableProps, QTableSlots } from 'quasar'; import { resetScrollBar } from 'src/stores/utils'; import useBranchStore from 'stores/branch'; import useFlowStore from 'stores/flow'; @@ -72,6 +72,7 @@ const typeBranchItem = [ color: 'var(--blue-6-hsl)', }, ]; +const refFilter = ref>(); const holdDialog = ref(false); const isSubCreate = ref(false); const columns = [ @@ -302,7 +303,10 @@ const stats = ref< }[] >([]); -const splitterModel = ref(25); +// const splitterModel = ref(25); +const splitterModel = computed(() => + $q.screen.lt.md ? (currentHq.value.id ? 0 : 100) : 25, +); const defaultFormData = { headOfficeId: null, @@ -1020,12 +1024,13 @@ watch(currentHq, () => { class="col" before-class="overflow-hidden" after-class="overflow-hidden" + :disable="$q.screen.lt.sm" > -
+
state.search, getWorkerList); ...data, _selectedIndex: selectedIndex(data), }))" - class="col-2" + class="col-md-2 col-sm-6 col-12" >
-
+
state.search, getWorkerList); expand-icon="mdi-chevron-down-circle" header-class="q-py-sm text-medium text-body items-center surface-1" v-for="{ id, amount, worker, product } in productServiceList" + :key="id" > +
-
+ -
@@ -1445,7 +1474,7 @@ watch( :fetch-data="async () => await fetchUserList()" />
-
+
diff --git a/src/pages/08_request-list/MainPage.vue b/src/pages/08_request-list/MainPage.vue index 9097c43d..7d37ac74 100644 --- a/src/pages/08_request-list/MainPage.vue +++ b/src/pages/08_request-list/MainPage.vue @@ -58,7 +58,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) { }); if (ret) { - data.value = ret.result; + $q.screen.xs ? data.value.push(...ret.result) : (data.value = ret.result); + pageState.total = ret.total; pageMax.value = Math.ceil(ret.total / pageSize.value); } @@ -332,20 +333,40 @@ watch([() => pageState.inputSearch, () => pageState.statusFilter], () =>
- + + + +
diff --git a/src/stores/user/index.ts b/src/stores/user/index.ts index 0a171101..1d26ea90 100644 --- a/src/stores/user/index.ts +++ b/src/stores/user/index.ts @@ -209,11 +209,10 @@ const useUserStore = defineStore('api-user', () => { responsibleDistrictId?: string; activeBranchOnly?: boolean; }) { - const res = await api.get>(`/user`, { params: opts }); + const res = await api.get>('/user', { params: opts }); if (res && res.status === 200) { - data.value = res.data; - return data.value; + return res.data; } return false; } From 1ae5d5b603da060f99280c2b24625838852e0c08 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 29 Jan 2025 14:33:47 +0700 Subject: [PATCH 32/61] refactor: responsive --- src/components/shared/SelectZone.vue | 93 +++++++++++++++------------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/src/components/shared/SelectZone.vue b/src/components/shared/SelectZone.vue index adcd8775..8dd9cc5f 100644 --- a/src/components/shared/SelectZone.vue +++ b/src/components/shared/SelectZone.vue @@ -11,7 +11,6 @@ const selectedItem = defineModel('selectedItem', { default: [] }); const props = withDefaults( defineProps<{ - // eslint-disable-next-line @typescript-eslint/no-explicit-any items: any; newItems?: any; color?: string; @@ -78,7 +77,6 @@ function assignSelect(to: unknown[], from: unknown[]) { }
@@ -2673,479 +2697,513 @@ watch(
- - - + + - - + + - +
- -
+
@@ -3154,37 +3212,7 @@ watch( {{ $t('general.recordPerPage') }}
- - - - - {{ v }} - - - - +
@@ -3220,7 +3248,7 @@ watch( " />
-
+
From 508e6d3f5b0168e11603096f9e2a1f44f0626e0a Mon Sep 17 00:00:00 2001 From: puriphatt Date: Wed, 29 Jan 2025 17:37:42 +0700 Subject: [PATCH 35/61] refactor: 07 => screen.xs fetch scroll --- src/pages/07_agencies-management/MainPage.vue | 467 +++++++++--------- 1 file changed, 246 insertions(+), 221 deletions(-) diff --git a/src/pages/07_agencies-management/MainPage.vue b/src/pages/07_agencies-management/MainPage.vue index 7e1dcf5b..c80f114a 100644 --- a/src/pages/07_agencies-management/MainPage.vue +++ b/src/pages/07_agencies-management/MainPage.vue @@ -241,7 +241,7 @@ async function fetchData() { }); if (ret) { - data.value = ret.result; + $q.screen.xs ? data.value.push(...ret.result) : (data.value = ret.result); pageMax.value = Math.ceil(ret.total / pageSize.value); pageState.total = ret.total; } @@ -316,7 +316,7 @@ watch(
-
+
- - - - @@ -1792,49 +1834,77 @@ const emptyCreateDialog = ref(false);
-
- -
-
+ + + + +
@@ -1875,7 +1945,7 @@ const emptyCreateDialog = ref(false); " />
-
+