Merge pull request #186 from Frappet/feat/14-report
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 9s
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 9s
feat: report
This commit is contained in:
commit
1158d84103
14 changed files with 908 additions and 14 deletions
|
|
@ -18,6 +18,7 @@
|
||||||
"@quasar/extras": "^1.16.12",
|
"@quasar/extras": "^1.16.12",
|
||||||
"@tato30/vue-pdf": "^1.11.0",
|
"@tato30/vue-pdf": "^1.11.0",
|
||||||
"@vuepic/vue-datepicker": "^8.8.1",
|
"@vuepic/vue-datepicker": "^8.8.1",
|
||||||
|
"apexcharts": "^4.5.0",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
"keycloak-js": "^25.0.4",
|
"keycloak-js": "^25.0.4",
|
||||||
|
|
@ -34,6 +35,7 @@
|
||||||
"udsv": "^0.6.0",
|
"udsv": "^0.6.0",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"vue": "^3.4.38",
|
"vue": "^3.4.38",
|
||||||
|
"vue3-apexcharts": "^1.7.0",
|
||||||
"vue-dragscroll": "^4.0.6",
|
"vue-dragscroll": "^4.0.6",
|
||||||
"vue-i18n": "^9.14.0",
|
"vue-i18n": "^9.14.0",
|
||||||
"vue-pdf": "^4.3.0",
|
"vue-pdf": "^4.3.0",
|
||||||
|
|
|
||||||
78
pnpm-lock.yaml
generated
78
pnpm-lock.yaml
generated
|
|
@ -17,6 +17,9 @@ importers:
|
||||||
'@vuepic/vue-datepicker':
|
'@vuepic/vue-datepicker':
|
||||||
specifier: ^8.8.1
|
specifier: ^8.8.1
|
||||||
version: 8.8.1(vue@3.4.38(typescript@5.5.4))
|
version: 8.8.1(vue@3.4.38(typescript@5.5.4))
|
||||||
|
apexcharts:
|
||||||
|
specifier: ^4.5.0
|
||||||
|
version: 4.5.0
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.7.4
|
specifier: ^1.7.4
|
||||||
version: 1.7.4
|
version: 1.7.4
|
||||||
|
|
@ -77,6 +80,9 @@ importers:
|
||||||
vue-router:
|
vue-router:
|
||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3(vue@3.4.38(typescript@5.5.4))
|
version: 4.4.3(vue@3.4.38(typescript@5.5.4))
|
||||||
|
vue3-apexcharts:
|
||||||
|
specifier: ^1.7.0
|
||||||
|
version: 1.8.0(apexcharts@4.5.0)(vue@3.4.38(typescript@5.5.4))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@faker-js/faker':
|
'@faker-js/faker':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
|
|
@ -725,6 +731,31 @@ packages:
|
||||||
'@socket.io/component-emitter@3.1.1':
|
'@socket.io/component-emitter@3.1.1':
|
||||||
resolution: {integrity: sha512-dzJtaDAAoXx4GCOJpbB2eG/Qj8VDpdwkLsWGzGm+0L7E8/434RyMbAHmk9ubXWVAb9nXmc44jUf8GKqVDiKezg==}
|
resolution: {integrity: sha512-dzJtaDAAoXx4GCOJpbB2eG/Qj8VDpdwkLsWGzGm+0L7E8/434RyMbAHmk9ubXWVAb9nXmc44jUf8GKqVDiKezg==}
|
||||||
|
|
||||||
|
'@svgdotjs/svg.draggable.js@3.0.6':
|
||||||
|
resolution: {integrity: sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@svgdotjs/svg.js': ^3.2.4
|
||||||
|
|
||||||
|
'@svgdotjs/svg.filter.js@3.0.9':
|
||||||
|
resolution: {integrity: sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
'@svgdotjs/svg.js@3.2.4':
|
||||||
|
resolution: {integrity: sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==}
|
||||||
|
|
||||||
|
'@svgdotjs/svg.resize.js@2.0.5':
|
||||||
|
resolution: {integrity: sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==}
|
||||||
|
engines: {node: '>= 14.18'}
|
||||||
|
peerDependencies:
|
||||||
|
'@svgdotjs/svg.js': ^3.2.4
|
||||||
|
'@svgdotjs/svg.select.js': ^4.0.1
|
||||||
|
|
||||||
|
'@svgdotjs/svg.select.js@4.0.2':
|
||||||
|
resolution: {integrity: sha512-5gWdrvoQX3keo03SCmgaBbD+kFftq0F/f2bzCbNnpkkvW6tk4rl4MakORzFuNjvXPWwB4az9GwuvVxQVnjaK2g==}
|
||||||
|
engines: {node: '>= 14.18'}
|
||||||
|
peerDependencies:
|
||||||
|
'@svgdotjs/svg.js': ^3.2.4
|
||||||
|
|
||||||
'@tato30/vue-pdf@1.11.0':
|
'@tato30/vue-pdf@1.11.0':
|
||||||
resolution: {integrity: sha512-GQNfVqq8if6/tezgcW1E0iU7kO5VfzMihdU6sfRnw0ewDYcP43JFt3r2fmDggyAY5lu7ArEwCFvlWi7WbvleCw==}
|
resolution: {integrity: sha512-GQNfVqq8if6/tezgcW1E0iU7kO5VfzMihdU6sfRnw0ewDYcP43JFt3r2fmDggyAY5lu7ArEwCFvlWi7WbvleCw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -979,6 +1010,9 @@ packages:
|
||||||
'@xtuc/long@4.2.2':
|
'@xtuc/long@4.2.2':
|
||||||
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
|
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
|
||||||
|
|
||||||
|
'@yr/monotone-cubic-spline@1.0.3':
|
||||||
|
resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==}
|
||||||
|
|
||||||
abbrev@1.1.1:
|
abbrev@1.1.1:
|
||||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||||
|
|
||||||
|
|
@ -1052,6 +1086,9 @@ packages:
|
||||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
apexcharts@4.5.0:
|
||||||
|
resolution: {integrity: sha512-E7ZkrVqPNBUWy/Rmg8DEIqHNBmElzICE/oxOX5Ekvs2ICQUOK/VkEkMH09JGJu+O/EA0NL31hxlmF+wrwrSLaQ==}
|
||||||
|
|
||||||
aproba@1.2.0:
|
aproba@1.2.0:
|
||||||
resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==}
|
resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==}
|
||||||
|
|
||||||
|
|
@ -3569,6 +3606,12 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.2.0
|
vue: ^3.2.0
|
||||||
|
|
||||||
|
vue3-apexcharts@1.8.0:
|
||||||
|
resolution: {integrity: sha512-5tSD4mXTBbIJ9ir+58qHE6oNtIe0RNgqIRYMKpcsIaxkKtwUww4JhvPkpUFlmiW4OJbbdklgjleXq1lfcM4gdA==}
|
||||||
|
peerDependencies:
|
||||||
|
apexcharts: '>=4.0.0'
|
||||||
|
vue: '>=3.0.0'
|
||||||
|
|
||||||
vue@3.4.38:
|
vue@3.4.38:
|
||||||
resolution: {integrity: sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==}
|
resolution: {integrity: sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -4186,6 +4229,25 @@ snapshots:
|
||||||
|
|
||||||
'@socket.io/component-emitter@3.1.1': {}
|
'@socket.io/component-emitter@3.1.1': {}
|
||||||
|
|
||||||
|
'@svgdotjs/svg.draggable.js@3.0.6(@svgdotjs/svg.js@3.2.4)':
|
||||||
|
dependencies:
|
||||||
|
'@svgdotjs/svg.js': 3.2.4
|
||||||
|
|
||||||
|
'@svgdotjs/svg.filter.js@3.0.9':
|
||||||
|
dependencies:
|
||||||
|
'@svgdotjs/svg.js': 3.2.4
|
||||||
|
|
||||||
|
'@svgdotjs/svg.js@3.2.4': {}
|
||||||
|
|
||||||
|
'@svgdotjs/svg.resize.js@2.0.5(@svgdotjs/svg.js@3.2.4)(@svgdotjs/svg.select.js@4.0.2(@svgdotjs/svg.js@3.2.4))':
|
||||||
|
dependencies:
|
||||||
|
'@svgdotjs/svg.js': 3.2.4
|
||||||
|
'@svgdotjs/svg.select.js': 4.0.2(@svgdotjs/svg.js@3.2.4)
|
||||||
|
|
||||||
|
'@svgdotjs/svg.select.js@4.0.2(@svgdotjs/svg.js@3.2.4)':
|
||||||
|
dependencies:
|
||||||
|
'@svgdotjs/svg.js': 3.2.4
|
||||||
|
|
||||||
'@tato30/vue-pdf@1.11.0(vue@3.4.38(typescript@5.5.4))':
|
'@tato30/vue-pdf@1.11.0(vue@3.4.38(typescript@5.5.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
pdfjs-dist: 4.5.136
|
pdfjs-dist: 4.5.136
|
||||||
|
|
@ -4549,6 +4611,8 @@ snapshots:
|
||||||
|
|
||||||
'@xtuc/long@4.2.2': {}
|
'@xtuc/long@4.2.2': {}
|
||||||
|
|
||||||
|
'@yr/monotone-cubic-spline@1.0.3': {}
|
||||||
|
|
||||||
abbrev@1.1.1:
|
abbrev@1.1.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -4625,6 +4689,15 @@ snapshots:
|
||||||
normalize-path: 3.0.0
|
normalize-path: 3.0.0
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
apexcharts@4.5.0:
|
||||||
|
dependencies:
|
||||||
|
'@svgdotjs/svg.draggable.js': 3.0.6(@svgdotjs/svg.js@3.2.4)
|
||||||
|
'@svgdotjs/svg.filter.js': 3.0.9
|
||||||
|
'@svgdotjs/svg.js': 3.2.4
|
||||||
|
'@svgdotjs/svg.resize.js': 2.0.5(@svgdotjs/svg.js@3.2.4)(@svgdotjs/svg.select.js@4.0.2(@svgdotjs/svg.js@3.2.4))
|
||||||
|
'@svgdotjs/svg.select.js': 4.0.2(@svgdotjs/svg.js@3.2.4)
|
||||||
|
'@yr/monotone-cubic-spline': 1.0.3
|
||||||
|
|
||||||
aproba@1.2.0: {}
|
aproba@1.2.0: {}
|
||||||
|
|
||||||
aproba@2.0.0:
|
aproba@2.0.0:
|
||||||
|
|
@ -7513,6 +7586,11 @@ snapshots:
|
||||||
'@vue/devtools-api': 6.6.3
|
'@vue/devtools-api': 6.6.3
|
||||||
vue: 3.4.38(typescript@5.5.4)
|
vue: 3.4.38(typescript@5.5.4)
|
||||||
|
|
||||||
|
vue3-apexcharts@1.8.0(apexcharts@4.5.0)(vue@3.4.38(typescript@5.5.4)):
|
||||||
|
dependencies:
|
||||||
|
apexcharts: 4.5.0
|
||||||
|
vue: 3.4.38(typescript@5.5.4)
|
||||||
|
|
||||||
vue@3.4.38(typescript@5.5.4):
|
vue@3.4.38(typescript@5.5.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/compiler-dom': 3.4.38
|
'@vue/compiler-dom': 3.4.38
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ import '@vuepic/vue-datepicker/dist/main.css';
|
||||||
import GlobalDialog from 'components/GlobalDialog.vue';
|
import GlobalDialog from 'components/GlobalDialog.vue';
|
||||||
import GlobalLoading from 'components/GlobalLoading.vue';
|
import GlobalLoading from 'components/GlobalLoading.vue';
|
||||||
import VueDragscroll from 'vue-dragscroll';
|
import VueDragscroll from 'vue-dragscroll';
|
||||||
|
import VueApexCharts from 'vue3-apexcharts';
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
app.component('global-dialog', GlobalDialog);
|
app.component('global-dialog', GlobalDialog);
|
||||||
app.component('global-loading', GlobalLoading);
|
app.component('global-loading', GlobalLoading);
|
||||||
app.component('VueDatePicker', VueDatePicker);
|
app.component('VueDatePicker', VueDatePicker);
|
||||||
|
app.component('VueApexCharts', VueApexCharts);
|
||||||
app.use(VueDragscroll);
|
app.use(VueDragscroll);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
29
src/components/14_report/Expansion.vue
Normal file
29
src/components/14_report/Expansion.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
defaultOpened?: boolean;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-expansion-item
|
||||||
|
dense
|
||||||
|
class="overflow-hidden bordered full-width"
|
||||||
|
switch-toggle-side
|
||||||
|
style="border-radius: var(--radius-2)"
|
||||||
|
expand-icon="mdi-chevron-down-circle"
|
||||||
|
header-class="surface-1 q-py-sm text-medium text-body1"
|
||||||
|
:default-opened="defaultOpened"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span>
|
||||||
|
<slot name="header" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<main class="surface-1 q-pa-md full-width">
|
||||||
|
<slot name="main" />
|
||||||
|
</main>
|
||||||
|
</q-expansion-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -1336,4 +1336,41 @@ export default {
|
||||||
Succeed: 'Completed',
|
Succeed: 'Completed',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
report: {
|
||||||
|
report: {
|
||||||
|
title: 'Report',
|
||||||
|
view: {
|
||||||
|
Document: 'Document Status Report',
|
||||||
|
Invoice: 'Payment Report',
|
||||||
|
Product: 'Product and Service Report',
|
||||||
|
Sale: 'Sales Summary Report',
|
||||||
|
},
|
||||||
|
document: {
|
||||||
|
code: 'Code',
|
||||||
|
status: 'Quotation Status',
|
||||||
|
createAt: 'Created Date',
|
||||||
|
updateAt: 'Updated Date',
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
code: 'Code',
|
||||||
|
status: 'Status',
|
||||||
|
createAt: 'Created Date',
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
did: 'Processed Quantity',
|
||||||
|
sale: 'Sold Quantity',
|
||||||
|
name: 'Product Name',
|
||||||
|
code: 'Product Code',
|
||||||
|
},
|
||||||
|
sale: {
|
||||||
|
byCustomer: 'Sales by Branch',
|
||||||
|
byProductGroup: 'Sales by Product Category',
|
||||||
|
bySale: 'Sales by Salesperson',
|
||||||
|
code: 'Code',
|
||||||
|
name: 'Name',
|
||||||
|
count: 'Sold Quantity',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1315,4 +1315,40 @@ export default {
|
||||||
Succeed: 'เสร็จสิ้น',
|
Succeed: 'เสร็จสิ้น',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
report: {
|
||||||
|
title: 'รายงาน',
|
||||||
|
view: {
|
||||||
|
Document: 'รายงานสถานะเอกสาร',
|
||||||
|
Invoice: 'รายงานการชำระเงิน',
|
||||||
|
Product: 'รายงานสินค้าและบริการ',
|
||||||
|
Sale: 'รายงานสรุปยอดขาย',
|
||||||
|
},
|
||||||
|
document: {
|
||||||
|
code: 'รหัส',
|
||||||
|
status: 'สถานะใบเสนอราคา',
|
||||||
|
createAt: 'วันที่สร้าง',
|
||||||
|
updateAt: 'วันที่แก้ไข',
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
code: 'รหัส',
|
||||||
|
status: 'สถานะ',
|
||||||
|
createAt: 'วันที่สร้าง',
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
did: 'จำนวนที่ออกไปดำเนินการ',
|
||||||
|
sale: 'จำนวนที่ขายออกไป',
|
||||||
|
name: 'ชื่อสินค้า',
|
||||||
|
code: 'รหัสสินค้า',
|
||||||
|
},
|
||||||
|
|
||||||
|
sale: {
|
||||||
|
byCustomer: 'ยอดขายตามสาขา',
|
||||||
|
byProductGroup: 'ยอดขายตามประเภทสินค้า',
|
||||||
|
bySale: 'ยอดขายตามพนักงานขาย',
|
||||||
|
code: 'รหัส',
|
||||||
|
name: 'ชื่อ',
|
||||||
|
count: 'จำนวนที่ขาย',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -171,10 +171,9 @@ onMounted(async () => {
|
||||||
{
|
{
|
||||||
label: 'menu.overall',
|
label: 'menu.overall',
|
||||||
icon: 'mdi-monitor-dashboard',
|
icon: 'mdi-monitor-dashboard',
|
||||||
disabled: true,
|
|
||||||
children: [
|
children: [
|
||||||
{ label: 'report', route: '' },
|
{ label: 'report', route: '/report' },
|
||||||
{ label: 'dashboard', route: '' },
|
{ label: 'dashboard', route: '/dash-board' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
361
src/pages/14_report/MainPage.vue
Normal file
361
src/pages/14_report/MainPage.vue
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// NOTE: Library
|
||||||
|
import { computed, onMounted, reactive, watch } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { getRole } from 'src/services/keycloak';
|
||||||
|
|
||||||
|
// NOTE: Components
|
||||||
|
import TableReport from './Table/TableReport.vue';
|
||||||
|
|
||||||
|
// NOTE: Stores & Type
|
||||||
|
import { baseUrl } from 'stores/utils';
|
||||||
|
import { hslaColors as colorsQuotation } from 'src/pages/05_quotation/constants';
|
||||||
|
import { hslaColors as colorsInvoice } from 'src/pages/10_invoice/constants';
|
||||||
|
import { useNavigator } from 'src/stores/navigator';
|
||||||
|
import {
|
||||||
|
ViewMode,
|
||||||
|
pageTabs,
|
||||||
|
colReportQuotation,
|
||||||
|
colReport,
|
||||||
|
colReportProduct,
|
||||||
|
colReportSale,
|
||||||
|
} from './constants';
|
||||||
|
import useFlowStore from 'src/stores/flow';
|
||||||
|
import { useReportStore } from 'src/stores/report';
|
||||||
|
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
||||||
|
import Expansion from 'src/components/14_report/Expansion.vue';
|
||||||
|
|
||||||
|
// NOTE: Variable
|
||||||
|
const navigatorStore = useNavigator();
|
||||||
|
const reportStore = useReportStore();
|
||||||
|
const flow = useFlowStore();
|
||||||
|
|
||||||
|
const {
|
||||||
|
dataReportQuotation,
|
||||||
|
dataReportInvoice,
|
||||||
|
dataReportReceipt,
|
||||||
|
dataReportSale,
|
||||||
|
dataReportProduct,
|
||||||
|
} = storeToRefs(reportStore);
|
||||||
|
|
||||||
|
const userRoles = computed(() => getRole() || []);
|
||||||
|
|
||||||
|
async function fetchReportQuotation() {
|
||||||
|
dataReportQuotation.value = (await reportStore.getReportQuotation()) || [];
|
||||||
|
}
|
||||||
|
async function fetchReportInvoice() {
|
||||||
|
dataReportInvoice.value = (await reportStore.getReportInvoice()) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchReportReceipt() {
|
||||||
|
dataReportReceipt.value = (await reportStore.getReportReceipt()) || [];
|
||||||
|
}
|
||||||
|
async function fetchReportSale() {
|
||||||
|
dataReportSale.value = (await reportStore.getReportSale()) || undefined;
|
||||||
|
}
|
||||||
|
async function fetchReportProduct() {
|
||||||
|
dataReportProduct.value = (await reportStore.getReportProduct()) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
navigatorStore.current.title = 'report.title';
|
||||||
|
navigatorStore.current.path = [{ text: '' }];
|
||||||
|
});
|
||||||
|
|
||||||
|
const isExecutive = computed(() => {
|
||||||
|
const roles = userRoles.value;
|
||||||
|
return roles.includes('executive');
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredTabs = computed(() => {
|
||||||
|
return pageTabs.filter((tab) => {
|
||||||
|
if (!isExecutive.value) {
|
||||||
|
return !tab.by.includes('admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageState = reactive({
|
||||||
|
currentTab: isExecutive.value ? ViewMode.Product : ViewMode.Document,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchReportTab() {
|
||||||
|
switch (pageState.currentTab) {
|
||||||
|
case ViewMode.Document: {
|
||||||
|
await fetchReportQuotation();
|
||||||
|
await fetchReportInvoice();
|
||||||
|
await fetchReportReceipt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ViewMode.Invoice: {
|
||||||
|
await fetchReportInvoice();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ViewMode.Product: {
|
||||||
|
await fetchReportProduct();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ViewMode.Sale: {
|
||||||
|
await fetchReportSale();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchReportTab();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch([() => pageState.currentTab], async () => {
|
||||||
|
await fetchReportTab();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="column full-height no-wrap">
|
||||||
|
<section class="col surface-1 rounded bordered overflow-hidden">
|
||||||
|
<div class="column full-height">
|
||||||
|
<!-- SEC: header content -->
|
||||||
|
<header
|
||||||
|
class="row surface-3 justify-between full-width items-center"
|
||||||
|
style="z-index: 1"
|
||||||
|
>
|
||||||
|
<nav class="surface-2 bordered-b q-px-md full-width">
|
||||||
|
<q-tabs
|
||||||
|
inline-label
|
||||||
|
mobile-arrows
|
||||||
|
dense
|
||||||
|
v-model="pageState.currentTab"
|
||||||
|
align="left"
|
||||||
|
class="full-width"
|
||||||
|
active-color="info"
|
||||||
|
>
|
||||||
|
<q-tab
|
||||||
|
v-for="tab in filteredTabs"
|
||||||
|
:name="tab.value"
|
||||||
|
:key="tab.value"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
pageState.currentTab = tab.value;
|
||||||
|
flow.rotate();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="row text-capitalize"
|
||||||
|
:class="
|
||||||
|
pageState.currentTab === tab.value
|
||||||
|
? 'text-bold'
|
||||||
|
: 'app-text-muted'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t(`report.view.${tab.label}`) }}
|
||||||
|
</div>
|
||||||
|
</q-tab>
|
||||||
|
</q-tabs>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- SEC: body content -->
|
||||||
|
<article class="col surface-2 full-width scroll q-pa-md">
|
||||||
|
<!-- #TODO change Table -->
|
||||||
|
<template v-if="pageState.currentTab === ViewMode.Document">
|
||||||
|
<div class="q-gutter-y-md">
|
||||||
|
<!-- Quotatio -->
|
||||||
|
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>{{ $t('quotation.title') }}</template>
|
||||||
|
|
||||||
|
<template #main>
|
||||||
|
<TableReport
|
||||||
|
:row="dataReportQuotation"
|
||||||
|
:columns="colReportQuotation"
|
||||||
|
>
|
||||||
|
<template #status="{ item }">
|
||||||
|
<BadgeComponent
|
||||||
|
:title="$t(`quotation.status.${item.row.status}`)"
|
||||||
|
:hsla-color="colorsQuotation[item.row.status] || ''"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</TableReport>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
|
||||||
|
<!-- Invoice -->
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>{{ $t('invoice.title') }}</template>
|
||||||
|
<template #main>
|
||||||
|
<TableReport :row="dataReportInvoice" :columns="colReport">
|
||||||
|
<template #status="{ item }">
|
||||||
|
<BadgeComponent
|
||||||
|
:title="$t(`invoice.status.${item.row.status}`)"
|
||||||
|
:hsla-color="colorsInvoice[item.row.status] || ''"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</TableReport>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
|
||||||
|
<!-- Receipt -->
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>{{ $t('receipt.title') }}</template>
|
||||||
|
<template #main>
|
||||||
|
<TableReport :row="dataReportReceipt" :columns="colReport">
|
||||||
|
<template #status="{ item }">
|
||||||
|
<BadgeComponent
|
||||||
|
:title="$t(`invoice.status.${item.row.status}`)"
|
||||||
|
:hsla-color="colorsInvoice[item.row.status] || ''"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</TableReport>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="pageState.currentTab === ViewMode.Invoice">
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>{{ $t('invoice.title') }}</template>
|
||||||
|
<template #main>
|
||||||
|
<TableReport :row="dataReportInvoice" :columns="colReport">
|
||||||
|
<template #status="{ item }">
|
||||||
|
<BadgeComponent
|
||||||
|
:title="$t(`invoice.status.${item.row.status}`)"
|
||||||
|
:hsla-color="colorsInvoice[item.row.status] || ''"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</TableReport>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="pageState.currentTab === ViewMode.Product">
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>{{ $t('productService.title') }}</template>
|
||||||
|
<template #main>
|
||||||
|
<TableReport
|
||||||
|
:row="dataReportProduct"
|
||||||
|
:columns="colReportProduct"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
</template>
|
||||||
|
<template v-if="pageState.currentTab === ViewMode.Sale">
|
||||||
|
<div class="q-gutter-y-md">
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>{{ $t('report.sale.byCustomer') }}</template>
|
||||||
|
<template #main>
|
||||||
|
<TableReport
|
||||||
|
:row="
|
||||||
|
dataReportSale?.byCustomer.map((v) => {
|
||||||
|
return {
|
||||||
|
code: v.code,
|
||||||
|
name:
|
||||||
|
v.customer.customerType === 'CORP'
|
||||||
|
? v.customerName
|
||||||
|
: $i18n.locale === 'eng'
|
||||||
|
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||||
|
: `${v.firstName} ${v.lastName}`,
|
||||||
|
|
||||||
|
_count: v._count,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:columns="colReportSale"
|
||||||
|
>
|
||||||
|
<template #name="{ item }">
|
||||||
|
{{ item.row.name }}
|
||||||
|
</template>
|
||||||
|
</TableReport>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>
|
||||||
|
{{ $t('report.sale.byProductGroup') }}
|
||||||
|
</template>
|
||||||
|
<template #main>
|
||||||
|
<TableReport
|
||||||
|
:row="
|
||||||
|
dataReportSale?.byProductGroup.map((v) => {
|
||||||
|
return {
|
||||||
|
code: v.code,
|
||||||
|
name: v.name,
|
||||||
|
_count: v._count,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:columns="colReportSale"
|
||||||
|
>
|
||||||
|
<template #name="{ item }">
|
||||||
|
{{ item.row.name }}
|
||||||
|
</template>
|
||||||
|
</TableReport>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
<Expansion default-opened>
|
||||||
|
<template #header>{{ $t('report.sale.bySale') }}</template>
|
||||||
|
<template #main>
|
||||||
|
<TableReport
|
||||||
|
:row="
|
||||||
|
dataReportSale?.bySale.map((v) => {
|
||||||
|
return {
|
||||||
|
code: v.code,
|
||||||
|
name:
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||||
|
: `${v.firstName} ${v.lastName}`,
|
||||||
|
_count: v._count,
|
||||||
|
url: v.selectedImage,
|
||||||
|
id: v.id,
|
||||||
|
gender: v.gender,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:columns="colReportSale"
|
||||||
|
>
|
||||||
|
<template #name="{ item }">
|
||||||
|
<q-avatar size="md">
|
||||||
|
<q-img
|
||||||
|
class="text-center"
|
||||||
|
:ratio="1"
|
||||||
|
:src="`${baseUrl}/user/${item.row.id}/profile-image/${item.row.url}?ts=${Date.now()}`"
|
||||||
|
>
|
||||||
|
<template #error>
|
||||||
|
<div
|
||||||
|
class="no-padding full-width full-height flex items-center justify-center"
|
||||||
|
:style="`${item.row.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
|
||||||
|
>
|
||||||
|
<q-img
|
||||||
|
v-if="item.row.gender"
|
||||||
|
:src="
|
||||||
|
item.row.gender === 'male'
|
||||||
|
? '/no-img-man.png'
|
||||||
|
: '/no-img-female.png'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<q-icon
|
||||||
|
v-else
|
||||||
|
size="sm"
|
||||||
|
name="mdi-account-outline"
|
||||||
|
style="color: white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-img>
|
||||||
|
</q-avatar>
|
||||||
|
|
||||||
|
{{ item.row.name }}
|
||||||
|
</template>
|
||||||
|
</TableReport>
|
||||||
|
</template>
|
||||||
|
</Expansion>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
64
src/pages/14_report/Table/TableReport.vue
Normal file
64
src/pages/14_report/Table/TableReport.vue
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { QTableSlots, QTableProps } from 'quasar';
|
||||||
|
|
||||||
|
const prop = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
row: QTableProps['rows'];
|
||||||
|
columns: QTableProps['columns'];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
row: () => [],
|
||||||
|
columns: () => [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-table
|
||||||
|
:rows-per-page-options="[5, 0]"
|
||||||
|
:rows="
|
||||||
|
row.map((item, i) => ({
|
||||||
|
...item,
|
||||||
|
_index: i,
|
||||||
|
}))
|
||||||
|
"
|
||||||
|
:columns
|
||||||
|
bordered
|
||||||
|
flat
|
||||||
|
selection="multiple"
|
||||||
|
class="full-width"
|
||||||
|
>
|
||||||
|
<template v-slot:header="props">
|
||||||
|
<q-tr
|
||||||
|
style="background-color: hsla(var(--info-bg) / 0.07)"
|
||||||
|
:props="props"
|
||||||
|
>
|
||||||
|
<q-th v-for="col in columns" :key="col.name" :props="props">
|
||||||
|
{{ $t(col.label) }}
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template
|
||||||
|
v-slot:body="props: {
|
||||||
|
row: any & { _index: number };
|
||||||
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||||
|
>
|
||||||
|
<q-tr :class="{ dark: $q.dark.isActive }" class="text-center">
|
||||||
|
<q-td v-for="col in columns" :align="col.align">
|
||||||
|
<!-- NOTE: custom column will starts with # -->
|
||||||
|
<template v-if="!col.name.startsWith('#')">
|
||||||
|
{{
|
||||||
|
typeof col.field === 'string'
|
||||||
|
? props.row[col.field]
|
||||||
|
: col.field(props.row)
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<slot :name="col.name.replace(/^#/, '')" :item="props" />
|
||||||
|
</template>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</template>
|
||||||
153
src/pages/14_report/constants.ts
Normal file
153
src/pages/14_report/constants.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { QTableProps } from 'quasar';
|
||||||
|
import { Invoice, Receipt } from 'src/stores/payment/types';
|
||||||
|
import {
|
||||||
|
Report,
|
||||||
|
ReportProduct,
|
||||||
|
ReportQuotation,
|
||||||
|
} from 'src/stores/report/types';
|
||||||
|
import { formatNumberDecimal } from 'src/stores/utils';
|
||||||
|
import { dateFormatJS } from 'src/utils/datetime';
|
||||||
|
|
||||||
|
export enum ViewMode {
|
||||||
|
Document = 'document',
|
||||||
|
Invoice = 'invoice',
|
||||||
|
Receipt = 'receipt',
|
||||||
|
Product = 'product',
|
||||||
|
Sale = 'sale',
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColumnsSale = {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
_count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ColumnsBySale = ColumnsSale & {
|
||||||
|
url?: string;
|
||||||
|
id?: string;
|
||||||
|
gender?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const colReportQuotation = [
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.document.code',
|
||||||
|
field: (data: ReportQuotation) => data.code,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '#status',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.document.status',
|
||||||
|
field: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createAt',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.document.createAt',
|
||||||
|
field: (data: ReportQuotation) => dateFormatJS({ date: data.createdAt }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updateAt',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.document.updateAt',
|
||||||
|
field: (data: ReportQuotation) => dateFormatJS({ date: data.updatedAt }),
|
||||||
|
},
|
||||||
|
] as const satisfies QTableProps['columns'];
|
||||||
|
|
||||||
|
export const colReport = [
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.table.code',
|
||||||
|
field: (data: Report) => data.code,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '#status',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.table.status',
|
||||||
|
field: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createAt',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.table.createAt',
|
||||||
|
field: (data: Report) => dateFormatJS({ date: data.createdAt }),
|
||||||
|
},
|
||||||
|
] as const satisfies QTableProps['columns'];
|
||||||
|
|
||||||
|
export const colReportProduct = [
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.product.code',
|
||||||
|
field: (data: ReportProduct) => data.code,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.product.name',
|
||||||
|
field: (data: ReportProduct) => data.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sale',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.product.sale',
|
||||||
|
field: (data: ReportProduct) => data.sale,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'did',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.product.did',
|
||||||
|
field: (data: ReportProduct) => data.did,
|
||||||
|
},
|
||||||
|
] as const satisfies QTableProps['columns'];
|
||||||
|
|
||||||
|
export const colReportSale = [
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
align: 'left',
|
||||||
|
label: 'report.sale.code',
|
||||||
|
field: (data: ColumnsSale) => data.code,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '#name',
|
||||||
|
align: 'left',
|
||||||
|
label: 'report.sale.name',
|
||||||
|
field: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'count',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.sale.count',
|
||||||
|
field: (data: ColumnsSale) => data._count,
|
||||||
|
},
|
||||||
|
] as const satisfies QTableProps['columns'];
|
||||||
|
|
||||||
|
export const colReportBySale = [
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
align: 'left',
|
||||||
|
label: 'report.sale.code',
|
||||||
|
field: (data: ColumnsBySale) => data.code,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '#name',
|
||||||
|
align: 'left',
|
||||||
|
label: 'report.sale.name',
|
||||||
|
field: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'count',
|
||||||
|
align: 'center',
|
||||||
|
label: 'report.sale.count',
|
||||||
|
field: (data: ColumnsBySale) => data._count,
|
||||||
|
},
|
||||||
|
] as const satisfies QTableProps['columns'];
|
||||||
|
|
||||||
|
export const pageTabs = [
|
||||||
|
{ label: 'Document', value: ViewMode.Document, by: ['user'] },
|
||||||
|
{ label: 'Invoice', value: ViewMode.Invoice, by: ['user'] },
|
||||||
|
{ label: 'Product', value: ViewMode.Product, by: ['user'] },
|
||||||
|
{ label: 'Sale', value: ViewMode.Sale, by: ['admin'] },
|
||||||
|
];
|
||||||
11
src/pages/15_dash-board/MainPage.vue
Normal file
11
src/pages/15_dash-board/MainPage.vue
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// NOTE: Library
|
||||||
|
|
||||||
|
// NOTE: Components
|
||||||
|
|
||||||
|
// NOTE: Stores & Type
|
||||||
|
|
||||||
|
// NOTE: Variable
|
||||||
|
</script>
|
||||||
|
<template></template>
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -75,16 +75,16 @@ const routes: RouteRecordRaw[] = [
|
||||||
name: 'productAndService',
|
name: 'productAndService',
|
||||||
component: () => import('pages/04_product-service/MainPage.vue'),
|
component: () => import('pages/04_product-service/MainPage.vue'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/quotation',
|
|
||||||
name: 'Quotation',
|
|
||||||
component: () => import('pages/05_quotation/MainPage.vue'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/workflow',
|
path: '/workflow',
|
||||||
name: 'Workflow',
|
name: 'Workflow',
|
||||||
component: () => import('pages/04_flow-managment/MainPage.vue'),
|
component: () => import('pages/04_flow-managment/MainPage.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/quotation',
|
||||||
|
name: 'Quotation',
|
||||||
|
component: () => import('pages/05_quotation/MainPage.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/document-management',
|
path: '/document-management',
|
||||||
name: 'document-management',
|
name: 'document-management',
|
||||||
|
|
@ -105,15 +105,20 @@ const routes: RouteRecordRaw[] = [
|
||||||
name: 'TaskOrder',
|
name: 'TaskOrder',
|
||||||
component: () => import('pages/09_task-order/MainPage.vue'),
|
component: () => import('pages/09_task-order/MainPage.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/invoice',
|
||||||
|
name: '/Invoice',
|
||||||
|
component: () => import('pages/10_invoice/MainPage.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/credit-note',
|
path: '/credit-note',
|
||||||
name: 'CreditNote',
|
name: 'CreditNote',
|
||||||
component: () => import('pages/11_credit-note/MainPage.vue'),
|
component: () => import('pages/11_credit-note/MainPage.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/invoice',
|
path: '/debit-note',
|
||||||
name: '/Invoice',
|
name: 'debitNote',
|
||||||
component: () => import('pages/10_invoice/MainPage.vue'),
|
component: () => import('pages/12_debit-note/MainPage.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/receipt',
|
path: '/receipt',
|
||||||
|
|
@ -121,9 +126,14 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: () => import('pages/13_receipt/MainPage.vue'),
|
component: () => import('pages/13_receipt/MainPage.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/debit-note',
|
path: '/report',
|
||||||
name: 'debitNote',
|
name: 'report',
|
||||||
component: () => import('pages/12_debit-note/MainPage.vue'),
|
component: () => import('pages/14_report/MainPage.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/dash-board',
|
||||||
|
name: 'dashBoard',
|
||||||
|
component: () => import('pages/15_dash-board/MainPage.vue'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
71
src/stores/report/index.ts
Normal file
71
src/stores/report/index.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { Pagination, Status } from '../types';
|
||||||
|
import { api } from 'src/boot/axios';
|
||||||
|
import { Report, ReportProduct, ReportQuotation, ReportSale } from './types';
|
||||||
|
|
||||||
|
const ENDPOINT = 'report';
|
||||||
|
|
||||||
|
export async function getReportQuotation() {
|
||||||
|
const res = await api.get<ReportQuotation[]>(`/${ENDPOINT}/quotation`);
|
||||||
|
if (res.status < 400) {
|
||||||
|
res.data;
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReportInvoice() {
|
||||||
|
const res = await api.get<Report[]>(`/${ENDPOINT}/invoice`);
|
||||||
|
if (res.status < 400) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReportReceipt() {
|
||||||
|
const res = await api.get<Report[]>(`/${ENDPOINT}/receipt`);
|
||||||
|
if (res.status < 400) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReportSale() {
|
||||||
|
const res = await api.get<ReportSale>(`/${ENDPOINT}/sale`);
|
||||||
|
|
||||||
|
if (res.status < 400) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReportProduct() {
|
||||||
|
const res = await api.get<ReportProduct[]>(`/${ENDPOINT}/Product`);
|
||||||
|
if (res.status < 400) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useReportStore = defineStore('report-store', () => {
|
||||||
|
const dataReportQuotation = ref<ReportQuotation[]>([]);
|
||||||
|
const dataReportInvoice = ref<Report[]>([]);
|
||||||
|
const dataReportReceipt = ref<Report[]>([]);
|
||||||
|
const dataReportSale = ref<ReportSale>();
|
||||||
|
const dataReportProduct = ref<ReportProduct[]>([]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dataReportQuotation,
|
||||||
|
dataReportInvoice,
|
||||||
|
dataReportReceipt,
|
||||||
|
dataReportSale,
|
||||||
|
dataReportProduct,
|
||||||
|
|
||||||
|
getReportQuotation,
|
||||||
|
getReportInvoice,
|
||||||
|
getReportReceipt,
|
||||||
|
getReportSale,
|
||||||
|
getReportProduct,
|
||||||
|
};
|
||||||
|
});
|
||||||
41
src/stores/report/types.ts
Normal file
41
src/stores/report/types.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { QuotationStatus } from 'src/stores/quotations/types';
|
||||||
|
import { ProductGroup } from '../product-service/types';
|
||||||
|
import { User } from '../user';
|
||||||
|
import { CustomerBranch } from '../customer';
|
||||||
|
|
||||||
|
export type ReportQuotation = {
|
||||||
|
updatedAt: Date | null;
|
||||||
|
createdAt: Date | null;
|
||||||
|
status: QuotationStatus;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum Status {
|
||||||
|
PaymentInProcess = 'PaymentInProcess',
|
||||||
|
PaymentSuccess = 'PaymentSuccess',
|
||||||
|
PaymentWait = 'PaymentWait',
|
||||||
|
PaymentRetry = 'PaymentRetry',
|
||||||
|
}
|
||||||
|
|
||||||
|
// use with Invoice and Receipt
|
||||||
|
export type Report = {
|
||||||
|
createdAt: Date | null;
|
||||||
|
status: Status;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReportProduct = {
|
||||||
|
updatedAt: Date | null;
|
||||||
|
createdAt: Date | null;
|
||||||
|
order: number;
|
||||||
|
did: number;
|
||||||
|
sale: number;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReportSale = {
|
||||||
|
byCustomer: (Omit<CustomerBranch, '_count'> & { _count: number })[];
|
||||||
|
bySale: (User & { _count: number })[];
|
||||||
|
byProductGroup: (Omit<ProductGroup, '_count'> & { _count: number })[];
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue