Compare commits
108 commits
1101fa68d8
...
ff5767cdd1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff5767cdd1 | ||
|
|
02c7598aec | ||
|
|
93700a2b54 | ||
|
|
d67b9b2f02 | ||
|
|
18844c70bc | ||
|
|
84d3e0d777 | ||
|
|
a0b7fb3a1b | ||
|
|
21699b14c5 | ||
|
|
8c9e9abc18 | ||
|
|
ef81522561 | ||
|
|
dfc17e9623 | ||
|
|
5c12bcbab7 | ||
|
|
9a8363091d | ||
|
|
aac82ce477 | ||
|
|
9f6d972c91 | ||
|
|
92b4db45d2 | ||
|
|
56a63185a1 | ||
|
|
28395b4f80 | ||
|
|
1d38dbc6cf | ||
|
|
a4a101712c | ||
|
|
2a2bfa3180 | ||
|
|
8cf93d0016 | ||
|
|
63036b03fd | ||
|
|
4040da58f9 | ||
|
|
cf67ed3d47 | ||
|
|
8d8ad40de1 | ||
|
|
74291c0552 | ||
|
|
88f40dcb47 | ||
|
|
03b03b4bc8 | ||
|
|
285b821c16 | ||
|
|
648ed38181 | ||
|
|
ac42ee60d8 | ||
|
|
1e6be274e2 | ||
|
|
ea21ec4632 | ||
|
|
73562a59c1 | ||
|
|
7897103a1b | ||
|
|
36cef7ceb6 | ||
|
|
461dd359b1 | ||
|
|
efeb1b51eb | ||
|
|
fd5d4b7979 | ||
|
|
181ddc8f03 | ||
|
|
d7e53b764c | ||
|
|
d95d72806d | ||
|
|
550ed55de0 | ||
|
|
145784ee40 | ||
|
|
e189b9a880 | ||
|
|
4e86a90b0e | ||
|
|
08b0dcbce0 | ||
|
|
1d5f77f3a6 | ||
|
|
82f48a4b80 | ||
|
|
d909be2fc4 | ||
|
|
586fbed4e3 | ||
|
|
0efe78a37a | ||
|
|
febfbf4828 | ||
|
|
d1bb504174 | ||
|
|
af37904ce0 | ||
|
|
0a5b6af649 | ||
|
|
71b06c82bd | ||
|
|
b1295d00ff | ||
|
|
d3e5aec842 | ||
|
|
2e813f6e88 | ||
|
|
ed5a05709a | ||
|
|
a40f9f9775 | ||
|
|
5b1ccadf92 | ||
|
|
a5d73ba1ff | ||
|
|
69f368ede1 | ||
|
|
bc5097a0a8 | ||
|
|
79abde8629 | ||
|
|
bd38c008a6 | ||
|
|
7fcb4d7744 | ||
|
|
1a8be5ac34 | ||
|
|
3efe8e19f4 | ||
|
|
ace3af2a4b | ||
|
|
f22a7e09b3 | ||
|
|
0de6921636 | ||
|
|
98ab120e56 | ||
|
|
6f2471c33b | ||
|
|
2511690d54 | ||
|
|
25b62de139 | ||
|
|
68e1abb4cb | ||
|
|
bc507b7b4c | ||
|
|
6f16964859 | ||
|
|
80f68cd702 | ||
|
|
174c30875e | ||
|
|
18d5c4ff82 | ||
|
|
d5d95648b1 | ||
|
|
12ec914603 | ||
|
|
789502c1b2 | ||
|
|
c8b4339cf6 | ||
|
|
2d94d163d2 | ||
|
|
f8b56fd37e | ||
|
|
88cabff86e | ||
|
|
3c85f955c2 | ||
|
|
50bb4638c5 | ||
|
|
0a87843b3b | ||
|
|
4fb26bf54b | ||
|
|
e2f8f3332a | ||
|
|
a24303377f | ||
|
|
416424b8eb | ||
|
|
71c1f9c770 | ||
|
|
9312701096 | ||
|
|
0e685a99f7 | ||
|
|
3646956038 | ||
|
|
1e9a5abc1c | ||
|
|
af792678dd | ||
|
|
90589b3daf | ||
|
|
fb23ec5fd4 | ||
|
|
0dca8a7029 |
119 changed files with 7439 additions and 16930 deletions
|
|
@ -1,9 +0,0 @@
|
|||
/dist
|
||||
/src-capacitor
|
||||
/src-cordova
|
||||
/.quasar
|
||||
/node_modules
|
||||
.eslintrc.cjs
|
||||
/src-ssr
|
||||
/quasar.config.*.temporary.compiled*
|
||||
/tests
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
|
||||
parserOptions: {
|
||||
parser: require.resolve('@typescript-eslint/parser'),
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
'vue/setup-compiler-macros': true,
|
||||
},
|
||||
|
||||
extends: [
|
||||
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
|
||||
// ESLint typescript rules
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
|
||||
// Uncomment any of the lines below to choose desired strictness,
|
||||
// but leave only one uncommented!
|
||||
// See https://eslint.vuejs.org/rules/#available-rules
|
||||
'plugin:vue/vue3-essential',
|
||||
|
||||
// https://github.com/prettier/eslint-config-prettier#installation
|
||||
// usage with Prettier, provided by 'eslint-config-prettier'.
|
||||
'prettier',
|
||||
],
|
||||
|
||||
plugins: [
|
||||
// required to apply rules which need type information
|
||||
'@typescript-eslint',
|
||||
|
||||
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
|
||||
// required to lint *.vue files
|
||||
'vue',
|
||||
],
|
||||
|
||||
globals: {
|
||||
ga: 'readonly', // Google Analytics
|
||||
cordova: 'readonly',
|
||||
__statics: 'readonly',
|
||||
__QUASAR_SSR__: 'readonly',
|
||||
__QUASAR_SSR_SERVER__: 'readonly',
|
||||
__QUASAR_SSR_CLIENT__: 'readonly',
|
||||
__QUASAR_SSR_PWA__: 'readonly',
|
||||
process: 'readonly',
|
||||
Capacitor: 'readonly',
|
||||
chrome: 'readonly',
|
||||
},
|
||||
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
quotes: ['warn', 'single', { avoidEscape: true }],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
},
|
||||
};
|
||||
|
|
@ -9,7 +9,7 @@ env:
|
|||
REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }}
|
||||
CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }}/${{ vars.CONTAINER_IMAGE_NAME }}:latest
|
||||
jobs:
|
||||
gitea-release:
|
||||
build-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
@ -51,7 +51,7 @@ jobs:
|
|||
"description": "**Details:**\n- Image: `${{ env.CONTAINER_IMAGE_NAME }}`\n- Deployed by: `${{ github.actor }}`",
|
||||
"color": 3066993,
|
||||
"footer": {
|
||||
"text": "Gitea Local Release Notification",
|
||||
"text": "Local Release Notification",
|
||||
"icon_url": "https://example.com/success-icon.png"
|
||||
},
|
||||
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
||||
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
"description": "**Details:**\n- Image: `${{ env.CONTAINER_IMAGE_NAME }}`\n- Attempted by: `${{ github.actor }}`",
|
||||
"color": 15158332,
|
||||
"footer": {
|
||||
"text": "Gitea Local Release Notification",
|
||||
"text": "Local Release Notification",
|
||||
"icon_url": "https://example.com/failure-icon.png"
|
||||
},
|
||||
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
||||
|
|
|
|||
12398
package-lock.json
generated
12398
package-lock.json
generated
File diff suppressed because it is too large
Load diff
56
package.json
56
package.json
|
|
@ -7,23 +7,24 @@
|
|||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.ts,.vue ./",
|
||||
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
"test": "echo \"No test specified\" && exit 0",
|
||||
"dev": "quasar dev",
|
||||
"build": "quasar build",
|
||||
"postinstall": "quasar prepare",
|
||||
"changelog:generate": "git-cliff -o CHANGELOG.md"
|
||||
},
|
||||
"dependencies": {
|
||||
"@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0",
|
||||
"@quasar/extras": "^1.16.12",
|
||||
"@tato30/vue-pdf": "^1.11.0",
|
||||
"@quasar/extras": "^1.16.17",
|
||||
"@tato30/vue-pdf": "^1.11.3",
|
||||
"@vuepic/vue-datepicker": "^8.8.1",
|
||||
"apexcharts": "^4.5.0",
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.8.4",
|
||||
"cropperjs": "^1.6.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"highlight.js": "^11.11.1",
|
||||
"keycloak-js": "^25.0.4",
|
||||
"keycloak-js": "^25.0.6",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-anchor": "^9.2.0",
|
||||
"markdown-it-highlightjs": "^4.2.0",
|
||||
|
|
@ -31,49 +32,44 @@
|
|||
"markdown-it-html5-media": "^0.7.1",
|
||||
"markdown-it-image-figures": "^2.1.1",
|
||||
"markdown-it-video": "^0.6.3",
|
||||
"mime": "^4.0.4",
|
||||
"mime": "^4.0.6",
|
||||
"moment": "^2.30.1",
|
||||
"number-to-words": "^1.2.4",
|
||||
"open-props": "^1.7.5",
|
||||
"pinia": "^2.2.2",
|
||||
"quasar": "^2.16.9",
|
||||
"signature_pad": "^5.0.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"open-props": "^1.7.14",
|
||||
"pinia": "^2.3.1",
|
||||
"quasar": "^2.18.1",
|
||||
"signature_pad": "^5.0.7",
|
||||
"tesseract.js": "^5.1.1",
|
||||
"thai-baht-text": "^2.0.5",
|
||||
"udsv": "^0.6.0",
|
||||
"uuid": "^10.0.0",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.5.13",
|
||||
"vue-dragscroll": "^4.0.6",
|
||||
"vue-i18n": "^9.14.0",
|
||||
"vue-i18n": "^11.1.2",
|
||||
"vue-pdf": "^4.3.0",
|
||||
"vue-router": "^4.4.3",
|
||||
"vue3-apexcharts": "^1.7.0"
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-tsc": "^2.2.8",
|
||||
"vue3-apexcharts": "^1.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^9.3.0",
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@quasar/app-vite": "2.0.0-beta.19",
|
||||
"@faker-js/faker": "^9.6.0",
|
||||
"@iconify/vue": "^4.3.0",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.5",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@quasar/app-vite": "^2.2.0",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/markdown-it-highlightjs": "^3.3.4",
|
||||
"@types/node": "^20.16.1",
|
||||
"@types/node": "^20.17.28",
|
||||
"@types/number-to-words": "^1.2.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.5.4",
|
||||
"vue-component-type-helpers": "^2.1.10"
|
||||
"vue-component-type-helpers": "^2.2.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^24 || ^22 || ^20 || ^18",
|
||||
"node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",
|
||||
"npm": ">= 6.13.4",
|
||||
"yarn": ">= 1.21.1"
|
||||
}
|
||||
|
|
|
|||
3642
pnpm-lock.yaml
generated
3642
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
18
postcss.config.js
Normal file
18
postcss.config.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import autoprefixer from 'autoprefixer';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
autoprefixer({
|
||||
overrideBrowserslist: [
|
||||
'last 4 Chrome versions',
|
||||
'last 4 Firefox versions',
|
||||
'last 4 Edge versions',
|
||||
'last 4 Safari versions',
|
||||
'last 4 Android versions',
|
||||
'last 4 ChromeAndroid versions',
|
||||
'last 4 FirefoxAndroid versions',
|
||||
'last 4 iOS versions',
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
BIN
public/img-group.png
Normal file
BIN
public/img-group.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
|
|
@ -1,5 +1,28 @@
|
|||
{
|
||||
"eng": {
|
||||
"visaType": [
|
||||
{
|
||||
"label": "Non-LA",
|
||||
"value": "nla"
|
||||
},
|
||||
{
|
||||
"label": "Non-B",
|
||||
"value": "nb"
|
||||
},
|
||||
{
|
||||
"label": "TV.60",
|
||||
"value": "tv60"
|
||||
},
|
||||
{
|
||||
"label": "Non-TR",
|
||||
"value": "ntr"
|
||||
},
|
||||
{
|
||||
"label": "TV.30",
|
||||
"value": "tv30"
|
||||
}
|
||||
],
|
||||
|
||||
"workerStatus": [
|
||||
{
|
||||
"label": "Normal",
|
||||
|
|
@ -154,7 +177,8 @@
|
|||
{ "label": "VS2", "value": "VS2" },
|
||||
{ "label": "WO", "value": "WO" },
|
||||
{ "label": "WP390", "value": "WP390" },
|
||||
{ "label": "WP44", "value": "WP44" }
|
||||
{ "label": "WP44", "value": "WP44" },
|
||||
{ "label": "CUST", "value": "CUST" }
|
||||
],
|
||||
|
||||
"prefix": [
|
||||
|
|
@ -183,29 +207,44 @@
|
|||
}
|
||||
],
|
||||
|
||||
"training": [
|
||||
"border": [
|
||||
{
|
||||
"label": "Myanmar Labor Training Center - Mae Sot, Tak Province",
|
||||
"label": "Mae Sot, Tak Province",
|
||||
"value": "trainingTak"
|
||||
},
|
||||
{
|
||||
"label": "Myanmar Labor Training Center - Kawthoung, Ranong Province",
|
||||
"label": "Koh Song, Ranong province",
|
||||
"value": "trainingRanong"
|
||||
},
|
||||
{
|
||||
"label": "Laos Labor Training Center - Nong Khai, Nong Khai Province",
|
||||
"label": "Nong Khai, Nong Khai Province",
|
||||
"value": "trainingNongKhai"
|
||||
},
|
||||
{
|
||||
"label": "Cambodian Labor Training Center - Aranyaprathet, Sa Kaeo Province",
|
||||
"label": "Aranyaprathet, Sa Kaeo Province",
|
||||
"value": "trainingSaKaeo"
|
||||
},
|
||||
{
|
||||
"label": "Cambodian Labor Training Center - Ban Laem, Chanthaburi Province",
|
||||
"label": "Ban Laem, Chanthaburi Province",
|
||||
"value": "trainingChanthaburi"
|
||||
}
|
||||
],
|
||||
|
||||
"training": [
|
||||
{
|
||||
"label": "The first center accepts work. and end of employment Tak Province",
|
||||
"value": "trainingTak"
|
||||
},
|
||||
{
|
||||
"label": "The first center accepts work. and end of employment Nong Khai Province",
|
||||
"value": "trainingNongKhai"
|
||||
},
|
||||
{
|
||||
"label": "The first center accepts work. and end of employment Sa Kaeo Province",
|
||||
"value": "trainingSaKaeo"
|
||||
}
|
||||
],
|
||||
|
||||
"nationality": [
|
||||
{
|
||||
"label": "Thai",
|
||||
|
|
@ -1050,6 +1089,29 @@
|
|||
},
|
||||
|
||||
"tha": {
|
||||
"visaType": [
|
||||
{
|
||||
"label": "Non-LA",
|
||||
"value": "nla"
|
||||
},
|
||||
{
|
||||
"label": "Non-B",
|
||||
"value": "nb"
|
||||
},
|
||||
{
|
||||
"label": "ผผ.60",
|
||||
"value": "tv60"
|
||||
},
|
||||
{
|
||||
"label": "Non-TR",
|
||||
"value": "ntr"
|
||||
},
|
||||
{
|
||||
"label": "ผผ.30",
|
||||
"value": "tv30"
|
||||
}
|
||||
],
|
||||
|
||||
"workerStatus": [
|
||||
{
|
||||
"label": "ปกติ",
|
||||
|
|
@ -1204,7 +1266,8 @@
|
|||
{ "label": "VS2", "value": "VS2" },
|
||||
{ "label": "WO", "value": "WO" },
|
||||
{ "label": "WP390", "value": "WP390" },
|
||||
{ "label": "WP44", "value": "WP44" }
|
||||
{ "label": "WP44", "value": "WP44" },
|
||||
{ "label": "CUST", "value": "CUST" }
|
||||
],
|
||||
|
||||
"prefix": [
|
||||
|
|
@ -1233,29 +1296,44 @@
|
|||
}
|
||||
],
|
||||
|
||||
"training": [
|
||||
"border": [
|
||||
{
|
||||
"label": "สถานที่อบรมแรงงานเมียนมา-แม่สอด จ.ตาก",
|
||||
"label": "แม่สอด จ.ตาก",
|
||||
"value": "trainingTak"
|
||||
},
|
||||
{
|
||||
"label": "สถานที่อบรมแรงงานเมียนมา-เกาะสอง จ.ระนอง",
|
||||
"label": "เกาะสอง จ.ระนอง",
|
||||
"value": "trainingRanong"
|
||||
},
|
||||
{
|
||||
"label": "สถานที่อบรมแรงงานลาว-หนองคาย จ.หนองคาย",
|
||||
"label": "หนองคาย จ.หนองคาย",
|
||||
"value": "trainingNongKhai"
|
||||
},
|
||||
{
|
||||
"label": "สถานที่อบรมแรงงานกัมพูชา-อรัญประเทศ จ.สระแก้ว",
|
||||
"label": "อรัญประเทศ จ.สระแก้ว",
|
||||
"value": "trainingSaKaeo"
|
||||
},
|
||||
{
|
||||
"label": "สถานที่อบรมแรงงานกัมพูชา-บ้านแหลม จ.จันทบุรี",
|
||||
"label": "บ้านแหลม จ.จันทบุรี",
|
||||
"value": "trainingChanthaburi"
|
||||
}
|
||||
],
|
||||
|
||||
"training": [
|
||||
{
|
||||
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดตาก",
|
||||
"value": "trainingTak"
|
||||
},
|
||||
{
|
||||
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดหนองคาย",
|
||||
"value": "trainingNongKhai"
|
||||
},
|
||||
{
|
||||
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดสระแก้ว",
|
||||
"value": "trainingSaKaeo"
|
||||
}
|
||||
],
|
||||
|
||||
"nationality": [
|
||||
{
|
||||
"label": "ไทย",
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
/* eslint-env node */
|
||||
|
||||
// Configuration for your app
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
||||
|
||||
import { configure } from 'quasar/wrappers';
|
||||
import { defineConfig } from '#q-app/wrappers';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export default configure((ctx) => {
|
||||
export default defineConfig((ctx) => {
|
||||
return {
|
||||
eslint: {
|
||||
fix: true,
|
||||
warnings: true,
|
||||
errors: true,
|
||||
},
|
||||
boot: ['i18n', 'axios', 'components'],
|
||||
css: ['app.scss'],
|
||||
extras: ['mdi-v7'],
|
||||
build: {
|
||||
target: {
|
||||
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
||||
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
|
||||
node: 'node20',
|
||||
},
|
||||
typescript: {
|
||||
vueShim: true,
|
||||
},
|
||||
vueRouterMode: 'history',
|
||||
vitePlugins: [
|
||||
[
|
||||
|
|
@ -35,7 +31,7 @@ export default configure((ctx) => {
|
|||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
open: false,
|
||||
port: 5173,
|
||||
port: 5174,
|
||||
},
|
||||
framework: {
|
||||
config: {},
|
||||
|
|
|
|||
167
reports.json
167
reports.json
|
|
@ -1,167 +0,0 @@
|
|||
{
|
||||
"config": {
|
||||
"configFile": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/playwright.config.ts",
|
||||
"rootDir": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/tests",
|
||||
"forbidOnly": false,
|
||||
"fullyParallel": true,
|
||||
"globalSetup": null,
|
||||
"globalTeardown": null,
|
||||
"globalTimeout": 0,
|
||||
"grep": {},
|
||||
"grepInvert": null,
|
||||
"maxFailures": 0,
|
||||
"metadata": {
|
||||
"actualWorkers": 1
|
||||
},
|
||||
"preserveOutput": "always",
|
||||
"reporter": [
|
||||
[
|
||||
"json",
|
||||
{
|
||||
"outputFile": "reports.json"
|
||||
}
|
||||
]
|
||||
],
|
||||
"reportSlowTests": {
|
||||
"max": 5,
|
||||
"threshold": 15000
|
||||
},
|
||||
"quiet": false,
|
||||
"projects": [
|
||||
{
|
||||
"outputDir": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/test-results",
|
||||
"repeatEach": 1,
|
||||
"retries": 0,
|
||||
"metadata": {},
|
||||
"id": "chromium",
|
||||
"name": "chromium",
|
||||
"testDir": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/tests",
|
||||
"testIgnore": [],
|
||||
"testMatch": [
|
||||
"**/*.@(spec|test).?(c|m)[jt]s?(x)"
|
||||
],
|
||||
"timeout": 30000
|
||||
}
|
||||
],
|
||||
"shard": null,
|
||||
"updateSnapshots": "missing",
|
||||
"version": "1.44.1",
|
||||
"workers": 1,
|
||||
"webServer": null
|
||||
},
|
||||
"suites": [
|
||||
{
|
||||
"title": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
|
||||
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
|
||||
"column": 0,
|
||||
"line": 0,
|
||||
"specs": [
|
||||
{
|
||||
"title": "Login",
|
||||
"ok": true,
|
||||
"tags": [],
|
||||
"tests": [
|
||||
{
|
||||
"timeout": 30000,
|
||||
"annotations": [],
|
||||
"expectedStatus": "passed",
|
||||
"projectId": "chromium",
|
||||
"projectName": "chromium",
|
||||
"results": [
|
||||
{
|
||||
"workerIndex": 4,
|
||||
"status": "passed",
|
||||
"duration": 3024,
|
||||
"errors": [],
|
||||
"stdout": [],
|
||||
"stderr": [],
|
||||
"retry": 0,
|
||||
"startTime": "2024-07-30T02:59:00.817Z",
|
||||
"attachments": []
|
||||
}
|
||||
],
|
||||
"status": "expected"
|
||||
}
|
||||
],
|
||||
"id": "8c5091bd59605f227965-8109f0f4a59e27330a76",
|
||||
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
|
||||
"line": 16,
|
||||
"column": 1
|
||||
},
|
||||
{
|
||||
"title": "Create Branch Managenment",
|
||||
"ok": true,
|
||||
"tags": [],
|
||||
"tests": [
|
||||
{
|
||||
"timeout": 30000,
|
||||
"annotations": [],
|
||||
"expectedStatus": "passed",
|
||||
"projectId": "chromium",
|
||||
"projectName": "chromium",
|
||||
"results": [
|
||||
{
|
||||
"workerIndex": 4,
|
||||
"status": "passed",
|
||||
"duration": 5091,
|
||||
"errors": [],
|
||||
"stdout": [],
|
||||
"stderr": [],
|
||||
"retry": 0,
|
||||
"startTime": "2024-07-30T02:59:05.659Z",
|
||||
"attachments": []
|
||||
}
|
||||
],
|
||||
"status": "expected"
|
||||
}
|
||||
],
|
||||
"id": "8c5091bd59605f227965-5a0d70f27623401a3479",
|
||||
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
|
||||
"line": 27,
|
||||
"column": 1
|
||||
},
|
||||
{
|
||||
"title": "Create Branch Managenment Second",
|
||||
"ok": true,
|
||||
"tags": [],
|
||||
"tests": [
|
||||
{
|
||||
"timeout": 30000,
|
||||
"annotations": [],
|
||||
"expectedStatus": "passed",
|
||||
"projectId": "chromium",
|
||||
"projectName": "chromium",
|
||||
"results": [
|
||||
{
|
||||
"workerIndex": 4,
|
||||
"status": "passed",
|
||||
"duration": 5029,
|
||||
"errors": [],
|
||||
"stdout": [],
|
||||
"stderr": [],
|
||||
"retry": 0,
|
||||
"startTime": "2024-07-30T02:59:10.755Z",
|
||||
"attachments": []
|
||||
}
|
||||
],
|
||||
"status": "expected"
|
||||
}
|
||||
],
|
||||
"id": "8c5091bd59605f227965-d619bd2184e7f07d4970",
|
||||
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
|
||||
"line": 52,
|
||||
"column": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"stats": {
|
||||
"startTime": "2024-07-30T02:59:00.334Z",
|
||||
"duration": 15556.794999999925,
|
||||
"expected": 3,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import axios, { AxiosInstance } from 'axios';
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import { defineBoot } from '#q-app/wrappers';
|
||||
import { getToken } from 'src/services/keycloak';
|
||||
import { dialog } from 'stores/utils';
|
||||
import useLoader from 'stores/loader';
|
||||
import useFlowStore from 'src/stores/flow';
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
$axios: AxiosInstance;
|
||||
$api: AxiosInstance;
|
||||
|
|
@ -24,10 +24,10 @@ function parseError(
|
|||
status: number,
|
||||
body?: { status: number; message: string; code: string },
|
||||
) {
|
||||
if (status === 422) return 'invalideData';
|
||||
if (status === 422) return 'invalidData';
|
||||
if (body && body.code) return body.code;
|
||||
|
||||
return 'errorOccure';
|
||||
return 'errorOccurred';
|
||||
}
|
||||
|
||||
api.interceptors.request.use(async (config) => {
|
||||
|
|
@ -64,7 +64,7 @@ api.interceptors.response.use(
|
|||
},
|
||||
);
|
||||
|
||||
export default boot(({ app }) => {
|
||||
export default defineBoot(({ app }) => {
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
||||
app.config.globalProperties.$axios = axios;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { boot } from 'quasar/wrappers';
|
||||
import { defineBoot } from '#q-app/wrappers';
|
||||
import VueDatePicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
import GlobalDialog from 'components/GlobalDialog.vue';
|
||||
|
|
@ -6,7 +6,7 @@ import GlobalLoading from 'components/GlobalLoading.vue';
|
|||
import VueDragscroll from 'vue-dragscroll';
|
||||
import VueApexCharts from 'vue3-apexcharts';
|
||||
|
||||
export default boot(({ app }) => {
|
||||
export default defineBoot(({ app }) => {
|
||||
app.component('global-dialog', GlobalDialog);
|
||||
app.component('global-loading', GlobalLoading);
|
||||
app.component('VueDatePicker', VueDatePicker);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { boot } from 'quasar/wrappers';
|
||||
import { defineBoot } from '#q-app/wrappers';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
import messages from 'src/i18n';
|
||||
import { Lang } from 'src/utils/ui';
|
||||
|
||||
export type MessageLanguages = keyof typeof messages;
|
||||
// Type-define 'eng' as the master schema for the resource
|
||||
|
|
@ -21,16 +22,17 @@ declare module 'vue-i18n' {
|
|||
}
|
||||
/* eslint-enable @typescript-eslint/no-empty-interface */
|
||||
|
||||
export const i18n = createI18n({
|
||||
export const i18n = createI18n<
|
||||
{ message: MessageSchema },
|
||||
MessageLanguages,
|
||||
false
|
||||
>({
|
||||
locale: 'tha',
|
||||
legacy: false,
|
||||
messages: {
|
||||
'en-US': {},
|
||||
...messages,
|
||||
},
|
||||
messages,
|
||||
});
|
||||
|
||||
export default boot(({ app }) => {
|
||||
export default defineBoot(({ app }) => {
|
||||
// Set i18n instance on app
|
||||
app.use(i18n);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ defineProps<{
|
|||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
view?: boolean;
|
||||
single?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -121,7 +122,7 @@ watch(
|
|||
/>
|
||||
{{ $t(`${title}`) }}
|
||||
<AddButton
|
||||
v-if="!readonly"
|
||||
v-if="!readonly && !single"
|
||||
id="btn-add-bank"
|
||||
icon-only
|
||||
class="q-ml-sm"
|
||||
|
|
@ -141,7 +142,10 @@ watch(
|
|||
style="padding-block: 0.01px"
|
||||
spaced="lg"
|
||||
/>
|
||||
<span class="col-12 app-text-muted-2 flex justify-between items-center">
|
||||
<span
|
||||
v-if="!single"
|
||||
class="col-12 app-text-muted-2 flex justify-between items-center"
|
||||
>
|
||||
{{ `${$t('branch.form.bankAccountNo')} ${i + 1}` }}
|
||||
<div class="row items-center">
|
||||
<div style="height: 30.8px" />
|
||||
|
|
@ -172,7 +176,8 @@ watch(
|
|||
</span>
|
||||
|
||||
<div
|
||||
class="bordered q-mr-sm rounded col text-center overflow-hidden"
|
||||
v-if="!single"
|
||||
class="bordered q-mr-sm rounded col-4 text-center overflow-hidden"
|
||||
:class="{ 'pointer-none': readonly, 'q-my-sm': $q.screen.lt.md }"
|
||||
>
|
||||
<ImageHover
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import useUserStore from 'stores/user';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { UserAttachmentDelete } from 'stores/user/types';
|
||||
import { dialog, selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { UserAttachmentDelete, AgencyStatus } from 'stores/user/types';
|
||||
import { dialog } from 'stores/utils';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { QSelect } from 'quasar';
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
import SelectInput from 'src/components/shared/SelectInput.vue';
|
||||
|
||||
import SelectOffice from 'components/shared/select-muliple/SelectOffice.vue';
|
||||
|
||||
|
|
@ -29,15 +29,16 @@ const discountCondition = defineModel<string | null | undefined>(
|
|||
const sourceNationality = defineModel<string | null | undefined>(
|
||||
'sourceNationality',
|
||||
);
|
||||
const importNationality = defineModel<string | null | undefined>(
|
||||
const importNationality = defineModel<string[] | null | undefined>(
|
||||
'importNationality',
|
||||
);
|
||||
const trainingPlace = defineModel<string | null | undefined>('trainingPlace');
|
||||
const checkpoint = defineModel<string | null | undefined>('checkPoint');
|
||||
const checkpointEN = defineModel<string | null | undefined>('checkPointEn');
|
||||
const agencyFile = defineModel<File[]>('agencyFile');
|
||||
const agencyFileList =
|
||||
defineModel<{ name: string; url: string }[]>('agencyFileList');
|
||||
const checkpoint = defineModel<string | null | undefined>('checkpoint');
|
||||
const userFile = defineModel<File[]>('userFile');
|
||||
const userFileList =
|
||||
defineModel<{ name: string; url: string }[]>('userFileList');
|
||||
const remark = defineModel<string | null | undefined>('remark');
|
||||
const agencyStatus = defineModel<string | null | undefined>('agencyStatus');
|
||||
|
||||
const attachmentRef = ref();
|
||||
|
||||
|
|
@ -69,66 +70,12 @@ function deleteFile(name: string) {
|
|||
userStore.deleteAttachment(userId.value, payload);
|
||||
const result = await userStore.fetchAttachment(userId.value);
|
||||
if (result) {
|
||||
agencyFileList.value = result;
|
||||
userFileList.value = result;
|
||||
}
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
const nationalityOptions = ref<Record<string, unknown>[]>([]);
|
||||
let nationalityFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
const trainingPlaceOptions = ref<Record<string, unknown>[]>([]);
|
||||
let trainingPlaceFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
const responsibleAreaOptions = ref<Record<string, unknown>[]>([]);
|
||||
let responsibleAreaFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
onMounted(() => {
|
||||
if (optionStore.globalOption?.nationality) {
|
||||
nationalityFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.nationality),
|
||||
nationalityOptions,
|
||||
'label',
|
||||
);
|
||||
trainingPlaceFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.training),
|
||||
trainingPlaceOptions,
|
||||
'label',
|
||||
);
|
||||
responsibleAreaFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.area),
|
||||
responsibleAreaOptions,
|
||||
'label',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
nationalityFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.nationality),
|
||||
nationalityOptions,
|
||||
'label',
|
||||
);
|
||||
trainingPlaceFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.training),
|
||||
trainingPlaceOptions,
|
||||
'label',
|
||||
);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
|
|
@ -186,11 +133,12 @@ watch(
|
|||
/>
|
||||
|
||||
<SelectOffice
|
||||
v-if="userType === 'MESSENGER'"
|
||||
for="input-responsible-area"
|
||||
v-model:value="responsibleArea"
|
||||
v-if="userType === 'MESSENGER'"
|
||||
:readonly="readonly"
|
||||
:label="$t('personnel.form.responsibleArea')"
|
||||
class="col"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -218,207 +166,171 @@ watch(
|
|||
class="row col-12 q-col-gutter-sm"
|
||||
style="margin-left: 0px; padding-left: 0px"
|
||||
>
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
class="col-md-3 col-6"
|
||||
<SelectInput
|
||||
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
|
||||
id="input-source-nationality"
|
||||
for="input-source-nationality"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:option="optionStore.globalOption.nationality"
|
||||
class="col-md-3 col-6"
|
||||
:readonly
|
||||
clearable
|
||||
:label="$t('personnel.form.sourceNationality')"
|
||||
:options="nationalityOptions"
|
||||
@filter="nationalityFilter"
|
||||
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (sourceNationality = v) : '')
|
||||
"
|
||||
@clear="sourceNationality = ''"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
class="col-md-3 col-6"
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
v-model="importNationality"
|
||||
id="input-import-nationality"
|
||||
for="input-import-nationality"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:label="$t('personnel.form.importNationality')"
|
||||
:options="nationalityOptions"
|
||||
@filter="nationalityFilter"
|
||||
:model-value="readonly ? importNationality || '-' : importNationality"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (importNationality = v) : '')
|
||||
"
|
||||
@clear="importNationality = ''"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-select
|
||||
outlined
|
||||
:option="optionStore.globalOption.nationality"
|
||||
class="col-md-3 col-6"
|
||||
:readonly
|
||||
multiple
|
||||
:hideSelected="false"
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="label"
|
||||
class="col-md-6 col-12"
|
||||
id="select-trainig-place"
|
||||
for="select-trainig-place"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:label="$t('personnel.form.trainingPlace')"
|
||||
:options="trainingPlaceOptions"
|
||||
@filter="trainingPlaceFilter"
|
||||
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (trainingPlace = v) : '')
|
||||
"
|
||||
@clear="trainingPlace = ''"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
for="input-checkpoint"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:label="$t('personnel.form.checkpoint')"
|
||||
class="col-6"
|
||||
fillInput
|
||||
:label="$t('personnel.form.importNationality')"
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
:model-value="readonly ? checkpoint || '-' : checkpoint"
|
||||
id="select-checkpoint"
|
||||
for="select-checkpoint"
|
||||
:option="optionStore.globalOption.border"
|
||||
class="col-md-6 col-12"
|
||||
:readonly
|
||||
:label="$t('personnel.form.checkpoint')"
|
||||
clearable
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
|
||||
"
|
||||
@clear="checkpoint = ''"
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
|
||||
id="select-trainig-place"
|
||||
for="select-trainig-place"
|
||||
:option="optionStore.globalOption.training"
|
||||
class="col-md-8 col-12"
|
||||
:readonly
|
||||
:label="$t('personnel.form.trainingPlace')"
|
||||
clearable
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (trainingPlace = v) : '')
|
||||
"
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
:model-value="readonly ? agencyStatus || '-' : agencyStatus"
|
||||
id="select-checkpoint-en"
|
||||
for="select-checkpoint-en"
|
||||
:option="[
|
||||
{ label: $t('personnel.form.normal'), value: AgencyStatus.Normal },
|
||||
{
|
||||
label: $t('personnel.form.canceled'),
|
||||
value: AgencyStatus.Canceled,
|
||||
},
|
||||
{
|
||||
label: $t('personnel.form.blacklist'),
|
||||
value: AgencyStatus.Blacklist,
|
||||
},
|
||||
]"
|
||||
class="col-md-4 col-12"
|
||||
:readonly
|
||||
:label="$t('personnel.form.agencyStatus')"
|
||||
clearable
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (agencyStatus = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
for="input-checkpoint-en"
|
||||
for="input-discount-condition"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:label="$t('personnel.form.checkpointEN')"
|
||||
class="col-6"
|
||||
:model-value="readonly ? checkpointEN || '-' : checkpointEN"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (checkpointEN = v) : '')
|
||||
"
|
||||
@clear="checkpointEN = ''"
|
||||
/>
|
||||
<q-file
|
||||
ref="attachmentRef"
|
||||
for="input-attachment"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
multiple
|
||||
append
|
||||
:label="$t('personnel.form.attachment')"
|
||||
:readonly
|
||||
:label="$t('general.remark')"
|
||||
class="col-12"
|
||||
v-model="agencyFile"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<Icon
|
||||
icon="material-symbols:attach-file"
|
||||
width="20px"
|
||||
style="color: var(--brand-1)"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:file="file">
|
||||
<div class="row full-width items-center">
|
||||
<span class="col ellipsis">
|
||||
{{ file.file.name }}
|
||||
</span>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
flat
|
||||
padding="2 2"
|
||||
class="app-text-muted"
|
||||
icon="mdi-close-circle"
|
||||
@click.stop="attachmentRef.removeAtIndex(file.index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</q-file>
|
||||
type="textarea"
|
||||
:model-value="readonly ? remark || '-' : remark"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (remark = v) : '')
|
||||
"
|
||||
@clear="remark = ''"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="agencyFileList && agencyFileList?.length > 0" class="col-12">
|
||||
<q-list bordered separator class="rounded" style="padding: 0">
|
||||
<q-item
|
||||
id="attachment-file"
|
||||
for="attachment-file"
|
||||
v-for="item in agencyFileList"
|
||||
clickable
|
||||
:key="item.url"
|
||||
class="items-center row"
|
||||
@click="() => openNewTab(item.url)"
|
||||
>
|
||||
<q-item-section>
|
||||
<div class="row items-center justify-between">
|
||||
<div class="col">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<q-btn
|
||||
id="delete-file"
|
||||
v-if="!readonly && userId"
|
||||
rounded
|
||||
flat
|
||||
dense
|
||||
unelevated
|
||||
size="md"
|
||||
icon="mdi-trash-can-outline"
|
||||
class="app-text-negative"
|
||||
@click.stop="deleteFile(item.name)"
|
||||
/>
|
||||
<q-file
|
||||
v-if="userType"
|
||||
ref="attachmentRef"
|
||||
for="input-attachment"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
multiple
|
||||
append
|
||||
:label="$t('personnel.form.attachment')"
|
||||
class="col"
|
||||
v-model="userFile"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<Icon
|
||||
icon="material-symbols:attach-file"
|
||||
width="20px"
|
||||
style="color: var(--brand-1)"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:file="file">
|
||||
<div class="row full-width items-center">
|
||||
<span class="col ellipsis">
|
||||
{{ file.file.name }}
|
||||
</span>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
flat
|
||||
padding="2 2"
|
||||
class="app-text-muted"
|
||||
icon="mdi-close-circle"
|
||||
@click.stop="attachmentRef.removeAtIndex(file.index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</q-file>
|
||||
|
||||
<div v-if="userFileList && userFileList?.length > 0" class="col-12">
|
||||
<q-list bordered separator class="rounded" style="padding: 0">
|
||||
<q-item
|
||||
id="attachment-file"
|
||||
for="attachment-file"
|
||||
v-for="item in userFileList"
|
||||
clickable
|
||||
:key="item.url"
|
||||
class="items-center row"
|
||||
@click="() => openNewTab(item.url)"
|
||||
>
|
||||
<q-item-section>
|
||||
<div class="row items-center justify-between">
|
||||
<div class="col">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
<q-btn
|
||||
id="delete-file"
|
||||
v-if="!readonly && userId"
|
||||
rounded
|
||||
flat
|
||||
dense
|
||||
unelevated
|
||||
size="md"
|
||||
icon="mdi-trash-can-outline"
|
||||
class="app-text-negative"
|
||||
@click.stop="deleteFile(item.name)"
|
||||
/>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { QSelect } from 'quasar';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { calculateAge, disabledAfterToday } from 'src/utils/datetime';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { capitalize } from 'vue';
|
||||
import { watch } from 'vue';
|
||||
import SelectInput from '../shared/SelectInput.vue';
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
|
@ -23,6 +21,8 @@ const midNameEN = defineModel<string | null>('midNameEn');
|
|||
const citizenId = defineModel<string>('citizenId');
|
||||
const citizenIssue = defineModel<Date | null>('citizenIssue');
|
||||
const citizenExpire = defineModel<Date | null>('citizenExpire');
|
||||
const contactName = defineModel<string>('contactName');
|
||||
const contactTel = defineModel<string>('contactTel');
|
||||
|
||||
const props = defineProps<{
|
||||
dense?: boolean;
|
||||
|
|
@ -30,73 +30,19 @@ const props = defineProps<{
|
|||
readonly?: boolean;
|
||||
separator?: boolean;
|
||||
employee?: boolean;
|
||||
agency?: boolean;
|
||||
title?: string;
|
||||
prefixId: string;
|
||||
hideNameEn?: boolean;
|
||||
}>();
|
||||
|
||||
const prefixNameOptions = ref<Record<string, unknown>[]>([]);
|
||||
let prefixNameFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
const genderOptions = ref<Record<string, unknown>[]>([]);
|
||||
let genderFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
const nationalityOptions = ref<Record<string, unknown>[]>([]);
|
||||
let nationalityFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
function matPreFixName() {
|
||||
function matchPreFixName() {
|
||||
if (gender.value === 'male') prefixName.value = 'mr';
|
||||
if (gender.value === 'female') prefixName.value = 'mrs';
|
||||
if (gender.value === 'female' && prefixName.value === 'mr') {
|
||||
prefixName.value = 'mrs';
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
nationalityFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.nationality),
|
||||
nationalityOptions,
|
||||
'label',
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
nationalityFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.nationality),
|
||||
nationalityOptions,
|
||||
'label',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => prefixName.value,
|
||||
(v) => {
|
||||
|
|
@ -110,7 +56,7 @@ watch(
|
|||
() => gender.value,
|
||||
() => {
|
||||
if (props.readonly) return;
|
||||
matPreFixName();
|
||||
matchPreFixName();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
|
@ -150,40 +96,19 @@ watch(
|
|||
for="input-citizen-id"
|
||||
/>
|
||||
<div class="col-12 row" style="display: flex; gap: var(--size-2)">
|
||||
<q-select
|
||||
outlined
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
<SelectInput
|
||||
hide-dropdown-icon
|
||||
autocomplete="off"
|
||||
class="col-md-1 col-6"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="prefixNameOptions"
|
||||
:readonly
|
||||
:option="optionStore.globalOption?.prefix"
|
||||
:id="`${prefixId}-select-prefix-name`"
|
||||
:for="`${prefixId}-select-prefix-name`"
|
||||
:label="$t('personnel.form.prefixName')"
|
||||
@filter="prefixNameFilter"
|
||||
:model-value="readonly ? prefixName || '-' : prefixName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
||||
:rules="
|
||||
agency ? [] : [(val: string) => !!val || $t('form.error.required')]
|
||||
"
|
||||
@clear="prefixName = ''"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
:label="$t('personnel.form.prefixName')"
|
||||
class="col-md-1 col-6"
|
||||
v-model="prefixName"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
:for="`${prefixId}-input-first-name`"
|
||||
|
|
@ -194,7 +119,11 @@ watch(
|
|||
class="col"
|
||||
:label="$t('personnel.form.firstName')"
|
||||
v-model="firstName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
:rules="
|
||||
employee || agency
|
||||
? []
|
||||
: [(val: string) => !!val || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
@ -229,24 +158,16 @@ watch(
|
|||
class="col-12 row"
|
||||
style="display: flex; gap: var(--size-2)"
|
||||
>
|
||||
<q-input
|
||||
:for="`${prefixId}-input-first-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
:readonly="readonly"
|
||||
:disable="!readonly"
|
||||
<SelectInput
|
||||
hide-dropdown-icon
|
||||
:readonly
|
||||
:option="optionStore.rawOption?.eng.prefix"
|
||||
:id="`${prefixId}-select-prefix-name-en`"
|
||||
:for="`${prefixId}-select-prefix-name-en`"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
:label="$t('personnel.form.prefixName')"
|
||||
class="col-md-1 col-6"
|
||||
label="Title"
|
||||
:model-value="
|
||||
readonly
|
||||
? capitalize(prefixName || '') || '-'
|
||||
: capitalize(prefixName || '')
|
||||
"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
||||
"
|
||||
@clear="prefixName = ''"
|
||||
v-model="prefixName"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
@ -287,10 +208,16 @@ watch(
|
|||
class="col"
|
||||
label="Surname"
|
||||
v-model="lastNameEN"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!val || /^[A-Za-z\s]+$/.test(val) || $t('form.error.letterOnly'),
|
||||
]"
|
||||
:rules="
|
||||
employee
|
||||
? []
|
||||
: [
|
||||
(val: string) =>
|
||||
!val ||
|
||||
/^[A-Za-z\s]+$/.test(val) ||
|
||||
$t('form.error.letterOnly'),
|
||||
]
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -351,39 +278,16 @@ watch(
|
|||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-select
|
||||
<SelectInput
|
||||
v-if="!employee"
|
||||
outlined
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
autocomplete="off"
|
||||
class="col-md-2 col-6"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="genderOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:readonly
|
||||
:option="optionStore.globalOption?.gender"
|
||||
:id="`${prefixId}-select-gender`"
|
||||
:for="`${prefixId}-select-gender`"
|
||||
:label="$t('form.gender')"
|
||||
@filter="genderFilter"
|
||||
:model-value="readonly ? gender || '-' : gender"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (gender = v) : '')"
|
||||
@clear="gender = ''"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
class="col-md-2 col-6"
|
||||
v-model="gender"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
v-model="birthDate"
|
||||
|
|
@ -392,11 +296,15 @@ watch(
|
|||
:readonly="readonly"
|
||||
:label="$t('form.birthDate')"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', { field: $t('form.birthDate') }),
|
||||
]"
|
||||
:rules="
|
||||
employee
|
||||
? []
|
||||
: [
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', { field: $t('form.birthDate') }),
|
||||
]
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
@ -456,72 +364,67 @@ watch(
|
|||
"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
<SelectInput
|
||||
v-if="employee"
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
autocomplete="off"
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
class="col-md-2 col-6"
|
||||
:dense="dense"
|
||||
v-model="gender"
|
||||
:readonly="readonly"
|
||||
:options="genderOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:readonly
|
||||
:option="optionStore.globalOption?.gender"
|
||||
:id="`${prefixId}-select-gender`"
|
||||
:for="`${prefixId}-select-gender`"
|
||||
:label="$t('form.gender')"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
@filter="genderFilter"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-select
|
||||
v-if="employee"
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
autocomplete="off"
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
v-model="nationality"
|
||||
class="col-md-2 col-6"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="nationalityOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="gender"
|
||||
/>
|
||||
<SelectInput
|
||||
v-if="employee"
|
||||
:readonly
|
||||
:option="optionStore.globalOption.nationality"
|
||||
:id="`${prefixId}-select-nationality`"
|
||||
:for="`${prefixId}-select-nationality`"
|
||||
:label="$t('general.nationality')"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
@filter="nationalityFilter"
|
||||
class="col-md-2 col-6"
|
||||
v-model="nationality"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<q-input
|
||||
v-if="agency"
|
||||
for="input-agencies-contact-name"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-4 col-12"
|
||||
:label="$t('personnel.form.contactName')"
|
||||
:model-value="readonly ? contactName || '-' : contactName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (contactName = v) : '')
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
v-if="agency"
|
||||
for="input-agencies-contact-tel"
|
||||
id="input-agencies-contact-tel"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-4 col-12"
|
||||
:label="$t('personnel.form.contactTel')"
|
||||
:model-value="readonly ? contactTel || '-' : contactTel"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (contactTel = v) : '')
|
||||
"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-select>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
:readonly="readonly || employeeOther.statusSave"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('form.firstName')"
|
||||
:label="$t('general.nativeLanguage', { msg: $t('form.firstName') })"
|
||||
:model-value="employeeOther.fatherFirstName"
|
||||
@update:model-value="
|
||||
(v) =>
|
||||
|
|
@ -122,7 +122,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
:readonly="readonly || employeeOther.statusSave"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('form.lastName')"
|
||||
:label="$t('general.nativeLanguage', { msg: $t('form.lastName') })"
|
||||
:model-value="employeeOther.fatherLastName"
|
||||
@update:model-value="
|
||||
(v) =>
|
||||
|
|
@ -177,7 +177,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
:readonly="readonly || employeeOther.statusSave"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('form.firstName')"
|
||||
:label="$t('general.nativeLanguage', { msg: $t('form.firstName') })"
|
||||
:model-value="employeeOther.motherFirstName"
|
||||
@update:model-value="
|
||||
(v) =>
|
||||
|
|
@ -193,7 +193,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
:readonly="readonly || employeeOther.statusSave"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('form.lastName')"
|
||||
:label="$t('general.nativeLanguage', { msg: $t('form.lastName') })"
|
||||
:model-value="employeeOther.motherLastName"
|
||||
@update:model-value="
|
||||
(v) =>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ onMounted(() => {
|
|||
'label',
|
||||
);
|
||||
passportIssuingCountryFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.nationality),
|
||||
ref(optionStore.rawOption?.eng.nationality),
|
||||
passportIssuingCountryOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -121,13 +121,13 @@ onMounted(() => {
|
|||
);
|
||||
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.gender),
|
||||
ref(optionStore.rawOption?.eng.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
nationalityFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.nationality),
|
||||
ref(optionStore.rawOption?.eng.nationality),
|
||||
nationalityOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -152,7 +152,7 @@ watch(
|
|||
);
|
||||
|
||||
passportIssuingCountryFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.nationality),
|
||||
ref(optionStore.rawOption?.eng.nationality),
|
||||
passportIssuingCountryOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -164,13 +164,13 @@ watch(
|
|||
);
|
||||
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.gender),
|
||||
ref(optionStore.rawOption?.eng.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
nationalityFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.nationality),
|
||||
ref(optionStore.rawOption?.eng.nationality),
|
||||
nationalityOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -258,7 +258,7 @@ watch(
|
|||
:options="workerStatusOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:for="`${prefixId}-select-visa-type`"
|
||||
:label="$t('customerEmployee.form.workerType')"
|
||||
:label="$t('customerEmployee.form.workerStatus')"
|
||||
@filter="workerStatusFilter"
|
||||
:model-value="readonly ? workerStatus || '-' : workerStatus"
|
||||
@update:model-value="
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ const entryCount = defineModel<number>('entryCount');
|
|||
const issuePlace = defineModel<string>('issuePlace');
|
||||
const issueCountry = defineModel<string>('issueCountry');
|
||||
const issueDate = defineModel<Date | null | string>('visaIssueDate');
|
||||
const type = defineModel<string>('visaType');
|
||||
const type = defineModel<string>('type');
|
||||
const expireDate = defineModel<Date>('expireDate');
|
||||
const remark = defineModel<string>('remark');
|
||||
const workerType = defineModel<string>('workerType');
|
||||
const number = defineModel<string>('visaNumber');
|
||||
const number = defineModel<string>('number');
|
||||
|
||||
const calculatedVisaDate = computed(() => {
|
||||
if (!issueDate.value) return undefined;
|
||||
|
|
@ -78,6 +78,12 @@ onMounted(async () => {
|
|||
await fetchProvince();
|
||||
});
|
||||
|
||||
const visaIssueCountryOptions = ref<Record<string, unknown>[]>([]);
|
||||
let visaIssueCountryFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
const visaTypeOptions = ref<Record<string, unknown>[]>([]);
|
||||
let visaTypeFilter: (
|
||||
value: string,
|
||||
|
|
@ -92,11 +98,17 @@ let workerTypeFilter: (
|
|||
|
||||
onMounted(() => {
|
||||
visaTypeFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.nationality),
|
||||
ref(optionStore.globalOption?.visaType),
|
||||
visaTypeOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
visaIssueCountryFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.nationality),
|
||||
visaIssueCountryOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
workerTypeFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.workerType),
|
||||
workerTypeOptions,
|
||||
|
|
@ -107,8 +119,14 @@ onMounted(() => {
|
|||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
visaIssueCountryFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.nationality),
|
||||
visaIssueCountryOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
visaTypeFilter = selectFilterOptionRefMod(
|
||||
optionStore.globalOption.nationality,
|
||||
optionStore.globalOption.visaType,
|
||||
visaTypeOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -422,11 +440,11 @@ watch(
|
|||
class="col-md-4 col-6"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="visaTypeOptions"
|
||||
:options="visaIssueCountryOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:for="`${prefixId}-select-issue-country`"
|
||||
:label="$t('customerEmployee.form.issueCountry')"
|
||||
@filter="visaTypeFilter"
|
||||
@filter="visaIssueCountryFilter"
|
||||
:model-value="readonly ? issueCountry || '-' : issueCountry"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (issueCountry = v) : '')
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { QSelect } from 'quasar';
|
|||
import { CustomerBranch } from 'stores/customer/types';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import SelectCustomer from 'components/shared/select/SelectCustomer.vue';
|
||||
import {
|
||||
EditButton,
|
||||
DeleteButton,
|
||||
|
|
@ -11,6 +12,7 @@ import {
|
|||
} from 'components/button';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { formatAddress } from 'src/utils/address';
|
||||
|
||||
const { locale } = useI18n();
|
||||
|
||||
|
|
@ -21,6 +23,13 @@ const optionsBranch = defineModel<{ id: string; name: string }[]>(
|
|||
);
|
||||
|
||||
// employee
|
||||
|
||||
const customerBranchId = defineModel<string>('customerBranchId');
|
||||
|
||||
const currentCustomerBranch = defineModel<CustomerBranch>(
|
||||
'currentCustomerBranch',
|
||||
);
|
||||
|
||||
const customerBranch = defineModel<{
|
||||
id: string;
|
||||
address: string;
|
||||
|
|
@ -107,180 +116,14 @@ defineEmits<{
|
|||
</div>
|
||||
|
||||
<div class="col-12 row" style="gap: var(--size-2)">
|
||||
<q-select
|
||||
:id="`${prefixId}-select-employer-branch`"
|
||||
:for="`${prefixId}-select-employer-branch`"
|
||||
:use-input="!customerBranch"
|
||||
autocomplete="off"
|
||||
input-debounce="0"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
<SelectCustomer
|
||||
v-model:value="customerBranchId"
|
||||
v-model:value-option="currentCustomerBranch"
|
||||
:label="$t('customer.form.branchCode')"
|
||||
v-model="customerBranch"
|
||||
:option-value="
|
||||
(v) => ({
|
||||
id: v.id,
|
||||
address: v.address,
|
||||
addressEN: v.addressEN,
|
||||
provinceId: v.provinceId,
|
||||
districtId: v.districtId,
|
||||
subDistrictId: v.subDistrictId,
|
||||
zipCode: v.zipCode,
|
||||
})
|
||||
"
|
||||
emit-value
|
||||
map-options
|
||||
:options="employeeOwnerOption"
|
||||
@filter="(val, update) => $emit('filterOwnerBranch', val, update)"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('customerEmployee.branch'),
|
||||
}),
|
||||
]"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item
|
||||
v-if="scope.opt"
|
||||
v-bind="scope.itemProps"
|
||||
class="row items-start col-12 no-padding"
|
||||
>
|
||||
<div class="q-ma-sm">
|
||||
<i class="isax isax-frame5" style="color: var(--brand-1)" />
|
||||
</div>
|
||||
<div class="q-mt-sm">
|
||||
<div>
|
||||
<span v-if="scope.opt.customer.customerType">
|
||||
<span style="font-weight: 600">
|
||||
{{
|
||||
scope.opt.customer.customerType === 'CORP'
|
||||
? $t('customer.form.registerName')
|
||||
: $t('customer.form.ownerName')
|
||||
}}:
|
||||
</span>
|
||||
{{
|
||||
scope.opt.customer.customerType === 'CORP'
|
||||
? $i18n.locale === 'eng'
|
||||
? scope.opt.registerNameEN
|
||||
: scope.opt.registerName
|
||||
: $i18n.locale === 'eng'
|
||||
? `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstNameEN} ${scope.opt.lastNameEN}` ||
|
||||
'-'
|
||||
: `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstName} ${scope.opt.lastName}` ||
|
||||
'-'
|
||||
}}
|
||||
({{ scope.opt.code }})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-caption app-text-muted-2 q-mb-xs">
|
||||
<span v-if="scope.opt.customer" class="col column">
|
||||
{{
|
||||
$t(
|
||||
`branch.form.title.${scope.opt.code.endsWith('-00') ? 'branchHQLabel' : 'branchLabel'}`,
|
||||
)
|
||||
}}
|
||||
|
||||
{{
|
||||
!scope.opt.code.endsWith('-00')
|
||||
? +scope.opt.code.split('-')[1]
|
||||
: ''
|
||||
}}
|
||||
</span>
|
||||
<span v-if="scope.opt.province" class="col">
|
||||
{{ $t('general.address') }}
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
|
||||
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
|
||||
}}
|
||||
{{ scope.opt.subDistrict?.zipCode || '' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</q-item>
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template v-slot:selected-item="scope">
|
||||
<div
|
||||
v-if="scope.opt"
|
||||
class="row items-center no-wrap"
|
||||
style="width: 1px"
|
||||
>
|
||||
<div class="q-mr-sm">
|
||||
<span style="font-weight: 600">
|
||||
{{
|
||||
scope.opt.customer.customerType === 'CORP'
|
||||
? $t('customer.form.registerName')
|
||||
: $t('customer.form.ownerName')
|
||||
}}:
|
||||
</span>
|
||||
{{
|
||||
scope.opt.customer.customerType === 'CORP'
|
||||
? $i18n.locale === 'eng'
|
||||
? scope.opt.registerNameEN
|
||||
: scope.opt.registerName
|
||||
: $i18n.locale === 'eng'
|
||||
? `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstNameEN} ${scope.opt.lastNameEN}` ||
|
||||
'-'
|
||||
: `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstName} ${scope.opt.lastName}` ||
|
||||
'-'
|
||||
}}
|
||||
({{ scope.opt.code }})
|
||||
</div>
|
||||
<div
|
||||
class="text-caption app-text-muted-2"
|
||||
v-if="scope.opt.customer && scope.opt.province"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
`branch.form.title.${scope.opt.code.endsWith('-00') ? 'branchHQLabel' : 'branchLabel'}`,
|
||||
)
|
||||
}}
|
||||
|
||||
{{
|
||||
!scope.opt.code.endsWith('-00')
|
||||
? +scope.opt.code.split('-')[1]
|
||||
: ''
|
||||
}}
|
||||
|
||||
{{ $t('general.address') }}
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
|
||||
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
|
||||
}}
|
||||
{{ scope.opt.subDistrict?.zipCode || '' }}
|
||||
<q-tooltip v-if="scope.opt.customer && scope.opt.province">
|
||||
{{ $t('customerBranch.form.title') }}:
|
||||
|
||||
{{ $t('general.address') }}
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
|
||||
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
|
||||
}}
|
||||
{{ scope.opt.subDistrict?.zipCode || '' }}
|
||||
</q-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
v-if="!readonly && customerBranch"
|
||||
name="mdi-close-circle"
|
||||
@click.stop="customerBranch = undefined"
|
||||
class="cursor-pointer clear-btn"
|
||||
/>
|
||||
</template>
|
||||
</q-select>
|
||||
class="col-12 field-two"
|
||||
required
|
||||
:readonly
|
||||
/>
|
||||
|
||||
<q-input
|
||||
:for="`${prefixId}-input-code`"
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import { useI18n } from 'vue-i18n';
|
|||
|
||||
import useUserStore from 'src/stores/user';
|
||||
import useOptionStore from 'src/stores/options';
|
||||
import { useWorkflowTemplate } from 'src/stores/workflow-template';
|
||||
import { baseUrl } from 'stores/utils';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
import {
|
||||
WorkflowUserInTable,
|
||||
WorkflowTemplatePayload,
|
||||
WorkFlowPayloadStep,
|
||||
Group,
|
||||
} from 'src/stores/workflow-template/types';
|
||||
import { User } from 'src/stores/user/types';
|
||||
|
||||
|
|
@ -20,6 +22,7 @@ import ToggleButton from 'src/components/button/ToggleButton.vue';
|
|||
import NoData from '../NoData.vue';
|
||||
import SelectBranch from '../shared/select/SelectBranch.vue';
|
||||
import AddButton from '../button/AddButton.vue';
|
||||
import { QField } from 'quasar';
|
||||
|
||||
defineProps<{
|
||||
readonly?: boolean;
|
||||
|
|
@ -29,6 +32,7 @@ defineProps<{
|
|||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const optionStore = useOptionStore();
|
||||
const workflowStore = useWorkflowTemplate();
|
||||
|
||||
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
|
||||
default: [],
|
||||
|
|
@ -43,7 +47,7 @@ const flowData = defineModel<WorkflowTemplatePayload>('flowData', {
|
|||
},
|
||||
});
|
||||
|
||||
const objectOptions = [
|
||||
let objectOptions = [
|
||||
...(optionStore.globalOption?.agenciesType || []),
|
||||
{ label: t('flow.customer'), value: 'customer' },
|
||||
{ label: t('flow.officer'), value: 'officer' },
|
||||
|
|
@ -51,7 +55,9 @@ const objectOptions = [
|
|||
const options = ref(objectOptions);
|
||||
const role = ref<string[]>([]);
|
||||
const userList = ref<User[]>([]);
|
||||
const groupList = ref<Group[]>([]);
|
||||
const responsiblePersonSearch = ref('');
|
||||
const responsibleMenu = ref(false);
|
||||
|
||||
async function getUserList(opts?: { query: string }) {
|
||||
const resUser = await userStore.fetchList({
|
||||
|
|
@ -60,10 +66,10 @@ async function getUserList(opts?: { query: string }) {
|
|||
if (resUser) userList.value = resUser.result;
|
||||
}
|
||||
|
||||
// async function getUserById(responsiblePersonId: string) {
|
||||
// const resUser = await userStore.fetchById(responsiblePersonId);
|
||||
// if (resUser) userInTable.value.push(resUser);
|
||||
// }
|
||||
async function getGroupList() {
|
||||
const resGroup = await workflowStore.getGroupList();
|
||||
if (resGroup) groupList.value = resGroup;
|
||||
}
|
||||
|
||||
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
|
||||
const currStep = flowData.value.step[stepIndex];
|
||||
|
|
@ -78,6 +84,7 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
|
|||
userInTable.value[stepIndex] = {
|
||||
name: flowData.value.step[stepIndex].name,
|
||||
responsiblePerson: [],
|
||||
responsibleGroup: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +108,33 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
|
|||
}
|
||||
}
|
||||
|
||||
function selectResponsibleGroup(stepIndex: number, responsibleGroup: string) {
|
||||
const currStep = flowData.value.step[stepIndex];
|
||||
const existGroupIndex = currStep.responsibleGroup?.findIndex(
|
||||
(p) => p === responsibleGroup,
|
||||
);
|
||||
|
||||
if (existGroupIndex === -1) {
|
||||
currStep.responsibleGroup?.push(responsibleGroup);
|
||||
|
||||
if (!userInTable.value[stepIndex]) {
|
||||
userInTable.value[stepIndex] = {
|
||||
name: flowData.value.step[stepIndex].name,
|
||||
responsiblePerson: [],
|
||||
responsibleGroup: [],
|
||||
};
|
||||
}
|
||||
|
||||
userInTable.value[stepIndex]?.responsibleGroup.push(responsibleGroup);
|
||||
} else {
|
||||
currStep.responsibleGroup?.splice(Number(existGroupIndex), 1);
|
||||
userInTable.value[stepIndex]?.responsibleGroup.splice(
|
||||
Number(existGroupIndex),
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function selectItem(
|
||||
val: Record<string, unknown>,
|
||||
responsibleInstitution?: string[],
|
||||
|
|
@ -142,6 +176,7 @@ watch(
|
|||
onMounted(async () => {
|
||||
role.value = getRole() || [];
|
||||
await getUserList();
|
||||
await getGroupList();
|
||||
await userStore.fetchHqOption();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -467,92 +502,128 @@ onMounted(async () => {
|
|||
</div>
|
||||
|
||||
<!-- RESPONSIBLE-PERSON -->
|
||||
<q-select
|
||||
<q-field
|
||||
v-if="step.responsiblePersonId"
|
||||
behavior="menu"
|
||||
:for="`select-responsible-person-${index}-${onDrawer ? 'drawer' : 'dialog'}`"
|
||||
:bg-color="readonly ? 'transparent' : ''"
|
||||
:readonly
|
||||
outlined
|
||||
dense
|
||||
v-model="step.responsiblePersonId"
|
||||
multiple
|
||||
:options="[1, 2, 3]"
|
||||
hide-bottom-space
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
emit-value
|
||||
:stack-label="
|
||||
userInTable[index]?.responsiblePerson.length > 0 ||
|
||||
userInTable[index]?.responsibleGroup.length > 0
|
||||
"
|
||||
:label="$t('flow.responsiblePerson')"
|
||||
dense
|
||||
class="col-md-6 col-12"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:class="{ 'cursor-pointer': !readonly }"
|
||||
>
|
||||
<template v-slot:selected-item="scope">
|
||||
<div class="column full-width">
|
||||
<div
|
||||
class="row items-center no-wrap"
|
||||
v-for="person in userInTable[
|
||||
index
|
||||
]?.responsiblePerson.filter(
|
||||
(p) => p.id === scope.opt,
|
||||
)"
|
||||
:key="person.id"
|
||||
>
|
||||
<q-avatar class="q-ml-sm" size="md">
|
||||
<q-img
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
class="no-padding full-width full-height flex items-center justify-center"
|
||||
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
|
||||
>
|
||||
<q-img
|
||||
v-if="person.gender"
|
||||
:src="
|
||||
person.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>
|
||||
<div
|
||||
class="column q-pl-md"
|
||||
style="color: var(--foreground)"
|
||||
<template #control>
|
||||
<q-item
|
||||
dense
|
||||
class="items-center full-width no-padding"
|
||||
v-for="person in userInTable[
|
||||
index
|
||||
]?.responsiblePerson.filter((p) =>
|
||||
step.responsiblePersonId.includes(p.id),
|
||||
)"
|
||||
:key="person.id"
|
||||
>
|
||||
<q-avatar class="q-ml-sm" size="md">
|
||||
<q-img
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
|
||||
>
|
||||
<span>
|
||||
{{
|
||||
`${optionStore.mapOption(person.namePrefix || '')} ${
|
||||
$i18n.locale === 'eng'
|
||||
? person.firstNameEN
|
||||
: person.firstName
|
||||
} ${
|
||||
$i18n.locale === 'eng'
|
||||
? person.lastNameEN
|
||||
: person.lastName
|
||||
}`
|
||||
}}
|
||||
</span>
|
||||
<span class="text-caption app-text-muted">
|
||||
{{ person.code }}
|
||||
</span>
|
||||
</div>
|
||||
<template #error>
|
||||
<div
|
||||
class="no-padding full-width full-height flex items-center justify-center"
|
||||
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
|
||||
>
|
||||
<q-img
|
||||
v-if="person.gender"
|
||||
:src="
|
||||
person.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>
|
||||
<div
|
||||
class="column q-pl-md"
|
||||
style="color: var(--foreground)"
|
||||
>
|
||||
<span>
|
||||
{{
|
||||
`${optionStore.mapOption(person.namePrefix || '')} ${
|
||||
$i18n.locale === 'eng'
|
||||
? person.firstNameEN
|
||||
: person.firstName
|
||||
} ${
|
||||
$i18n.locale === 'eng'
|
||||
? person.lastNameEN
|
||||
: person.lastName
|
||||
}`
|
||||
}}
|
||||
</span>
|
||||
<span class="text-caption app-text-muted">
|
||||
{{ person.code }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</q-item>
|
||||
|
||||
<template v-slot:option></template>
|
||||
<q-menu v-if="!readonly" :offset="[0, 4]">
|
||||
<div
|
||||
v-if="step.responsibleGroup.length > 0"
|
||||
class="full-width app-text-muted text-weight-medium"
|
||||
style="font-size: 10px"
|
||||
>
|
||||
{{ $t('general.group') }}
|
||||
</div>
|
||||
<q-item
|
||||
class="items-center full-width no-padding"
|
||||
v-for="group in userInTable[
|
||||
index
|
||||
]?.responsibleGroup.filter((g) =>
|
||||
step.responsibleGroup.includes(g),
|
||||
)"
|
||||
:key="group"
|
||||
dense
|
||||
>
|
||||
<q-avatar class="q-ml-sm" size="md">
|
||||
<q-img
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
:src="`/img-group.png`"
|
||||
/>
|
||||
</q-avatar>
|
||||
<span class="q-pl-md">
|
||||
{{ group }}
|
||||
</span>
|
||||
</q-item>
|
||||
</template>
|
||||
<template #append>
|
||||
<q-icon
|
||||
name="mdi-menu-down"
|
||||
:class="{ rotated: responsibleMenu }"
|
||||
class="transition-rotate"
|
||||
/>
|
||||
</template>
|
||||
<q-menu
|
||||
v-if="!readonly"
|
||||
no-focus
|
||||
no-refocus
|
||||
:offset="[0, 4]"
|
||||
@before-show="() => (responsibleMenu = true)"
|
||||
@before-hide="() => (responsibleMenu = false)"
|
||||
>
|
||||
<q-list>
|
||||
<q-item>
|
||||
<q-input
|
||||
|
|
@ -581,6 +652,7 @@ onMounted(async () => {
|
|||
{{ $t('general.noData') }}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-else
|
||||
v-for="(person, i) in userList"
|
||||
dense
|
||||
:key="i"
|
||||
|
|
@ -655,6 +727,7 @@ onMounted(async () => {
|
|||
{{ $t('personnel.MESSENGER') }}
|
||||
</span>
|
||||
<q-item
|
||||
dense
|
||||
clickable
|
||||
@click="step.messengerByArea = !step.messengerByArea"
|
||||
class="column"
|
||||
|
|
@ -670,9 +743,49 @@ onMounted(async () => {
|
|||
</div>
|
||||
</div>
|
||||
</q-item>
|
||||
|
||||
<span class="text-caption app-text-muted-2 q-px-md">
|
||||
{{ $t('general.group') }}
|
||||
</span>
|
||||
<q-item
|
||||
v-if="groupList.length === 0"
|
||||
class="app-text-muted q-px-lg"
|
||||
>
|
||||
{{ $t('general.noData') }}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-else
|
||||
v-for="(group, i) in groupList"
|
||||
dense
|
||||
clickable
|
||||
@click="selectResponsibleGroup(index, group.name)"
|
||||
class="column"
|
||||
>
|
||||
<div class="row items-center">
|
||||
<q-checkbox
|
||||
size="xs"
|
||||
:model-value="
|
||||
step.responsibleGroup.includes(group.name)
|
||||
"
|
||||
@click.stop="
|
||||
selectResponsibleGroup(index, group.name)
|
||||
"
|
||||
/>
|
||||
<q-avatar class="q-ml-sm" size="md">
|
||||
<q-img
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
:src="`/img-group.png`"
|
||||
/>
|
||||
</q-avatar>
|
||||
<div class="column q-pl-md">
|
||||
<span>{{ group.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-select>
|
||||
</q-field>
|
||||
|
||||
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
|
||||
<q-select
|
||||
|
|
@ -787,8 +900,8 @@ onMounted(async () => {
|
|||
}
|
||||
|
||||
:deep(
|
||||
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
|
||||
) {
|
||||
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
|
||||
) {
|
||||
justify-content: start !important;
|
||||
padding-right: 8px !important;
|
||||
padding-top: 16px;
|
||||
|
|
@ -800,19 +913,26 @@ onMounted(async () => {
|
|||
}
|
||||
|
||||
:deep(
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
|
||||
:deep(
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
|
||||
.q-focus-helper
|
||||
) {
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
|
||||
.q-focus-helper
|
||||
) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
:deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.transition-rotate {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -244,16 +244,19 @@ withDefaults(
|
|||
<template v-if="col.name === '#calcVat'">
|
||||
<q-checkbox
|
||||
v-if="priceDisplay?.price && props.rowIndex === 0"
|
||||
:disable="readonly"
|
||||
v-model="calcVat"
|
||||
size="xs"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-if="priceDisplay?.agentPrice && props.rowIndex === 1"
|
||||
:disable="readonly"
|
||||
v-model="agentPriceCalcVat"
|
||||
size="xs"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-if="priceDisplay?.serviceCharge && props.rowIndex === 2"
|
||||
:disable="readonly"
|
||||
v-model="serviceChargeCalcVat"
|
||||
size="xs"
|
||||
/>
|
||||
|
|
@ -271,6 +274,8 @@ withDefaults(
|
|||
flat
|
||||
outlined
|
||||
dense
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="vatIncluded"
|
||||
></q-select>
|
||||
<q-select
|
||||
|
|
@ -285,6 +290,8 @@ withDefaults(
|
|||
flat
|
||||
outlined
|
||||
dense
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="agentPriceVatIncluded"
|
||||
></q-select>
|
||||
<q-select
|
||||
|
|
@ -299,6 +306,8 @@ withDefaults(
|
|||
flat
|
||||
outlined
|
||||
dense
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="serviceChargeVatIncluded"
|
||||
></q-select>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ defineProps<{
|
|||
const group = defineModel('group', { default: '' });
|
||||
const name = defineModel('name', { default: '' });
|
||||
const nameEn = defineModel('nameEn', { default: '' });
|
||||
const contactName = defineModel('contactName', { default: '' });
|
||||
const email = defineModel('email', { default: '' });
|
||||
const contactTel = defineModel('contactTel', { default: '' });
|
||||
|
||||
type Options = { label: string; value: string };
|
||||
</script>
|
||||
|
|
@ -35,7 +38,7 @@ type Options = { label: string; value: string };
|
|||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<SelectInput
|
||||
:class="{ col: $q.screen.lt.md }"
|
||||
class="col"
|
||||
:disable="!readonly && onDrawer"
|
||||
:readonly="readonly"
|
||||
for="input-agencies-code"
|
||||
|
|
@ -62,7 +65,7 @@ type Options = { label: string; value: string };
|
|||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md col-12"
|
||||
class="col-md-4 col-12"
|
||||
:label="$t('agencies.name')"
|
||||
v-model="name"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
|
|
@ -73,10 +76,78 @@ type Options = { label: string; value: string };
|
|||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md col-12"
|
||||
class="col-md-4 col-12"
|
||||
:label="'Agencies Name'"
|
||||
v-model="nameEn"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
for="input-agencies-contact-name"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-4 col-12"
|
||||
:label="$t('agencies.contactName')"
|
||||
:model-value="readonly ? contactName || '-' : contactName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (contactName = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
for="input-agencies-email"
|
||||
dense
|
||||
outlined
|
||||
hide-bottom-space
|
||||
:readonly="readonly"
|
||||
:label="$t('form.email')"
|
||||
:rules="
|
||||
readonly
|
||||
? undefined
|
||||
: [
|
||||
(v: string) =>
|
||||
!v ||
|
||||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
|
||||
$t('form.error.invalid'),
|
||||
]
|
||||
"
|
||||
class="col-md-4 col-12"
|
||||
:model-value="readonly ? email || '-' : email"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
|
||||
@clear="email = ''"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-email-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
for="input-agencies-contact-tel"
|
||||
id="input-agencies-contact-tel"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-4 col-12"
|
||||
:label="$t('agencies.contactTel')"
|
||||
:model-value="readonly ? contactTel || '-' : contactTel"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (contactTel = v) : '')
|
||||
"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useQuasar } from 'quasar';
|
|||
import SignaturePad from 'signature_pad';
|
||||
import Cropper from 'cropperjs';
|
||||
|
||||
defineExpose({ clearCanvas, clearUpload });
|
||||
defineExpose({ setCanvas, getCanvas, clearCanvas, clearUpload });
|
||||
|
||||
const $q = useQuasar();
|
||||
const isDarkActive = computed(() => $q.dark.isActive);
|
||||
|
|
@ -18,7 +18,7 @@ const cropper = ref();
|
|||
|
||||
const tab = ref('draw');
|
||||
const uploadFile = ref<File | undefined>(undefined);
|
||||
const profileUrl = ref<string | null>('');
|
||||
const imgUrl = ref<string | null>('');
|
||||
const inputFile = (() => {
|
||||
const element = document.createElement('input');
|
||||
element.type = 'file';
|
||||
|
|
@ -26,7 +26,7 @@ const inputFile = (() => {
|
|||
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => {
|
||||
if (typeof reader.result === 'string') profileUrl.value = reader.result;
|
||||
if (typeof reader.result === 'string') imgUrl.value = reader.result;
|
||||
});
|
||||
|
||||
element.addEventListener('change', () => {
|
||||
|
|
@ -39,12 +39,11 @@ const inputFile = (() => {
|
|||
return element;
|
||||
})();
|
||||
|
||||
async function initializeSignaturePad(canva?: HTMLCanvasElement) {
|
||||
if (canva) {
|
||||
signaturePad.value = new SignaturePad(canva, {
|
||||
backgroundColor: isDarkActive.value
|
||||
? 'rgb(21,25,29)'
|
||||
: 'rgb(248,249,250)',
|
||||
async function initializeSignaturePad() {
|
||||
const canvas = canvasRef.value;
|
||||
|
||||
if (canvas) {
|
||||
signaturePad.value = new SignaturePad(canvas, {
|
||||
penColor: 'blue',
|
||||
});
|
||||
} else {
|
||||
|
|
@ -77,34 +76,34 @@ function changeColor(color: string) {
|
|||
currentColor.value = color;
|
||||
}
|
||||
|
||||
function setCanvas() {
|
||||
const data = signaturePad.value.toDataURL('image/png');
|
||||
return data;
|
||||
}
|
||||
|
||||
function getCanvas(signature: string) {
|
||||
signaturePad.value.fromDataURL(signature);
|
||||
}
|
||||
|
||||
function clearCanvas() {
|
||||
signaturePad.value.clear();
|
||||
}
|
||||
|
||||
function clearUpload() {
|
||||
profileUrl.value = '';
|
||||
imgUrl.value = '';
|
||||
}
|
||||
|
||||
watch(
|
||||
() => tab.value,
|
||||
async () => {
|
||||
await initializeSignaturePad(canvasRef.value);
|
||||
await initializeCropper(imageRef.value);
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await initializeSignaturePad(canvasRef.value);
|
||||
await initializeCropper(imageRef.value);
|
||||
await initializeSignaturePad();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="surface-1 bordered rounded full-width">
|
||||
<div class="surface-1 column full-width full-height">
|
||||
<q-tabs
|
||||
v-model="tab"
|
||||
dense
|
||||
align="left"
|
||||
class="text-grey"
|
||||
class="text-grey surface-2"
|
||||
active-color="primary"
|
||||
indicator-color="primary"
|
||||
>
|
||||
|
|
@ -112,18 +111,18 @@ onMounted(async () => {
|
|||
<div class="row">
|
||||
<q-tab
|
||||
name="draw"
|
||||
label="Draw"
|
||||
:label="$t('general.draw')"
|
||||
style="border-top-left-radius: var(--radius-2)"
|
||||
/>
|
||||
<q-tab name="upload" label="Upload" />
|
||||
<q-tab name="upload" :label="$t('general.upload')" />
|
||||
</div>
|
||||
|
||||
<div class="q-pr-md">
|
||||
<q-btn
|
||||
v-if="tab === 'upload'"
|
||||
dense
|
||||
flat
|
||||
v-if="tab === 'upload'"
|
||||
:label="$t('newUpload')"
|
||||
:label="$t('general.newUpload')"
|
||||
color="info"
|
||||
@click="inputFile.click()"
|
||||
/>
|
||||
|
|
@ -132,89 +131,66 @@ onMounted(async () => {
|
|||
</q-tabs>
|
||||
<q-separator />
|
||||
|
||||
<div v-show="tab === 'draw'" class="q-pa-md">
|
||||
<section v-show="tab === 'draw'" class="q-pa-md col">
|
||||
<div class="column relative-position">
|
||||
<div class="absolute-top-right q-ma-md q-gutter-x-md row items-center">
|
||||
<article
|
||||
class="absolute-top-right q-ma-md q-gutter-x-md row items-center"
|
||||
>
|
||||
<span
|
||||
v-for="color in ['black', 'red', 'blue']"
|
||||
:key="color"
|
||||
:class="{ active: currentColor === color }"
|
||||
class="dot"
|
||||
:class="{ active: currentColor === 'black' }"
|
||||
style="background-color: black"
|
||||
@click="changeColor('black')"
|
||||
:style="`background-color: ${color}`"
|
||||
@click="changeColor(color)"
|
||||
>
|
||||
<q-icon
|
||||
v-if="currentColor === 'black'"
|
||||
v-if="currentColor === color"
|
||||
name="mdi-check"
|
||||
color="white"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
:class="{ active: currentColor === 'red' }"
|
||||
class="dot"
|
||||
style="background-color: red"
|
||||
@click="changeColor('red')"
|
||||
>
|
||||
<q-icon
|
||||
v-if="currentColor === 'red'"
|
||||
name="mdi-check"
|
||||
color="white"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
:class="{ active: currentColor === 'blue' }"
|
||||
class="dot"
|
||||
style="background-color: blue"
|
||||
@click="changeColor('blue')"
|
||||
>
|
||||
<q-icon
|
||||
v-if="currentColor === 'blue'"
|
||||
name="mdi-check"
|
||||
color="white"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<canvas
|
||||
class="signature-canvas"
|
||||
ref="canvasRef"
|
||||
id="signature-pad"
|
||||
width="700"
|
||||
height="310"
|
||||
width="766"
|
||||
height="364"
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-show="tab === 'upload'" class="q-pa-md">
|
||||
<section v-show="tab === 'upload'" class="q-pa-md col">
|
||||
<div
|
||||
class="bordered upload-border rounded column items-center justify-center"
|
||||
style="height: 312px"
|
||||
class="bordered upload-border rounded column items-center justify-center full-height"
|
||||
>
|
||||
<q-img
|
||||
v-show="profileUrl"
|
||||
v-show="imgUrl"
|
||||
ref="imageRef"
|
||||
:src="profileUrl ?? ''"
|
||||
:src="imgUrl ?? ''"
|
||||
style="object-fit: cover; width: 100%; height: 100%"
|
||||
/>
|
||||
<div v-if="!profileUrl">
|
||||
<div v-if="!imgUrl">
|
||||
<q-icon
|
||||
name="mdi-cloud-upload"
|
||||
size="10rem"
|
||||
style="color: hsla(var(--text-mute) / 0.2)"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<q-btn
|
||||
unelevated
|
||||
color="info"
|
||||
:label="$t('uploadFile')"
|
||||
:label="$t('general.upload')"
|
||||
icon="mdi-plus"
|
||||
@click="inputFile.click()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
|
||||
defineProps<{
|
||||
hideIcon?: boolean;
|
||||
icon?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
|
@ -10,10 +13,13 @@ defineProps<{
|
|||
v-if="!hideIcon"
|
||||
id="btn-add"
|
||||
padding="sm"
|
||||
icon="mdi-plus"
|
||||
:icon="icon ? undefined : 'mdi-plus'"
|
||||
direction="up"
|
||||
class="color-btn"
|
||||
>
|
||||
<template #icon v-if="icon">
|
||||
<Icon :icon width="24" />
|
||||
</template>
|
||||
<slot>
|
||||
<q-fab-action
|
||||
padding="xs"
|
||||
|
|
@ -29,10 +35,12 @@ defineProps<{
|
|||
fab
|
||||
id="btn-add"
|
||||
padding="sm"
|
||||
icon="mdi-plus"
|
||||
:icon="icon ? undefined : 'mdi-plus'"
|
||||
direction="up"
|
||||
class="color-btn"
|
||||
/>
|
||||
>
|
||||
<Icon v-if="icon" :icon width="24" />
|
||||
</q-btn>
|
||||
</q-page-sticky>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MainButton from './MainButton.vue';
|
||||
|
||||
defineEmits<{
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', v: MouseEvent): void;
|
||||
(e: 'fileSelected', v: File[]): void;
|
||||
}>();
|
||||
defineProps<{
|
||||
iconOnly?: boolean;
|
||||
|
|
@ -10,15 +12,29 @@ defineProps<{
|
|||
outlined?: boolean;
|
||||
disabled?: boolean;
|
||||
dark?: boolean;
|
||||
importFile?: boolean;
|
||||
|
||||
label?: string;
|
||||
icon?: string;
|
||||
}>();
|
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
function triggerFileInput() {
|
||||
inputRef.value?.click();
|
||||
}
|
||||
|
||||
function handleFileChange(event: Event) {
|
||||
const files = (event.target as HTMLInputElement).files;
|
||||
if (files && files.length > 0) {
|
||||
emit('fileSelected', Array.from(files));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainButton
|
||||
@click="(e) => $emit('click', e)"
|
||||
@click="(e) => (importFile ? triggerFileInput() : $emit('click', e))"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:icon="icon || 'mdi-import'"
|
||||
color="var(--info-bg)"
|
||||
|
|
@ -26,4 +42,13 @@ defineProps<{
|
|||
>
|
||||
{{ label || $t('general.import') }}
|
||||
</MainButton>
|
||||
|
||||
<input
|
||||
ref="inputRef"
|
||||
type="file"
|
||||
@change="(e) => handleFileChange(e)"
|
||||
hidden
|
||||
accept=".xls, .xlsx , .csv"
|
||||
multiple
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
32
src/components/button/PasteButton.vue
Normal file
32
src/components/button/PasteButton.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script lang="ts" setup>
|
||||
import MainButton from './MainButton.vue';
|
||||
|
||||
defineEmits<{
|
||||
(e: 'click', v: MouseEvent): void;
|
||||
}>();
|
||||
defineProps<{
|
||||
iconOnly?: boolean;
|
||||
solid?: boolean;
|
||||
outlined?: boolean;
|
||||
disabled?: boolean;
|
||||
dark?: boolean;
|
||||
|
||||
label?: string;
|
||||
icon?: string;
|
||||
|
||||
amount?: number;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainButton
|
||||
@click="(e) => $emit('click', e)"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:icon="icon || 'mdi-file-replace'"
|
||||
color="207 96% 32%"
|
||||
:title="iconOnly ? $t('general.paste') : undefined"
|
||||
>
|
||||
{{ label || $t('general.paste') }}
|
||||
{{ amount && amount > 0 ? `(${amount})` : '' }}
|
||||
</MainButton>
|
||||
</template>
|
||||
|
|
@ -14,3 +14,4 @@ export { default as PrintButton } from './PrintButton.vue';
|
|||
export { default as StateButton } from './StateButton.vue';
|
||||
export { default as NextButton } from './NextButton.vue';
|
||||
export { default as ImportButton } from './ImportButton.vue';
|
||||
export { default as PasteButton } from './PasteButton.vue';
|
||||
|
|
|
|||
189
src/components/shared/AdvanceSearch.vue
Normal file
189
src/components/shared/AdvanceSearch.vue
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { dateFormatJS } from 'src/utils/datetime';
|
||||
import SelectInput from './SelectInput.vue';
|
||||
import VueDatePicker from '@vuepic/vue-datepicker';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
defineProps<{
|
||||
active?: boolean;
|
||||
}>();
|
||||
|
||||
const date = defineModel<string[]>();
|
||||
|
||||
const dateRange = ref<string>('');
|
||||
const isDateSelect = ref(false);
|
||||
|
||||
function mapDateRange(val: string) {
|
||||
const today = dayjs();
|
||||
let start: dayjs.Dayjs, end: dayjs.Dayjs;
|
||||
|
||||
switch (val) {
|
||||
case 'toDay':
|
||||
start = today.startOf('day');
|
||||
end = today.endOf('day');
|
||||
break;
|
||||
case 'yesterday':
|
||||
start = today.subtract(1, 'day').startOf('day');
|
||||
end = today.subtract(1, 'day').endOf('day');
|
||||
break;
|
||||
case 'thisWeek':
|
||||
start = today.startOf('week');
|
||||
end = today.endOf('week');
|
||||
break;
|
||||
case 'lastWeek':
|
||||
start = today.subtract(1, 'week').startOf('week');
|
||||
end = today.subtract(1, 'week').endOf('week');
|
||||
break;
|
||||
case 'thisMonth':
|
||||
start = today.startOf('month');
|
||||
end = today.endOf('month');
|
||||
break;
|
||||
case 'lastMonth':
|
||||
start = today.subtract(1, 'month').startOf('month');
|
||||
end = today.subtract(1, 'month').endOf('month');
|
||||
break;
|
||||
case 'thisYear':
|
||||
start = today.startOf('year');
|
||||
end = today.endOf('year');
|
||||
break;
|
||||
case 'lastYear':
|
||||
start = today.subtract(1, 'year').startOf('year');
|
||||
end = today.subtract(1, 'year').endOf('year');
|
||||
break;
|
||||
case 'last7Days':
|
||||
start = today.subtract(6, 'day').startOf('day');
|
||||
end = today.endOf('day');
|
||||
break;
|
||||
case 'last30Days':
|
||||
start = today.subtract(29, 'day').startOf('day');
|
||||
end = today.endOf('day');
|
||||
break;
|
||||
case 'last90Days':
|
||||
start = today.subtract(89, 'day').startOf('day');
|
||||
end = today.endOf('day');
|
||||
break;
|
||||
case 'customDateRange':
|
||||
start = today.startOf('day');
|
||||
end = today.endOf('day');
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
return [start.toDate().toISOString(), end.toDate().toISOString()];
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dateRange.value,
|
||||
() => {
|
||||
if (!dateRange.value) return;
|
||||
date.value = mapDateRange(dateRange.value);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => date.value,
|
||||
() => {
|
||||
if (date.value && date.value.length === 0) dateRange.value = '';
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<q-btn
|
||||
size="xs"
|
||||
round
|
||||
dense
|
||||
unelevated
|
||||
icon="mdi-tune-variant"
|
||||
:flat="active ? false : !dateRange"
|
||||
:color="active || dateRange ? 'info' : undefined"
|
||||
>
|
||||
<q-menu
|
||||
:offset="[5, 10]"
|
||||
max-width="300px"
|
||||
class="bordered"
|
||||
:persistent="isDateSelect"
|
||||
>
|
||||
<div class="q-pa-sm">
|
||||
<div class="text-weight-medium">
|
||||
{{ $t('general.advanceSearch') }}
|
||||
</div>
|
||||
<SelectInput
|
||||
v-model="dateRange"
|
||||
:label="$t('general.period')"
|
||||
:option="[
|
||||
{ label: $t('dateRange.today'), value: 'toDay' },
|
||||
{ label: $t('dateRange.yesterday'), value: 'yesterday' },
|
||||
{ label: $t('dateRange.thisWeek'), value: 'thisWeek' },
|
||||
{ label: $t('dateRange.lastWeek'), value: 'lastWeek' },
|
||||
{ label: $t('dateRange.thisMonth'), value: 'thisMonth' },
|
||||
{ label: $t('dateRange.lastMonth'), value: 'lastMonth' },
|
||||
{ label: $t('dateRange.thisYear'), value: 'thisYear' },
|
||||
{ label: $t('dateRange.lastYear'), value: 'lastYear' },
|
||||
{ label: $t('dateRange.last7Days'), value: 'last7Days' },
|
||||
{ label: $t('dateRange.last30Days'), value: 'last30Days' },
|
||||
{ label: $t('dateRange.last90Days'), value: 'last90Days' },
|
||||
{
|
||||
label: $t('dateRange.customDateRange'),
|
||||
value: 'customDateRange',
|
||||
},
|
||||
]"
|
||||
clearable
|
||||
@clear="() => (date = [])"
|
||||
/>
|
||||
|
||||
<VueDatePicker
|
||||
v-if="dateRange === 'customDateRange'"
|
||||
utc
|
||||
range
|
||||
teleport
|
||||
auto-apply
|
||||
for="select-date-range"
|
||||
class="q-mt-sm"
|
||||
v-model="date"
|
||||
:dark="$q.dark.isActive"
|
||||
:locale="$i18n.locale === 'tha' ? 'th' : 'en'"
|
||||
@open="() => (isDateSelect = true)"
|
||||
@closed="() => (isDateSelect = false)"
|
||||
>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
placeholder="DD/MM/YYYY"
|
||||
hide-bottom-space
|
||||
dense
|
||||
outlined
|
||||
for="select-date-range"
|
||||
:model-value="
|
||||
date
|
||||
? dateFormatJS({ date: date[0] }) +
|
||||
' - ' +
|
||||
dateFormatJS({ date: date[1] })
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="mdi-calendar-outline" class="app-text-muted" />
|
||||
</template>
|
||||
<q-tooltip>
|
||||
{{
|
||||
date
|
||||
? dateFormatJS({ date: date[0] }) +
|
||||
' - ' +
|
||||
dateFormatJS({ date: date[1] })
|
||||
: ''
|
||||
}}
|
||||
</q-tooltip>
|
||||
</q-input>
|
||||
</template>
|
||||
</VueDatePicker>
|
||||
|
||||
<slot></slot>
|
||||
<!-- <SelectInput :label="$t('general.documentStatus')" :option="[]" /> -->
|
||||
</div>
|
||||
</q-menu>
|
||||
<q-tooltip v-if="$q.screen.gt.sm">
|
||||
{{ $t('general.advanceSearch') }}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</template>
|
||||
|
|
@ -25,7 +25,11 @@ withDefaults(
|
|||
alt="Image"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="data.length > 3" class="avatar remaining-count">
|
||||
<div
|
||||
v-if="data.length > 3"
|
||||
class="avatar remaining-count"
|
||||
style="cursor: default"
|
||||
>
|
||||
<q-tooltip>
|
||||
<div v-for="(person, i) in data.slice(3)" :key="i + 3">
|
||||
{{ person.name }}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const props = withDefaults(
|
|||
useUpload?: boolean;
|
||||
useCancel?: boolean;
|
||||
useRejectCancel?: boolean;
|
||||
useCopy?: boolean;
|
||||
disableCancel?: boolean;
|
||||
disableDelete?: boolean;
|
||||
}>(),
|
||||
|
|
@ -31,6 +32,7 @@ defineEmits<{
|
|||
(e: 'link'): void;
|
||||
(e: 'upload'): void;
|
||||
(e: 'delete'): void;
|
||||
(e: 'copy'): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: 'rejectCancel'): void;
|
||||
(e: 'changeStatus'): void;
|
||||
|
|
@ -172,6 +174,27 @@ watch(
|
|||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-item
|
||||
v-if="useCopy"
|
||||
v-close-popup
|
||||
dense
|
||||
clickable
|
||||
class="row q-py-sm"
|
||||
style="white-space: nowrap"
|
||||
:id="`btn-kebab-copy-${idName}`"
|
||||
@click.stop="() => $emit('copy')"
|
||||
>
|
||||
<q-icon
|
||||
size="xs"
|
||||
class="col-3"
|
||||
name="mdi-content-copy"
|
||||
style="color: hsl(var(--teal-5-hsl))"
|
||||
/>
|
||||
<span class="col-9 q-px-md flex items-center">
|
||||
{{ $t('general.copy') }}
|
||||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-item
|
||||
v-if="useCancel"
|
||||
v-close-popup
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const props = withDefaults(
|
|||
disable?: boolean;
|
||||
multiple?: boolean;
|
||||
hideInput?: boolean;
|
||||
hideDropdownIcon?: boolean;
|
||||
|
||||
rules?: ((value: string) => string | true)[];
|
||||
}>(),
|
||||
|
|
@ -82,7 +83,7 @@ watch(
|
|||
:hide-selected
|
||||
hide-bottom-space
|
||||
:fill-input="fillInput && !!model"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:hide-dropdown-icon="readonly || hideDropdownIcon"
|
||||
input-debounce="500"
|
||||
:option-value="
|
||||
typeof props.optionValue === 'string' ? props.optionValue : 'value'
|
||||
|
|
@ -103,6 +104,11 @@ watch(
|
|||
}
|
||||
"
|
||||
:rules
|
||||
@clear="
|
||||
() => {
|
||||
multiple ? (model = []) : (model = '');
|
||||
}
|
||||
"
|
||||
>
|
||||
<template v-if="$slots.prepend" v-slot:prepend>
|
||||
<slot name="prepend"></slot>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ onMounted(async () => {
|
|||
}
|
||||
|
||||
await getSelectedOption();
|
||||
|
||||
valueOption.value = selectOptions.value.find((v) => v.id === value.value);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -158,7 +160,7 @@ onMounted(async () => {
|
|||
</template>
|
||||
|
||||
<template #option="{ opt, scope }">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item @click="valueOption = opt" v-bind="scope.itemProps">
|
||||
<SelectCustomerItem :data="opt" :simple :simple-branch-no />
|
||||
</q-item>
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ const { getOptions, setFirstValue, getSelectedOption, filter } =
|
|||
const ret = await getList({
|
||||
query: query === '' ? undefined : query,
|
||||
...props.params,
|
||||
activeOnly: true,
|
||||
});
|
||||
if (ret) return ret.result;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@ const columns = [
|
|||
field: (v: Employee) =>
|
||||
locale.value === Lang.English
|
||||
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||
: `${v.firstName} ${v.lastName}`,
|
||||
: v.firstName
|
||||
? `${v.firstName} ${v.lastName}`
|
||||
: `${v.firstNameEN} ${v.lastNameEN}`,
|
||||
},
|
||||
{
|
||||
name: 'birthDate',
|
||||
|
|
|
|||
2
src/env.d.ts
vendored
2
src/env.d.ts
vendored
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable */
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: string;
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export default {
|
|||
branchStatus: 'Branch Status',
|
||||
success: 'Success',
|
||||
taxNo: 'Legal Person',
|
||||
contactName: 'Contact Name',
|
||||
contactName: 'Contact Person',
|
||||
image: 'Image of ',
|
||||
apply: 'Apply',
|
||||
licenseNumber: 'License number',
|
||||
|
|
@ -151,6 +151,15 @@ export default {
|
|||
dueDate: 'Due date',
|
||||
year: 'year',
|
||||
tableOfContent: 'Table of Contents',
|
||||
draw: 'Draw',
|
||||
newUpload: 'New Upload',
|
||||
nativeLanguage: '{msg} Native Language',
|
||||
copy: 'Copy',
|
||||
paste: 'Paste',
|
||||
period: 'Period',
|
||||
documentStatus: 'Document Status',
|
||||
advanceSearch: 'Advance Search',
|
||||
totalPeople: '{meg} people',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -245,7 +254,8 @@ export default {
|
|||
|
||||
manual: {
|
||||
title: 'Manual',
|
||||
usage: 'การใช้งาน',
|
||||
usage: 'Usage',
|
||||
troubleshooting: 'Troubleshooting',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -374,7 +384,7 @@ export default {
|
|||
branchLabel: 'Branch',
|
||||
branchHQLabel: 'Headoffice',
|
||||
taxNo: 'Legal Person',
|
||||
contactName: 'Contact Name',
|
||||
contactName: 'Contact Person',
|
||||
},
|
||||
page: {
|
||||
captionManage: 'Manage',
|
||||
|
|
@ -395,8 +405,8 @@ export default {
|
|||
code: 'Headoffice Code',
|
||||
codeBranch: 'Branch Code',
|
||||
taxNo: 'Tax Identification Number',
|
||||
contactName: 'Contact Name',
|
||||
contactTelephone: 'Contact Telephone',
|
||||
contactName: 'Contact Person',
|
||||
contactTelephone: 'Contact Number',
|
||||
branchName: 'Branch Name',
|
||||
branchNameEN: 'Branch Name (EN)',
|
||||
servicePointName: 'Service Point Name',
|
||||
|
|
@ -449,7 +459,7 @@ export default {
|
|||
responsibleArea: 'Responsibel Area',
|
||||
discount: 'Discount Condition',
|
||||
sourceNationality: 'Source Nationality',
|
||||
importNationality: 'import Nationality',
|
||||
importNationality: 'Import Nationality',
|
||||
trainingPlace: 'Training Place',
|
||||
checkpoint: 'Checkpoint',
|
||||
checkpointEN: 'Checkpoint (EN)',
|
||||
|
|
@ -457,6 +467,12 @@ export default {
|
|||
citizenId: 'Citizen ID',
|
||||
citizenIssue: 'Citizen Issue',
|
||||
citizenExpire: 'Citizen Expire',
|
||||
agencyStatus: 'Agency Status',
|
||||
normal: 'Normal',
|
||||
canceled: 'Canceled',
|
||||
blacklist: 'Black list',
|
||||
contactName: 'Contact Person',
|
||||
contactTel: 'Contact Number',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -470,7 +486,6 @@ export default {
|
|||
powerOfAttorney: 'Power of Attorney',
|
||||
others: 'Others',
|
||||
},
|
||||
|
||||
employer: 'Employer',
|
||||
employerLegalEntity: 'Legal Entity',
|
||||
employerNaturalPerson: 'Natrual Person',
|
||||
|
|
@ -492,15 +507,12 @@ export default {
|
|||
religion: 'Religion',
|
||||
issueDate: 'Issue Date',
|
||||
passportExpiryDate: 'Passport Expiry Date',
|
||||
|
||||
ownerName: 'Customer Name',
|
||||
firstName: 'First Name ',
|
||||
lastName: 'Last Name ',
|
||||
firstNameEN: 'First Name in English',
|
||||
lastNameEN: 'Last Name in English',
|
||||
|
||||
cardNumber: 'ID Card Number',
|
||||
|
||||
prefixName: 'Prefix',
|
||||
legalPersonNo: 'Legal Entity Registration Number',
|
||||
registerName: 'Company Name',
|
||||
|
|
@ -508,7 +520,6 @@ export default {
|
|||
registerDate: 'Registered On',
|
||||
registerCompanyName: 'Registered Name',
|
||||
authorizedCapital: 'Authorized Capital',
|
||||
|
||||
workplace: 'Workplace',
|
||||
workplaceEN: 'Workplace (EN)',
|
||||
address: 'Address',
|
||||
|
|
@ -516,7 +527,6 @@ export default {
|
|||
branchCode: 'Branch Code',
|
||||
customerCode: 'Employer Code',
|
||||
legalPersonCode: 'Legal Entity Code',
|
||||
|
||||
codeAbbrev: 'Company Abbreviation',
|
||||
codeNumber: 'Company Number',
|
||||
registeredBranch: 'Registered Branch',
|
||||
|
|
@ -554,7 +564,7 @@ export default {
|
|||
jobPosition: 'Job Position',
|
||||
address: 'Address',
|
||||
workPlace: 'Workplace',
|
||||
contactName: 'Contact Name',
|
||||
contactName: 'Contact Person',
|
||||
contactPhone: 'Contact Phone',
|
||||
totalEmployee: 'Total Employee',
|
||||
officeTel: 'Headoffice Telephone',
|
||||
|
|
@ -792,7 +802,7 @@ export default {
|
|||
employee: 'Employee',
|
||||
employeeName: 'Full Name',
|
||||
workName: 'Work Name',
|
||||
contactName: 'Contact Name',
|
||||
contactName: 'Contact Person',
|
||||
documentReceivePoint: 'Document Drop-Off Point"',
|
||||
dueDate: 'Quotation Due Date',
|
||||
specialCondition: 'Special Conditions',
|
||||
|
|
@ -874,7 +884,7 @@ export default {
|
|||
SplitCustom: 'Custom Installments Bill',
|
||||
BillFull: 'Full Amount Bill',
|
||||
BillSplit: 'Installments Bill',
|
||||
BillCustomSplit: 'Custom Installments Bill',
|
||||
BillSplitCustom: 'Custom Installments Bill',
|
||||
},
|
||||
|
||||
status: {
|
||||
|
|
@ -910,6 +920,9 @@ export default {
|
|||
code: 'Agencies Code',
|
||||
group: 'Agencies Group',
|
||||
name: 'Agencies Name',
|
||||
contactName: 'Contact Person',
|
||||
contactTel: 'Contact Number',
|
||||
bankInfo: 'Bank Information',
|
||||
},
|
||||
|
||||
requestList: {
|
||||
|
|
@ -931,8 +944,9 @@ export default {
|
|||
localEmployee: 'Local Employee',
|
||||
nonLocalEmployee: 'Non Local Employee',
|
||||
noWorkflowTemplate: 'A workflow template has not been selected.',
|
||||
|
||||
salesRepresentative: 'Sales Representative',
|
||||
|
||||
dataOffice: 'Employment Office District',
|
||||
ref: 'Reference',
|
||||
action: {
|
||||
title: 'Action',
|
||||
|
|
@ -996,7 +1010,7 @@ export default {
|
|||
issueBranch: 'Issue Branch',
|
||||
issueDate: 'Issue Date',
|
||||
madeBy: 'Made By',
|
||||
contactName: 'Contact Name',
|
||||
contactName: 'Contact Person',
|
||||
workOrderCode: 'Work Order Code',
|
||||
workOrderName: 'Work Order Name',
|
||||
telephone: 'Telephone',
|
||||
|
|
@ -1055,6 +1069,10 @@ export default {
|
|||
confirmDebitNoteAccept: 'Confirm acceptance of the debit note.',
|
||||
},
|
||||
message: {
|
||||
copy: 'Copy',
|
||||
warningPaste:
|
||||
'Do you want to replace the data with the newly copied information?',
|
||||
warningCopyEmpty: 'You have not copied any data yet',
|
||||
quotationAccept: 'Once accepted, no further modifications can be made',
|
||||
beingUse: '"{msg}" is being used.',
|
||||
incompleteDataEntry: 'Incomplete data entry on {tap} page',
|
||||
|
|
@ -1072,10 +1090,8 @@ export default {
|
|||
confirmSavingStatus:
|
||||
'Do you want to confirm the saving of the status change data?',
|
||||
confirmSending: 'Do you confirm the submission of the task?',
|
||||
confirmValidate: 'Do you confirm the validation?',
|
||||
warningSelectDeliveryStaff:
|
||||
'You have not yet selected a document delivery staff.',
|
||||
|
||||
confirmEndWorkWarning:
|
||||
"Do you want to end the work now? The current statuses 'Pending', 'In Progress', 'To Be Reprocessed' will be changed to 'Redo All'.",
|
||||
confirmEndWork: 'Do you want to end the work?',
|
||||
|
|
@ -1183,13 +1199,14 @@ export default {
|
|||
'Product with the same name already exists. If you want to create with this name please select another code.',
|
||||
userExists: 'User already exits.',
|
||||
sameNameExists: 'Same name exists.',
|
||||
samePropertyNameExists: 'Same property name exists.',
|
||||
|
||||
validateError: 'Validate Error',
|
||||
codeMisMatch: 'Code Mismatch',
|
||||
crossCompanyNotPermit: 'Cannot move between different headoffice',
|
||||
errorOccure:
|
||||
errorOccurred:
|
||||
'An error has occurred, causing the system to be unable to function. Please try again later.',
|
||||
invalideData: 'The information is incorrect. Please try again later.',
|
||||
invalidData: 'The information is incorrect. Please try again later.',
|
||||
authFailed: 'Authentication Failed. Please try again later. ',
|
||||
installmentsValidateFailed:
|
||||
'Validation failed. Each installment must include at least one product. Please review and update the installments accordingly.',
|
||||
|
|
@ -1476,4 +1493,19 @@ export default {
|
|||
type: 'Type',
|
||||
},
|
||||
},
|
||||
|
||||
dateRange: {
|
||||
today: 'Today',
|
||||
yesterday: 'Yesterday',
|
||||
thisWeek: 'This Week',
|
||||
lastWeek: 'Last Week',
|
||||
thisMonth: 'This Month',
|
||||
lastMonth: 'Last Month',
|
||||
thisYear: 'This Year',
|
||||
lastYear: 'Last Year',
|
||||
last7Days: 'Last 7 Days',
|
||||
last30Days: 'Last 30 Days',
|
||||
last90Days: 'Last 90 Days',
|
||||
customDateRange: 'Custom Date Range',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import eng from './eng';
|
||||
import tha from './tha';
|
||||
import tha from './tha'; // spellchecker:disable-line
|
||||
|
||||
export default {
|
||||
eng,
|
||||
tha,
|
||||
tha, // spellchecker:disable-line
|
||||
};
|
||||
|
|
|
|||
|
|
@ -151,6 +151,15 @@ export default {
|
|||
dueDate: 'วันครบกำหนด',
|
||||
year: 'ปี',
|
||||
tableOfContent: 'สารบัญ',
|
||||
draw: 'วาด',
|
||||
newUpload: 'อัปโหลดใหม่',
|
||||
nativeLanguage: '{msg} ภาษาต้นทาง',
|
||||
copy: 'คัดลอก',
|
||||
paste: 'วาง',
|
||||
period: 'ช่วงเวลา',
|
||||
documentStatus: 'สถานะเอกสาร',
|
||||
advanceSearch: 'ค้นหาขั้นสูง',
|
||||
totalPeople: '{meg} คน',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -246,6 +255,7 @@ export default {
|
|||
manual: {
|
||||
title: 'คู่มือ',
|
||||
usage: 'การใช้งาน',
|
||||
troubleshooting: 'การแก้ปัญหา',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -453,6 +463,12 @@ export default {
|
|||
citizenId: 'เลขที่บัตรประชาชน',
|
||||
citizenIssue: 'วันที่ออกบัตร',
|
||||
citizenExpire: 'วันที่หมดอายุ',
|
||||
agencyStatus: 'สถานะเอเจนซี่',
|
||||
normal: 'ปกติ',
|
||||
canceled: 'ยกเลิก',
|
||||
blacklist: 'แบล็คลิสต์',
|
||||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -571,7 +587,7 @@ export default {
|
|||
family: 'ข้อมูลครอบครัว',
|
||||
},
|
||||
workerStatus: 'สถานะคนงาน',
|
||||
previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
|
||||
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า',
|
||||
employerBranch: 'สาขานายจ้าง',
|
||||
employeeCode: 'รหัสลูกจ้าง',
|
||||
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
|
||||
|
|
@ -778,7 +794,7 @@ export default {
|
|||
branch: 'สาขาที่ออกใบเสนอราคา',
|
||||
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
|
||||
customer: 'ลูกค้า',
|
||||
newCustomer: 'แรงงานใหม่',
|
||||
newCustomer: 'ลูกค้าใหม่',
|
||||
employeeList: 'รายชื่อแรงงาน',
|
||||
employee: 'แรงงาน',
|
||||
employeeName: 'ชื่อ-นามสกุล แรงงาน',
|
||||
|
|
@ -901,6 +917,9 @@ export default {
|
|||
code: 'รหัสหน่วยงาน',
|
||||
group: 'กลุ่มหน่วยงาน',
|
||||
name: 'ชื่อหน่วยงาน',
|
||||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
contactTel: 'เบอร์โทรผู้ติดต่อ',
|
||||
bankInfo: 'ข้อมูลธนาคาร',
|
||||
},
|
||||
|
||||
requestList: {
|
||||
|
|
@ -922,6 +941,7 @@ export default {
|
|||
nonLocalEmployee: 'พนักงานนอกพื้นที่',
|
||||
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
|
||||
salesRepresentative: 'พนักงานขาย',
|
||||
dataOffice: 'สำนักงานเขตจัดหางาน',
|
||||
ref: 'อ้างอิง',
|
||||
action: {
|
||||
title: 'จัดการ',
|
||||
|
|
@ -1040,6 +1060,9 @@ export default {
|
|||
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
|
||||
},
|
||||
message: {
|
||||
copy: 'คัดลอก',
|
||||
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
|
||||
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
|
||||
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
|
||||
beingUse: '"{msg}" มีการใช้งานอยู่',
|
||||
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
|
||||
|
|
@ -1161,13 +1184,14 @@ export default {
|
|||
'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น',
|
||||
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
|
||||
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
|
||||
samePropertyNameExists: 'คุณสมบัตินี้มีอยู่ในระบบอยู่แล้ว',
|
||||
|
||||
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
|
||||
codeMisMatch: 'รหัสไม่ตรงกัน',
|
||||
crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้',
|
||||
errorOccure:
|
||||
errorOccurred:
|
||||
'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง',
|
||||
invalideData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
|
||||
invalidData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
|
||||
authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง',
|
||||
installmentsValidateFailed:
|
||||
'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ',
|
||||
|
|
@ -1457,4 +1481,19 @@ export default {
|
|||
type: 'ประเภท',
|
||||
},
|
||||
},
|
||||
|
||||
dateRange: {
|
||||
today: 'วันนี้',
|
||||
yesterday: 'เมื่อวานนี้',
|
||||
thisWeek: 'สัปดาห์นี้',
|
||||
lastWeek: 'สัปดาห์ที่แล้ว',
|
||||
thisMonth: 'เดือนนี้',
|
||||
lastMonth: 'เดือนที่แล้ว',
|
||||
thisYear: 'ปีนี้',
|
||||
lastYear: 'ปีที่แล้ว',
|
||||
last7Days: '7 วันที่ผ่านมา',
|
||||
last30Days: '30 วันที่ผ่านมา',
|
||||
last90Days: '90 วันที่ผ่านมา',
|
||||
customDateRange: 'กำหนดช่วงวันที่เอง',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -215,6 +215,10 @@ function initMenu() {
|
|||
label: 'usage',
|
||||
route: '/manual',
|
||||
},
|
||||
{
|
||||
label: 'troubleshooting',
|
||||
route: '/troubleshooting',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ const leftDrawerMini = ref(false);
|
|||
const unread = computed<number>(
|
||||
() => notificationData.value.filter((v) => !v.read).length || 0,
|
||||
);
|
||||
// const filterRole = ref<string[]>();
|
||||
const userImage = ref<string>();
|
||||
const userGender = ref('');
|
||||
const canvasRef = ref();
|
||||
|
|
@ -124,6 +123,17 @@ function readNoti(id: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function signatureSubmit() {
|
||||
const signature = canvasRef.value.setCanvas();
|
||||
userStore.setSignature(signature);
|
||||
canvasModal.value = false;
|
||||
}
|
||||
|
||||
async function signatureFetch() {
|
||||
const ret = await userStore.getSignature();
|
||||
if (ret) canvasRef.value.getCanvas(ret);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
initTheme();
|
||||
initLang();
|
||||
|
|
@ -367,12 +377,28 @@ onMounted(async () => {
|
|||
<div class="col column text-caption q-pl-md ellipsis">
|
||||
<span class="block ellipsis full-width text-weight-bold">
|
||||
{{ item.title }}
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
:delay="300"
|
||||
:offset="[10, 10]"
|
||||
>
|
||||
{{ item.title }}
|
||||
</q-tooltip>
|
||||
</span>
|
||||
<span
|
||||
class="block ellipsis full-width text-stone"
|
||||
:class="{ 'text-weight-medium': !item.read }"
|
||||
>
|
||||
{{ item.detail }}
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
:delay="300"
|
||||
:offset="[10, 10]"
|
||||
>
|
||||
{{ item.detail }}
|
||||
</q-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
|
|
@ -382,15 +408,6 @@ onMounted(async () => {
|
|||
>
|
||||
{{ moment(item.createdAt).fromNow() }}
|
||||
</span>
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
:delay="1000"
|
||||
:offset="[10, 10]"
|
||||
>
|
||||
{{ item.title }}
|
||||
{{ item.detail }}
|
||||
</q-tooltip>
|
||||
</q-item>
|
||||
</section>
|
||||
<section
|
||||
|
|
@ -495,13 +512,15 @@ onMounted(async () => {
|
|||
no-app-box
|
||||
:title="$t('menu.profile.addSignature')"
|
||||
:close="() => (canvasModal = false)"
|
||||
:submit="signatureSubmit"
|
||||
:show="signatureFetch"
|
||||
>
|
||||
<CanvasComponent ref="canvasRef" v-model:modal="canvasModal" />
|
||||
<template #footer>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
:label="$t('clear')"
|
||||
:label="$t('general.clear')"
|
||||
@click="
|
||||
() => {
|
||||
canvasRef.clearCanvas(), canvasRef.clearUpload();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const options = [
|
|||
label: 'menu.profile.signature',
|
||||
value: 'signature',
|
||||
color: 'grey',
|
||||
disabled: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
icon: 'mdi-brightness-6',
|
||||
|
|
|
|||
2
src/markdown-it.d.ts
vendored
Normal file
2
src/markdown-it.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
declare module 'markdown-it-image-figures';
|
||||
declare module 'markdown-it-html5-media';
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
// NOTE: Library
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted } from 'vue';
|
||||
import { onMounted, watch } from 'vue';
|
||||
|
||||
// NOTE: Components
|
||||
|
||||
|
|
@ -10,22 +10,33 @@ import { onMounted } from 'vue';
|
|||
import { useManualStore } from 'src/stores/manual';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
// NOTE: Variable
|
||||
const route = useRoute();
|
||||
const manualStore = useManualStore();
|
||||
const navigatorStore = useNavigator();
|
||||
const { dataManual } = storeToRefs(manualStore);
|
||||
|
||||
async function fetchManual() {
|
||||
const res = await manualStore.getManual();
|
||||
dataManual.value = res ? res : [];
|
||||
}
|
||||
const { dataManual, dataTroubleshooting } = storeToRefs(manualStore);
|
||||
|
||||
onMounted(async () => {
|
||||
navigatorStore.current.title = 'menu.manual.title';
|
||||
navigatorStore.current.path = [{ text: '' }];
|
||||
await fetchManual();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
async () => {
|
||||
if (route.name === 'Manual') {
|
||||
const res = await manualStore.getManual();
|
||||
dataManual.value = res ? res : [];
|
||||
}
|
||||
if (route.name === 'Troubleshooting') {
|
||||
const res = await manualStore.getTroubleshooting();
|
||||
dataTroubleshooting.value = res ? res : [];
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -34,7 +45,7 @@ onMounted(async () => {
|
|||
>
|
||||
<section class="scroll q-gutter-y-sm">
|
||||
<q-expansion-item
|
||||
v-for="v in dataManual"
|
||||
v-for="v in $route.name === 'Manual' ? dataManual : dataTroubleshooting"
|
||||
:key="v.labelEN"
|
||||
:content-inset-level="0.5"
|
||||
class="rounded overflow-hidden bordered"
|
||||
|
|
@ -58,7 +69,11 @@ onMounted(async () => {
|
|||
clickable
|
||||
dense
|
||||
class="dot items-center rounded q-my-xs"
|
||||
:to="`/manual/${v.category}/${x.name}`"
|
||||
:to="
|
||||
$route.name === 'Manual'
|
||||
? `/manual/${v.category}/${x.name}`
|
||||
: `/troubleshooting/${v.category}/${x.name}`
|
||||
"
|
||||
>
|
||||
<Icon
|
||||
v-if="!!x.icon"
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import hljs from 'highlight.js';
|
|||
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
// @ts-expect-error
|
||||
import mditFigureWithPCaption from 'markdown-it-image-figures';
|
||||
// @ts-expect-error
|
||||
import mditMedia from 'markdown-it-html5-media';
|
||||
import mditAnchor from 'markdown-it-anchor';
|
||||
import mditHighlight from 'markdown-it-highlightjs';
|
||||
|
|
@ -58,14 +56,28 @@ onUnmounted(() => {
|
|||
|
||||
async function getContent() {
|
||||
if (!category.value || !page.value) return;
|
||||
const res = await manualStore.getManualByPage({
|
||||
category: category.value,
|
||||
pageName: page.value,
|
||||
});
|
||||
if (res && res.ok) {
|
||||
const text = await res.text();
|
||||
content.value = text;
|
||||
contentParsed.value = md.parse(text, {});
|
||||
|
||||
if (ROUTE.name === 'ManualView') {
|
||||
const res = await manualStore.getManualByPage({
|
||||
category: category.value,
|
||||
pageName: page.value,
|
||||
});
|
||||
if (res && res.ok) {
|
||||
const text = await res.text();
|
||||
content.value = text;
|
||||
contentParsed.value = md.parse(text, {});
|
||||
}
|
||||
}
|
||||
if (ROUTE.name === 'TroubleshootingView') {
|
||||
const res = await manualStore.getTroubleshootingByPage({
|
||||
category: category.value,
|
||||
pageName: page.value,
|
||||
});
|
||||
if (res && res.ok) {
|
||||
const text = await res.text();
|
||||
content.value = text;
|
||||
contentParsed.value = md.parse(text, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +198,9 @@ async function scrollTo(id: string) {
|
|||
md.render(
|
||||
content.replaceAll(
|
||||
'assets/',
|
||||
`${baseUrl}/manual/${category}/assets/`,
|
||||
$route.name === 'ManualView'
|
||||
? `${baseUrl}/manual/${category}/assets/`
|
||||
: `${baseUrl}/troubleshooting/${category}/assets/`,
|
||||
),
|
||||
)
|
||||
"
|
||||
|
|
@ -317,4 +331,20 @@ async function scrollTo(id: string) {
|
|||
.markdown :deep(video) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.markdown :deep(table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.markdown :deep(:where(table th)) {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.markdown :deep(:where(table td, table th)) {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.25rem 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -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 { QSelect, QTableProps, QTableSlots } from 'quasar';
|
||||
import type { QTableProps, QTableSlots } from 'quasar';
|
||||
import { resetScrollBar } from 'src/stores/utils';
|
||||
import useBranchStore from 'stores/branch';
|
||||
import useFlowStore from 'stores/flow';
|
||||
|
|
@ -52,6 +52,7 @@ import {
|
|||
UndoButton,
|
||||
} from 'components/button';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const $q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
|
@ -72,7 +73,6 @@ const typeBranchItem = [
|
|||
color: 'var(--blue-6-hsl)',
|
||||
},
|
||||
];
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
const holdDialog = ref(false);
|
||||
const isSubCreate = ref(false);
|
||||
const columns = [
|
||||
|
|
@ -175,6 +175,8 @@ const qrCodeDialog = ref(false);
|
|||
const qrCodeimageUrl = ref<string>('');
|
||||
const formLastSubBranch = ref<number>(0);
|
||||
|
||||
const searchDate = ref<string[]>([]);
|
||||
|
||||
const branchStore = useBranchStore();
|
||||
const flowStore = useFlowStore();
|
||||
const { locale } = useI18n();
|
||||
|
|
@ -715,12 +717,20 @@ async function fetchList(opts: {
|
|||
tree?: boolean;
|
||||
withHead?: boolean;
|
||||
filter?: 'head' | 'sub';
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}) {
|
||||
await branchStore.fetchList(opts);
|
||||
}
|
||||
|
||||
watch(inputSearch, () => {
|
||||
fetchList({ tree: true, query: inputSearch.value, withHead: true });
|
||||
watch([inputSearch, searchDate], () => {
|
||||
fetchList({
|
||||
tree: true,
|
||||
query: inputSearch.value,
|
||||
withHead: true,
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
});
|
||||
currentSubBranch.value = undefined;
|
||||
});
|
||||
|
||||
|
|
@ -1170,26 +1180,49 @@ watch(currentHq, () => {
|
|||
<template v-slot:prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
|
||||
<AdvanceSearch
|
||||
v-model="searchDate"
|
||||
:active="$q.screen.lt.md && statusFilter !== 'all'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
autocomplete="off"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'all' },
|
||||
{
|
||||
label: $t('status.ACTIVE'),
|
||||
value: 'statusACTIVE',
|
||||
},
|
||||
{
|
||||
label: $t('status.INACTIVE'),
|
||||
value: 'statusINACTIVE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-6 justify-end">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import useOptionStore from 'stores/options';
|
|||
import useAddressStore from 'stores/address';
|
||||
import useMyBranch from 'src/stores/my-branch';
|
||||
import { calculateAge } from 'src/utils/datetime';
|
||||
import { QSelect, useQuasar, type QTableProps } from 'quasar';
|
||||
import { dialog, baseUrl } from 'stores/utils';
|
||||
import { useQuasar, type QTableProps } from 'quasar';
|
||||
import { dialog, baseUrl, setPrefixName } from 'stores/utils';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { isRoleInclude, resetScrollBar } from 'src/stores/utils';
|
||||
import { BranchUserStats } from 'stores/branch/types';
|
||||
|
|
@ -49,6 +49,7 @@ import FormPerson from 'components/02_personnel-management/FormPerson.vue';
|
|||
import FormByType from 'components/02_personnel-management/FormByType.vue';
|
||||
import FormInformation from 'components/02_personnel-management/FormInformation.vue';
|
||||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
|
|
@ -73,7 +74,6 @@ const isImageEdit = ref(false);
|
|||
const imageDialog = ref(false);
|
||||
const infoDrawerEdit = ref(false);
|
||||
const refreshImageState = ref(false);
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
const firstScroll = ref(false);
|
||||
|
||||
const inputSearch = ref('');
|
||||
|
|
@ -93,12 +93,14 @@ const currentUser = ref<User>();
|
|||
const userCode = ref<string>();
|
||||
|
||||
const statusToggle = ref(true);
|
||||
const agencyFile = ref<File[]>([]);
|
||||
const agencyFileList = ref<{ name: string; url: string }[]>([]);
|
||||
const userFile = ref<File[]>([]);
|
||||
const userFileList = ref<{ name: string; url: string }[]>([]);
|
||||
|
||||
const typeStats = ref<UserTypeStats>();
|
||||
const userStats = ref<BranchUserStats[]>();
|
||||
|
||||
const searchDate = ref<[]>([]);
|
||||
|
||||
const urlProfile = ref<string>();
|
||||
const profileFileImg = ref<File | null>(null);
|
||||
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
||||
|
|
@ -124,7 +126,7 @@ const defaultFormData = {
|
|||
streetEN: '',
|
||||
street: '',
|
||||
trainingPlace: null,
|
||||
importNationality: null,
|
||||
importNationality: [],
|
||||
sourceNationality: null,
|
||||
licenseExpireDate: null,
|
||||
licenseIssueDate: null,
|
||||
|
|
@ -151,6 +153,10 @@ const defaultFormData = {
|
|||
citizenExpire: null,
|
||||
citizenIssue: null,
|
||||
citizenId: '',
|
||||
contactName: '',
|
||||
contactTel: '',
|
||||
remark: '',
|
||||
agencyStatus: '',
|
||||
};
|
||||
|
||||
const formData = ref<UserCreate>({
|
||||
|
|
@ -172,7 +178,7 @@ const formData = ref<UserCreate>({
|
|||
streetEN: '',
|
||||
street: '',
|
||||
trainingPlace: null,
|
||||
importNationality: null,
|
||||
importNationality: [],
|
||||
sourceNationality: null,
|
||||
licenseExpireDate: null,
|
||||
licenseIssueDate: null,
|
||||
|
|
@ -199,6 +205,10 @@ const formData = ref<UserCreate>({
|
|||
citizenExpire: null,
|
||||
citizenIssue: null,
|
||||
citizenId: '',
|
||||
contactName: '',
|
||||
contactTel: '',
|
||||
remark: '',
|
||||
agencyStatus: '',
|
||||
});
|
||||
|
||||
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
||||
|
|
@ -327,7 +337,7 @@ function onClose(excludeDialog?: boolean) {
|
|||
urlProfile.value = '';
|
||||
profileFileImg.value = null;
|
||||
infoDrawerEdit.value = false;
|
||||
agencyFile.value = [];
|
||||
userFile.value = [];
|
||||
isEdit.value = false;
|
||||
statusToggle.value = true;
|
||||
isImageEdit.value = false;
|
||||
|
|
@ -336,6 +346,8 @@ function onClose(excludeDialog?: boolean) {
|
|||
mapUserType(currentTab.value);
|
||||
imageList.value = { selectedImage: '', list: [] };
|
||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
||||
userFileList.value = [];
|
||||
userFile.value = [];
|
||||
flowStore.rotate();
|
||||
}
|
||||
|
||||
|
|
@ -356,12 +368,10 @@ async function openDialog(
|
|||
isEdit.value = true;
|
||||
await assignFormData(id);
|
||||
|
||||
if (formData.value.userType === 'AGENCY') {
|
||||
const result = await userStore.fetchAttachment(id);
|
||||
const result = await userStore.fetchAttachment(id);
|
||||
|
||||
if (result) {
|
||||
agencyFileList.value = result;
|
||||
}
|
||||
if (result) {
|
||||
userFileList.value = result;
|
||||
}
|
||||
}
|
||||
if (userStore.userOption.hqOpts.length !== 0 && !id) {
|
||||
|
|
@ -419,15 +429,15 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
: '';
|
||||
const formDataEdit = {
|
||||
...formData.value,
|
||||
checkpointEN: formData.value.checkpoint,
|
||||
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE',
|
||||
} as const;
|
||||
|
||||
await userStore.editById(currentUser.value.id, formDataEdit);
|
||||
|
||||
if (currentUser.value.id && formDataEdit.userType === 'AGENCY') {
|
||||
if (!agencyFile.value) return;
|
||||
if (userFile.value) {
|
||||
const payload: UserAttachmentCreate = {
|
||||
file: agencyFile.value,
|
||||
file: userFile.value,
|
||||
};
|
||||
|
||||
if (payload?.file) {
|
||||
|
|
@ -450,16 +460,15 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
: hqId.value
|
||||
? hqId.value
|
||||
: '';
|
||||
formData.value.checkpointEN = formData.value.checkpoint;
|
||||
const result = await userStore.create(
|
||||
formData.value,
|
||||
onCreateImageList.value,
|
||||
);
|
||||
|
||||
if (result && formData.value.userType === 'AGENCY') {
|
||||
if (!agencyFile.value) return;
|
||||
|
||||
if (userFile.value && result) {
|
||||
const payload: UserAttachmentCreate = {
|
||||
file: agencyFile.value,
|
||||
file: userFile.value,
|
||||
};
|
||||
|
||||
if (payload?.file) {
|
||||
|
|
@ -546,6 +555,7 @@ async function triggerChangeStatus(id: string, status: string) {
|
|||
async function assignFormData(idEdit: string) {
|
||||
if (!userData.value) return;
|
||||
const foundUser = userData.value.result.find((user) => user.id === idEdit);
|
||||
console.log(foundUser);
|
||||
|
||||
if (foundUser) {
|
||||
currentUser.value = foundUser;
|
||||
|
|
@ -567,7 +577,10 @@ async function assignFormData(idEdit: string) {
|
|||
street: foundUser.street,
|
||||
streetEN: foundUser.streetEN,
|
||||
trainingPlace: foundUser.trainingPlace,
|
||||
importNationality: foundUser.importNationality,
|
||||
importNationality:
|
||||
typeof foundUser.importNationality === 'string'
|
||||
? [foundUser.importNationality]
|
||||
: foundUser.importNationality,
|
||||
sourceNationality: foundUser.sourceNationality,
|
||||
licenseNo: foundUser.licenseNo,
|
||||
discountCondition: foundUser.discountCondition,
|
||||
|
|
@ -587,6 +600,8 @@ async function assignFormData(idEdit: string) {
|
|||
responsibleArea: foundUser.responsibleArea,
|
||||
status: foundUser.status,
|
||||
selectedImage: foundUser.selectedImage,
|
||||
contactName: foundUser.contactName,
|
||||
contactTel: foundUser.contactTel,
|
||||
licenseExpireDate:
|
||||
(foundUser.licenseExpireDate &&
|
||||
new Date(foundUser.licenseExpireDate)) ||
|
||||
|
|
@ -603,6 +618,8 @@ async function assignFormData(idEdit: string) {
|
|||
(foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null,
|
||||
citizenExpire:
|
||||
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null,
|
||||
remark: foundUser.remark || '',
|
||||
agencyStatus: foundUser.agencyStatus || '',
|
||||
};
|
||||
|
||||
formData.value.status === 'ACTIVE' || 'CREATED'
|
||||
|
|
@ -661,6 +678,8 @@ async function fetchUserList(mobileFetch?: boolean) {
|
|||
: statusFilter.value === 'statusACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -735,11 +754,11 @@ watch(
|
|||
formData.value.responsibleArea = null;
|
||||
formData.value.discountCondition = null;
|
||||
formData.value.sourceNationality = null;
|
||||
formData.value.importNationality = null;
|
||||
formData.value.importNationality = [];
|
||||
formData.value.trainingPlace = null;
|
||||
formData.value.checkpoint = null;
|
||||
formData.value.checkpointEN = null;
|
||||
agencyFile.value = [];
|
||||
userFile.value = [];
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -750,7 +769,7 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
watch([inputSearch, statusFilter, pageSize], async () => {
|
||||
watch([inputSearch, statusFilter, pageSize, searchDate], async () => {
|
||||
if (userData.value) userData.value.result = [];
|
||||
currentPage.value = 1;
|
||||
|
||||
|
|
@ -872,26 +891,45 @@ watch(
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="searchDate"
|
||||
:active="$q.screen.lt.md && statusFilter !== 'all'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
autocomplete="off"
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'all' },
|
||||
{ label: $t('general.active'), value: 'statusACTIVE' },
|
||||
{
|
||||
label: $t('general.inactive'),
|
||||
value: 'statusINACTIVE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
|
|
@ -1551,7 +1589,18 @@ watch(
|
|||
v-model:toggle-status="formData.status"
|
||||
hideFade
|
||||
:toggle-title="$t('status.title')"
|
||||
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
||||
:title="
|
||||
setPrefixName(
|
||||
{
|
||||
namePrefix: formData.namePrefix,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
firstNameEN: formData.firstNameEN,
|
||||
lastNameEN: formData.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
"
|
||||
:caption="userCode"
|
||||
:img="
|
||||
`${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat(
|
||||
|
|
@ -1736,12 +1785,15 @@ watch(
|
|||
v-model:citizen-id="formData.citizenId"
|
||||
v-model:citizen-issue="formData.citizenIssue"
|
||||
v-model:citizen-expire="formData.citizenExpire"
|
||||
v-model:contact-name="formData.contactName"
|
||||
v-model:contact-tel="formData.contactTel"
|
||||
:title="'personnel.form.personalInformation'"
|
||||
prefix-id="drawer-info-personnel"
|
||||
dense
|
||||
outlined
|
||||
separator
|
||||
:readonly="!infoDrawerEdit"
|
||||
:agency="formData.userType === 'AGENCY'"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
|
||||
|
|
@ -1781,10 +1833,11 @@ watch(
|
|||
v-model:import-nationality="formData.importNationality"
|
||||
v-model:training-place="formData.trainingPlace"
|
||||
v-model:checkpoint="formData.checkpoint"
|
||||
v-model:checkpoint-en="formData.checkpointEN"
|
||||
v-model:agency-file="agencyFile"
|
||||
v-model:agency-file-list="agencyFileList"
|
||||
v-model:user-file="userFile"
|
||||
v-model:user-file-list="userFileList"
|
||||
v-model:user-id="currentUser.id"
|
||||
v-model:remark="formData.remark"
|
||||
v-model:agency-status="formData.agencyStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1828,7 +1881,18 @@ watch(
|
|||
}[formData.gender]
|
||||
"
|
||||
:toggleTitle="$t('status.title')"
|
||||
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
||||
:title="
|
||||
setPrefixName(
|
||||
{
|
||||
namePrefix: formData.namePrefix,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
firstNameEN: formData.firstNameEN,
|
||||
lastNameEN: formData.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
"
|
||||
:fallbackImg="
|
||||
{
|
||||
male: '/no-img-man.png',
|
||||
|
|
@ -1854,7 +1918,6 @@ watch(
|
|||
|
||||
<div
|
||||
class="col"
|
||||
id="personnel-form"
|
||||
:class="{
|
||||
'q-px-lg q-pb-lg': $q.screen.gt.sm,
|
||||
'q-px-md q-pb-sm': !$q.screen.gt.sm,
|
||||
|
|
@ -1898,7 +1961,7 @@ watch(
|
|||
? [
|
||||
{
|
||||
name: $t('personnel.form.workInformation'),
|
||||
anchor: 'dialog-info-work',
|
||||
anchor: 'dialog-form-work',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
|
|
@ -1914,6 +1977,7 @@ watch(
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="personnel-form"
|
||||
class="col-md-10 col-12 full-height scroll"
|
||||
:class="{
|
||||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
|
|
@ -1939,6 +2003,7 @@ watch(
|
|||
id="dialog-form-personal"
|
||||
prefix-id="form-dialog-personnel"
|
||||
dense
|
||||
:agency="formData.userType === 'AGENCY'"
|
||||
outlined
|
||||
separator
|
||||
:title="'personnel.form.personalInformation'"
|
||||
|
|
@ -1957,6 +2022,8 @@ watch(
|
|||
v-model:citizen-id="formData.citizenId"
|
||||
v-model:citizen-issue="formData.citizenIssue"
|
||||
v-model:citizen-expire="formData.citizenExpire"
|
||||
v-model:contact-name="formData.contactName"
|
||||
v-model:contact-tel="formData.contactTel"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<AddressForm
|
||||
|
|
@ -1992,8 +2059,10 @@ watch(
|
|||
v-model:import-nationality="formData.importNationality"
|
||||
v-model:training-place="formData.trainingPlace"
|
||||
v-model:checkpoint="formData.checkpoint"
|
||||
v-model:checkpoint-en="formData.checkpointEN"
|
||||
v-model:agency-file="agencyFile"
|
||||
v-model:agency-status="formData.agencyStatus"
|
||||
v-model:remark="formData.remark"
|
||||
v-model:user-file="userFile"
|
||||
v-model:user-file-list="userFileList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { QSelect, useQuasar } from 'quasar';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getUserId, getRole } from 'src/services/keycloak';
|
||||
import { baseUrl, waitAll } from 'src/stores/utils';
|
||||
import { baseUrl, setPrefixName, waitAll } from 'src/stores/utils';
|
||||
import { dateFormat } from 'src/utils/datetime';
|
||||
import { dialogCheckData } from 'stores/utils';
|
||||
|
||||
|
|
@ -86,6 +86,7 @@ import { nextTick } from 'vue';
|
|||
import FormEmployeeVisa from 'components/03_customer-management/FormEmployeeVisa.vue';
|
||||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||
import { AddButton } from 'components/button';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const $q = useQuasar();
|
||||
|
|
@ -101,28 +102,33 @@ const employeeFormStore = useEmployeeForm();
|
|||
const optionStore = useOptionStore();
|
||||
const ocrStore = useOcrStore();
|
||||
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
const statusEmployeeCreate = ref<boolean>(false);
|
||||
const mrz = ref<Awaited<ReturnType<typeof parseResultMRZ>>>();
|
||||
const tabFieldRequired = ref<{ [key: string]: (keyof CustomerBranchCreate)[] }>(
|
||||
{
|
||||
main: [],
|
||||
business: ['businessType', 'jobPosition'],
|
||||
address: [
|
||||
'address',
|
||||
'addressEN',
|
||||
'provinceId',
|
||||
'districtId',
|
||||
'subDistrictId',
|
||||
],
|
||||
contact: [],
|
||||
},
|
||||
);
|
||||
|
||||
const { state: customerFormState, currentFormData: customerFormData } =
|
||||
storeToRefs(customerFormStore);
|
||||
const { state: employeeFormState, currentFromDataEmployee } =
|
||||
storeToRefs(employeeFormStore);
|
||||
const {
|
||||
fetchListOfOptionBranch,
|
||||
customerFormUndo,
|
||||
customerConfirmUnsave,
|
||||
deleteCustomerById,
|
||||
validateTabField,
|
||||
deleteCustomerBranchById,
|
||||
} = customerFormStore;
|
||||
|
||||
const { employeeFormUndo, employeeConfirmUnsave, deleteEmployeeById } =
|
||||
employeeFormStore;
|
||||
|
||||
const {
|
||||
state: customerFormState,
|
||||
currentFormData: customerFormData,
|
||||
registerAbleBranchOption,
|
||||
tabFieldRequired,
|
||||
} = storeToRefs(customerFormStore);
|
||||
const {
|
||||
state: employeeFormState,
|
||||
currentFromDataEmployee,
|
||||
onCreateImageList,
|
||||
statusEmployeeCreate,
|
||||
refreshImageState,
|
||||
} = storeToRefs(employeeFormStore);
|
||||
|
||||
async function init() {
|
||||
navigatorStore.current.title = 'menu.customer';
|
||||
|
|
@ -136,6 +142,14 @@ async function init() {
|
|||
|
||||
gridView.value = $q.screen.lt.md ? true : false;
|
||||
|
||||
if (route.query.tab === 'customer') {
|
||||
currentTab.value = 'employer';
|
||||
if (route.query.id) openSpecificCustomer(route.query.id as string);
|
||||
} else if (route.query.tab === 'employee') {
|
||||
currentTab.value = 'employee';
|
||||
if (route.query.id) openSpecificEmployee(route.query.id as string);
|
||||
}
|
||||
|
||||
if (route.name === 'CustomerManagement') await fetchListCustomer(true);
|
||||
|
||||
if (
|
||||
|
|
@ -175,6 +189,7 @@ const statsCustomerType = ref<CustomerStats>({
|
|||
});
|
||||
|
||||
// NOTE: Page State
|
||||
const searchDate = ref<string[]>([]);
|
||||
const currentTab = ref<'employer' | 'employee'>('employer');
|
||||
const inputSearch = ref('');
|
||||
const currentStatus = ref<Status | 'All'>('All');
|
||||
|
|
@ -209,17 +224,19 @@ const dialogCustomerImageUpload = ref<InstanceType<typeof ImageUploadDialog>>();
|
|||
const dialogEmployeeImageUpload = ref<InstanceType<typeof ImageUploadDialog>>();
|
||||
|
||||
// image
|
||||
const refreshImageState = ref(false);
|
||||
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
||||
const onCreateImageList = ref<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>({ selectedImage: '', list: [] });
|
||||
|
||||
watch(() => route.name, init);
|
||||
watch(
|
||||
[currentTab, currentStatus, inputSearch, customerTypeSelected, pageSize],
|
||||
async ([tabName]) => {
|
||||
[
|
||||
currentTab,
|
||||
currentStatus,
|
||||
inputSearch,
|
||||
customerTypeSelected,
|
||||
pageSize,
|
||||
searchDate,
|
||||
],
|
||||
async ([tabName], [oldTabName]) => {
|
||||
// if (tabName !== oldTabName) searchDate.value = [];
|
||||
if (tabName === 'employer') {
|
||||
currentPageCustomer.value = 1;
|
||||
currentBtnOpen.value = [];
|
||||
|
|
@ -276,8 +293,6 @@ const fieldSelected = ref<string[]>(
|
|||
].filter((v, index, self) => self.indexOf(v) === index),
|
||||
);
|
||||
|
||||
const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
|
||||
|
||||
const branch = ref<CustomerBranch[]>();
|
||||
|
||||
const customerStats = [
|
||||
|
|
@ -294,81 +309,6 @@ const fieldCustomer = [
|
|||
const employeeHistoryDialog = ref(false);
|
||||
const employeeHistory = ref<EmployeeHistory[]>();
|
||||
|
||||
function validateTabField<T = CustomerBranchCreate>(
|
||||
value: T,
|
||||
fieldRequired: { [key: string]: (keyof T)[] },
|
||||
) {
|
||||
const list: string[] = [];
|
||||
|
||||
for (const tab in fieldRequired) {
|
||||
for (const field of fieldRequired[tab]) {
|
||||
if (!value[field] && !list.includes(tab)) list.push(tab);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
function deleteCustomerById(id: string) {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
await customerStore.deleteById(id);
|
||||
|
||||
await fetchListCustomer(true, $q.screen.xs);
|
||||
customerFormState.value.dialogModal = false;
|
||||
flowStore.rotate();
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteCustomerBranchById(id: string) {
|
||||
return await new Promise((resolve) => {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
await customerStore.deleteBranchById(id);
|
||||
flowStore.rotate();
|
||||
resolve(true);
|
||||
},
|
||||
cancel: () => {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
async function fetchListOfOptionBranch() {
|
||||
if (registerAbleBranchOption.value) return;
|
||||
|
||||
const uid = getUserId();
|
||||
const role = getRole();
|
||||
|
||||
if (!uid) return; // should not possible as the system require login to be able to access resource.
|
||||
|
||||
if (role?.includes('system')) {
|
||||
const result = await userBranchStore.fetchListOptionBranch();
|
||||
if (result && result.total > 0)
|
||||
registerAbleBranchOption.value = result.result;
|
||||
} else {
|
||||
const result = await userBranchStore.fetchListMyBranch(uid);
|
||||
if (result && result.total > 0)
|
||||
registerAbleBranchOption.value = result.result;
|
||||
}
|
||||
|
||||
// TODO: Assign (first) branch of the user as register branch of the data
|
||||
}
|
||||
|
||||
async function fetchListCustomer(fetchStats = false, mobileFetch?: boolean) {
|
||||
const total = statsCustomerType.value.PERS + statsCustomerType.value.CORP;
|
||||
|
||||
|
|
@ -386,6 +326,8 @@ async function fetchListCustomer(fetchStats = false, mobileFetch?: boolean) {
|
|||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
query: inputSearch.value,
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
customerType: (
|
||||
{
|
||||
all: undefined,
|
||||
|
|
@ -439,6 +381,8 @@ async function fetchListEmployee(opt?: {
|
|||
query: inputSearch.value,
|
||||
passport: true,
|
||||
visa: true,
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
});
|
||||
if (resultListEmployee) {
|
||||
maxPageEmployee.value = Math.ceil(
|
||||
|
|
@ -503,64 +447,6 @@ async function toggleStatusCustomer(id: string, status: boolean) {
|
|||
await fetchListCustomer(false, $q.screen.xs);
|
||||
flowStore.rotate();
|
||||
}
|
||||
|
||||
async function deleteEmployeeById(opts: {
|
||||
id?: string;
|
||||
type?: 'passport' | 'visa' | 'healthCheck' | 'work';
|
||||
index?: number;
|
||||
}) {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
if (opts.type === 'passport' && opts.index !== undefined) {
|
||||
await employeeFormStore.deletePassport(opts.index);
|
||||
}
|
||||
|
||||
if (opts.type === 'visa' && opts.index !== undefined) {
|
||||
await employeeFormStore.deleteVisa(opts.index);
|
||||
}
|
||||
|
||||
if (opts.type === 'healthCheck' && opts.index !== undefined) {
|
||||
await employeeFormStore.deleteHealthCheck(opts.index);
|
||||
}
|
||||
|
||||
if (opts.type === 'work' && opts.index !== undefined) {
|
||||
await employeeFormStore.deleteWorkHistory(opts.index);
|
||||
} else {
|
||||
if (!!opts.id) {
|
||||
const result = await employeeStore.deleteById(opts.id);
|
||||
|
||||
if (result) {
|
||||
employeeFormState.value.drawerModal = false;
|
||||
employeeFormState.value.dialogModal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (route.name !== 'CustomerBranchManagement') {
|
||||
await fetchListEmployee(
|
||||
currentTab.value === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId: customerFormState.value.currentCustomerId,
|
||||
}
|
||||
: { fetchStats: true, mobileFetch: $q.screen.xs },
|
||||
);
|
||||
|
||||
flowStore.rotate();
|
||||
}
|
||||
},
|
||||
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
async function openHistory(id: string) {
|
||||
const res = await employeeStore.getEditHistory(id);
|
||||
employeeHistory.value = res.reverse();
|
||||
|
|
@ -594,64 +480,6 @@ async function editEmployeeFormPersonal(id: string) {
|
|||
employeeFormState.value.drawerModal = true;
|
||||
}
|
||||
|
||||
function employeeConfirmUnsave(close = true) {
|
||||
dialog({
|
||||
color: 'warning',
|
||||
icon: 'mdi-alert',
|
||||
title: t('form.warning.title'),
|
||||
actionText: t('general.ok'),
|
||||
persistent: true,
|
||||
message: t('form.warning.unsave'),
|
||||
action: () => {
|
||||
employeeFormStore.resetFormDataEmployee();
|
||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
||||
employeeFormState.value.editReadonly = true;
|
||||
employeeFormState.value.dialogModal = !close;
|
||||
employeeFormState.value.drawerModal = !close;
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
function employeeFormUndo(close = true) {
|
||||
if (employeeFormStore.isFormDataDifferent()) {
|
||||
return employeeConfirmUnsave(close);
|
||||
}
|
||||
employeeFormStore.resetFormDataEmployee();
|
||||
employeeFormState.value.editReadonly = true;
|
||||
}
|
||||
|
||||
function customerConfirmUnsave(close = true) {
|
||||
dialog({
|
||||
color: 'warning',
|
||||
icon: 'mdi-alert',
|
||||
title: t('form.warning.title'),
|
||||
actionText: t('general.ok'),
|
||||
persistent: true,
|
||||
message: t('form.warning.unsave'),
|
||||
|
||||
action: () => {
|
||||
customerFormStore.resetForm();
|
||||
customerFormState.value.readonly = true;
|
||||
|
||||
if (!customerFormState.value.drawerModal) {
|
||||
customerFormState.value.dialogModal = !close;
|
||||
} else {
|
||||
customerFormState.value.drawerModal = !close;
|
||||
}
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
function customerFormUndo(close = true) {
|
||||
if (customerFormStore.isFormDataDifferent()) {
|
||||
return customerConfirmUnsave(close);
|
||||
}
|
||||
customerFormStore.resetForm();
|
||||
customerFormState.value.readonly = true;
|
||||
}
|
||||
|
||||
async function createCustomerForm(customerType: 'CORP' | 'PERS') {
|
||||
customerFormState.value.dialogModal = true;
|
||||
customerFormState.value.dialogType = 'create';
|
||||
|
|
@ -683,10 +511,34 @@ async function fetchImageList(
|
|||
return res;
|
||||
}
|
||||
|
||||
async function openSpecificCustomer(id: string) {
|
||||
await customerFormStore.assignFormData(id);
|
||||
await fetchImageList(
|
||||
id,
|
||||
customerFormData.value.selectedImage || '',
|
||||
'customer',
|
||||
);
|
||||
customerFormState.value.branchIndex = -1;
|
||||
customerFormState.value.drawerModal = true;
|
||||
customerFormState.value.editCustomerId = id;
|
||||
customerFormState.value.dialogType = 'info';
|
||||
}
|
||||
|
||||
async function openSpecificEmployee(id: string) {
|
||||
await employeeFormStore.assignFormDataEmployee(id);
|
||||
await fetchImageList(
|
||||
id,
|
||||
currentFromDataEmployee.value.selectedImage || '',
|
||||
'employee',
|
||||
);
|
||||
employeeFormState.value.dialogType = 'info';
|
||||
employeeFormState.value.drawerModal = true;
|
||||
}
|
||||
|
||||
// TODO: When in employee form, if select address same as customer then auto fill
|
||||
|
||||
watch(
|
||||
() => employeeFormState.value.formDataEmployeeOwner,
|
||||
() => employeeFormState.value.currentCustomerBranch,
|
||||
(e) => {
|
||||
if (!e) return;
|
||||
if (employeeFormState.value.formDataEmployeeSameAddr) {
|
||||
|
|
@ -703,21 +555,21 @@ watch(
|
|||
watch(
|
||||
() => employeeFormState.value.formDataEmployeeSameAddr,
|
||||
(isSame) => {
|
||||
if (!employeeFormState.value.formDataEmployeeOwner) return;
|
||||
if (!employeeFormState.value.currentCustomerBranch) return;
|
||||
if (isSame) {
|
||||
currentFromDataEmployee.value.address =
|
||||
employeeFormState.value.formDataEmployeeOwner.address;
|
||||
employeeFormState.value.currentCustomerBranch.address;
|
||||
currentFromDataEmployee.value.addressEN =
|
||||
employeeFormState.value.formDataEmployeeOwner.addressEN;
|
||||
employeeFormState.value.currentCustomerBranch.addressEN;
|
||||
currentFromDataEmployee.value.provinceId =
|
||||
employeeFormState.value.formDataEmployeeOwner.provinceId;
|
||||
employeeFormState.value.currentCustomerBranch.provinceId;
|
||||
currentFromDataEmployee.value.districtId =
|
||||
employeeFormState.value.formDataEmployeeOwner.districtId;
|
||||
employeeFormState.value.currentCustomerBranch.districtId;
|
||||
currentFromDataEmployee.value.subDistrictId =
|
||||
employeeFormState.value.formDataEmployeeOwner.subDistrictId;
|
||||
employeeFormState.value.currentCustomerBranch.subDistrictId;
|
||||
}
|
||||
currentFromDataEmployee.value.customerBranchId =
|
||||
employeeFormState.value.formDataEmployeeOwner.id;
|
||||
employeeFormState.value.currentCustomerBranch.id;
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -936,26 +788,43 @@ const emptyCreateDialog = ref(false);
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="searchDate"
|
||||
:active="$q.screen.lt.md && currentStatus !== 'All'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
id="select-status"
|
||||
for="select-status"
|
||||
v-model="currentStatus"
|
||||
outlined
|
||||
dense
|
||||
autocomplete="off"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'All' },
|
||||
{ label: $t('status.ACTIVE'), value: 'ACTIVE' },
|
||||
{ label: $t('status.INACTIVE'), value: 'INACTIVE' },
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
id="select-status"
|
||||
for="select-status"
|
||||
v-model="currentStatus"
|
||||
|
|
@ -1540,7 +1409,13 @@ const emptyCreateDialog = ref(false);
|
|||
customerFormState.branchIndex = 0;
|
||||
}
|
||||
"
|
||||
@delete="deleteCustomerById(props.row.id)"
|
||||
@delete="
|
||||
deleteCustomerById(
|
||||
props.row.id,
|
||||
async () =>
|
||||
await fetchListCustomer(true, $q.screen.xs),
|
||||
)
|
||||
"
|
||||
@change-status="
|
||||
async () => {
|
||||
triggerChangeStatus(
|
||||
|
|
@ -1588,7 +1463,23 @@ const emptyCreateDialog = ref(false);
|
|||
"
|
||||
@delete="
|
||||
(item: any) => {
|
||||
deleteEmployeeById({ id: item.id });
|
||||
deleteEmployeeById({
|
||||
id: item.id,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
@toggle-status="
|
||||
|
|
@ -1763,7 +1654,16 @@ const emptyCreateDialog = ref(false);
|
|||
customerFormState.branchIndex = 0;
|
||||
}
|
||||
"
|
||||
@delete="deleteCustomerById(props.row.id)"
|
||||
@delete="
|
||||
deleteCustomerById(
|
||||
props.row.id,
|
||||
async () =>
|
||||
await fetchListCustomer(
|
||||
true,
|
||||
$q.screen.xs,
|
||||
),
|
||||
)
|
||||
"
|
||||
@change-status="
|
||||
triggerChangeStatus(
|
||||
props.row.id,
|
||||
|
|
@ -1898,7 +1798,23 @@ const emptyCreateDialog = ref(false);
|
|||
@edit="(item: any) => editEmployeeFormPersonal(item.id)"
|
||||
@delete="
|
||||
(item: any) => {
|
||||
deleteEmployeeById({ id: item.id });
|
||||
deleteEmployeeById({
|
||||
id: item.id,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
@toggle-status="
|
||||
|
|
@ -2036,7 +1952,7 @@ const emptyCreateDialog = ref(false);
|
|||
async (currentBranch) => {
|
||||
createEmployeeForm();
|
||||
await nextTick();
|
||||
employeeFormState.formDataEmployeeOwner = { ...currentBranch };
|
||||
employeeFormState.currentBranchId = currentBranch.id;
|
||||
}
|
||||
"
|
||||
v-model:branch="branch"
|
||||
|
|
@ -2150,7 +2066,16 @@ const emptyCreateDialog = ref(false);
|
|||
"
|
||||
:title="
|
||||
customerFormData.customerType === 'PERS'
|
||||
? `${customerFormData.customerBranch[0]?.firstName} ${customerFormData.customerBranch[0]?.lastName}`
|
||||
? setPrefixName(
|
||||
{
|
||||
namePrefix: customerFormData.customerBranch[0]?.namePrefix,
|
||||
firstName: customerFormData.customerBranch[0]?.firstName,
|
||||
lastName: customerFormData.customerBranch[0]?.lastName,
|
||||
firstNameEN: customerFormData.customerBranch[0]?.firstNameEN,
|
||||
lastNameEN: customerFormData.customerBranch[0]?.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
: customerFormData.customerBranch[0]?.registerName
|
||||
"
|
||||
:caption="
|
||||
|
|
@ -2259,13 +2184,16 @@ const emptyCreateDialog = ref(false);
|
|||
id="form-basic-info-customer"
|
||||
:onCreate="customerFormState.dialogType === 'create'"
|
||||
@edit="
|
||||
(customerFormState.dialogType = 'edit'),
|
||||
(customerFormState.readonly = false)
|
||||
((customerFormState.dialogType = 'edit'),
|
||||
(customerFormState.readonly = false))
|
||||
"
|
||||
@cancel="() => customerFormUndo(false)"
|
||||
@delete="
|
||||
customerFormState.editCustomerId &&
|
||||
deleteCustomerById(customerFormState.editCustomerId)
|
||||
deleteCustomerById(
|
||||
customerFormState.editCustomerId,
|
||||
async () => await fetchListCustomer(true, $q.screen.xs),
|
||||
)
|
||||
"
|
||||
:customer-type="customerFormData.customerType"
|
||||
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
||||
|
|
@ -2435,7 +2363,11 @@ const emptyCreateDialog = ref(false);
|
|||
if (!customerFormState.editCustomerId) return;
|
||||
|
||||
if (idx === 0) {
|
||||
deleteCustomerById(customerFormState.editCustomerId);
|
||||
deleteCustomerById(
|
||||
customerFormState.editCustomerId,
|
||||
async () =>
|
||||
await fetchListCustomer(true, $q.screen.xs),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!!customerFormData.customerBranch?.[idx].id) {
|
||||
|
|
@ -2591,6 +2523,20 @@ const emptyCreateDialog = ref(false);
|
|||
"
|
||||
:toggleTitle="$t('status.title')"
|
||||
hideFade
|
||||
:title="
|
||||
currentFromDataEmployee
|
||||
? setPrefixName(
|
||||
{
|
||||
namePrefix: currentFromDataEmployee.namePrefix,
|
||||
firstName: currentFromDataEmployee.firstName,
|
||||
lastName: currentFromDataEmployee.lastName,
|
||||
firstNameEN: currentFromDataEmployee.firstNameEN,
|
||||
lastNameEN: currentFromDataEmployee.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
: '-'
|
||||
"
|
||||
@view="
|
||||
() => {
|
||||
employeeFormState.imageDialog = true;
|
||||
|
|
@ -2870,12 +2816,30 @@ const emptyCreateDialog = ref(false);
|
|||
id="btn-info-basic-delete"
|
||||
icon-only
|
||||
@click="
|
||||
() => deleteEmployeeById({ id: currentFromDataEmployee.id })
|
||||
() =>
|
||||
deleteEmployeeById({
|
||||
id: currentFromDataEmployee.id,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
})
|
||||
"
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BasicInformation
|
||||
no-action
|
||||
id="form-information"
|
||||
|
|
@ -2888,11 +2852,13 @@ const emptyCreateDialog = ref(false);
|
|||
title="form.field.basicInformation"
|
||||
:readonly="!employeeFormState.isEmployeeEdit"
|
||||
:employee-owner-option="employeeStore.ownerOption || []"
|
||||
v-model:customer-branch="employeeFormState.formDataEmployeeOwner"
|
||||
v-model:customer-branch-id="employeeFormState.currentBranchId"
|
||||
v-model:current-customer-branch="
|
||||
employeeFormState.currentCustomerBranch
|
||||
"
|
||||
v-model:employee-id="employeeFormState.currentEmployeeCode"
|
||||
v-model:nrc-no="currentFromDataEmployee.nrcNo"
|
||||
v-model:code="currentFromDataEmployee.code"
|
||||
@filter-owner-branch="employeeFormStore.employeeFilterOwnerBranch"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<FormPerson
|
||||
|
|
@ -2918,6 +2884,7 @@ const emptyCreateDialog = ref(false);
|
|||
class="q-mb-xl"
|
||||
/>
|
||||
<AddressForm
|
||||
disabledRule
|
||||
id="form-personal-address"
|
||||
prefix-id="form-employee"
|
||||
:readonly="!employeeFormState.isEmployeeEdit"
|
||||
|
|
@ -2940,7 +2907,7 @@ const emptyCreateDialog = ref(false);
|
|||
class="q-mb-xl"
|
||||
/>
|
||||
|
||||
<div class="row q-mb-md" id="drawer-info-file-upload">
|
||||
<div class="row q-mb-md" id="form-info-file-upload">
|
||||
<div class="col-12 q-pb-sm text-weight-bold text-body1">
|
||||
<q-icon
|
||||
flat
|
||||
|
|
@ -3448,7 +3415,24 @@ const emptyCreateDialog = ref(false);
|
|||
@click.stop="
|
||||
() => {
|
||||
employeeFormState.currentIndexPassport = index;
|
||||
deleteEmployeeById({ type: 'passport', index });
|
||||
deleteEmployeeById({
|
||||
type: 'passport',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
type="button"
|
||||
|
|
@ -3599,7 +3583,24 @@ const emptyCreateDialog = ref(false);
|
|||
@click.stop="
|
||||
() => {
|
||||
employeeFormState.currentIndexVisa = index;
|
||||
deleteEmployeeById({ type: 'visa', index });
|
||||
deleteEmployeeById({
|
||||
type: 'visa',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
type="button"
|
||||
|
|
@ -3671,7 +3672,23 @@ const emptyCreateDialog = ref(false);
|
|||
@delete="
|
||||
(index) => {
|
||||
employeeFormState.currentIndexCheckup = index;
|
||||
deleteEmployeeById({ type: 'healthCheck', index });
|
||||
deleteEmployeeById({
|
||||
type: 'healthCheck',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId: customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
@save="
|
||||
|
|
@ -3757,7 +3774,23 @@ const emptyCreateDialog = ref(false);
|
|||
@delete="
|
||||
(index) => {
|
||||
employeeFormState.currentIndexWorkHistory = index;
|
||||
deleteEmployeeById({ type: 'work', index });
|
||||
deleteEmployeeById({
|
||||
type: 'work',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId: customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
@save="
|
||||
|
|
@ -4104,7 +4137,17 @@ const emptyCreateDialog = ref(false);
|
|||
"
|
||||
:title="
|
||||
customerFormData.customerType === 'PERS'
|
||||
? `${customerFormData.customerBranch[0]?.firstName} ${customerFormData.customerBranch[0]?.lastName}`
|
||||
? setPrefixName(
|
||||
{
|
||||
namePrefix: customerFormData.customerBranch[0]?.namePrefix,
|
||||
firstName: customerFormData.customerBranch[0]?.firstName,
|
||||
lastName: customerFormData.customerBranch[0]?.lastName,
|
||||
firstNameEN:
|
||||
customerFormData.customerBranch[0]?.firstNameEN,
|
||||
lastNameEN: customerFormData.customerBranch[0]?.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
: customerFormData.customerBranch[0]?.registerName
|
||||
"
|
||||
:caption="
|
||||
|
|
@ -4218,13 +4261,16 @@ const emptyCreateDialog = ref(false);
|
|||
id="form-basic-info-customer"
|
||||
:onCreate="customerFormState.dialogType === 'create'"
|
||||
@edit="
|
||||
(customerFormState.dialogType = 'edit'),
|
||||
(customerFormState.readonly = false)
|
||||
((customerFormState.dialogType = 'edit'),
|
||||
(customerFormState.readonly = false))
|
||||
"
|
||||
@cancel="() => customerFormUndo(false)"
|
||||
@delete="
|
||||
customerFormState.editCustomerId &&
|
||||
deleteCustomerById(customerFormState.editCustomerId)
|
||||
deleteCustomerById(
|
||||
customerFormState.editCustomerId,
|
||||
async () => await fetchListCustomer(true, $q.screen.xs),
|
||||
)
|
||||
"
|
||||
:customer-type="customerFormData.customerType"
|
||||
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
||||
|
|
@ -4524,9 +4570,16 @@ const emptyCreateDialog = ref(false);
|
|||
fallback-cover="/images/employee-banner.png"
|
||||
:title="
|
||||
employeeFormState.currentEmployee
|
||||
? $i18n.locale === 'eng'
|
||||
? `${employeeFormState.currentEmployee.firstNameEN} ${employeeFormState.currentEmployee.lastNameEN}`
|
||||
: `${employeeFormState.currentEmployee.firstName} ${employeeFormState.currentEmployee.lastName}`
|
||||
? setPrefixName(
|
||||
{
|
||||
namePrefix: employeeFormState.currentEmployee.namePrefix,
|
||||
firstName: employeeFormState.currentEmployee.firstName,
|
||||
lastName: employeeFormState.currentEmployee.lastName,
|
||||
firstNameEN: employeeFormState.currentEmployee.firstNameEN,
|
||||
lastNameEN: employeeFormState.currentEmployee.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
: '-'
|
||||
"
|
||||
:caption="currentFromDataEmployee.code"
|
||||
|
|
@ -4821,7 +4874,23 @@ const emptyCreateDialog = ref(false);
|
|||
icon-only
|
||||
@click="
|
||||
() =>
|
||||
deleteEmployeeById({ id: currentFromDataEmployee.id })
|
||||
deleteEmployeeById({
|
||||
id: currentFromDataEmployee.id,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
})
|
||||
"
|
||||
type="button"
|
||||
/>
|
||||
|
|
@ -4835,16 +4904,13 @@ const emptyCreateDialog = ref(false);
|
|||
outlined
|
||||
title="form.field.basicInformation"
|
||||
:readonly="!employeeFormState.isEmployeeEdit"
|
||||
:employee-owner-option="employeeStore.ownerOption"
|
||||
v-model:customer-branch="
|
||||
employeeFormState.formDataEmployeeOwner
|
||||
v-model:customer-branch-id="employeeFormState.currentBranchId"
|
||||
v-model:current-customer-branch="
|
||||
employeeFormState.currentCustomerBranch
|
||||
"
|
||||
v-model:employee-id="employeeFormState.currentEmployeeCode"
|
||||
v-model:nrc-no="currentFromDataEmployee.nrcNo"
|
||||
v-model:code="currentFromDataEmployee.code"
|
||||
@filter-owner-branch="
|
||||
employeeFormStore.employeeFilterOwnerBranch
|
||||
"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<FormPerson
|
||||
|
|
@ -4871,6 +4937,7 @@ const emptyCreateDialog = ref(false);
|
|||
<AddressForm
|
||||
id="drawer-form-personal-address"
|
||||
employee
|
||||
disabledRule
|
||||
v-model:address="currentFromDataEmployee.address"
|
||||
v-model:address-en="currentFromDataEmployee.addressEN"
|
||||
v-model:moo="currentFromDataEmployee.moo"
|
||||
|
|
@ -5450,6 +5517,20 @@ const emptyCreateDialog = ref(false);
|
|||
deleteEmployeeById({
|
||||
type: 'passport',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
|
|
@ -5621,7 +5702,24 @@ const emptyCreateDialog = ref(false);
|
|||
icon-only
|
||||
@click.stop="
|
||||
() => {
|
||||
deleteEmployeeById({ type: 'visa', index });
|
||||
deleteEmployeeById({
|
||||
type: 'visa',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
type="button"
|
||||
|
|
@ -5742,7 +5840,24 @@ const emptyCreateDialog = ref(false);
|
|||
@delete="
|
||||
(index) => {
|
||||
employeeFormState.currentIndexCheckup = index;
|
||||
deleteEmployeeById({ type: 'healthCheck', index });
|
||||
deleteEmployeeById({
|
||||
type: 'healthCheck',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
|
@ -5812,7 +5927,24 @@ const emptyCreateDialog = ref(false);
|
|||
@delete="
|
||||
(index) => {
|
||||
employeeFormState.currentIndexWorkHistory = index;
|
||||
deleteEmployeeById({ type: 'work', index });
|
||||
deleteEmployeeById({
|
||||
type: 'work',
|
||||
index,
|
||||
fetch: async () =>
|
||||
await fetchListEmployee(
|
||||
currentTab === 'employer'
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
customerId:
|
||||
customerFormState.currentCustomerId,
|
||||
}
|
||||
: {
|
||||
fetchStats: true,
|
||||
mobileFetch: $q.screen.xs,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
"
|
||||
@save="
|
||||
|
|
|
|||
|
|
@ -1,22 +1,50 @@
|
|||
import { ref, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {
|
||||
CustomerBranch,
|
||||
CustomerBranchCreate,
|
||||
CustomerCreate,
|
||||
CustomerType,
|
||||
} from 'stores/customer/types';
|
||||
import { Employee, EmployeeCreate } from 'stores/employee/types';
|
||||
|
||||
import { dialog } from 'stores/utils';
|
||||
import useMyBranch from 'stores/my-branch';
|
||||
import useCustomerStore from 'stores/customer';
|
||||
import useEmployeeStore from 'stores/employee';
|
||||
import useFlowStore from 'stores/flow';
|
||||
import useMyBranchStore from 'stores/my-branch';
|
||||
|
||||
import { baseUrl } from 'src/stores/utils';
|
||||
import { getRole, getUserId } from 'src/services/keycloak';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export const useCustomerForm = defineStore('form-customer', () => {
|
||||
const customerStore = useCustomerStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const flowStore = useFlowStore();
|
||||
|
||||
const userBranchStore = useMyBranchStore();
|
||||
|
||||
const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
|
||||
|
||||
const tabFieldRequired = ref<{
|
||||
[key: string]: (keyof CustomerBranchCreate)[];
|
||||
}>({
|
||||
main: [],
|
||||
business: ['businessType', 'jobPosition'],
|
||||
address: [
|
||||
'address',
|
||||
'addressEN',
|
||||
'provinceId',
|
||||
'districtId',
|
||||
'subDistrictId',
|
||||
],
|
||||
contact: [],
|
||||
});
|
||||
|
||||
const defaultFormData: CustomerCreate = {
|
||||
// code: '',
|
||||
// namePrefix: '',
|
||||
|
|
@ -360,7 +388,118 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchListOfOptionBranch() {
|
||||
if (registerAbleBranchOption.value) return;
|
||||
|
||||
const uid = getUserId();
|
||||
const role = getRole();
|
||||
|
||||
if (!uid) return; // should not possible as the system require login to be able to access resource.
|
||||
|
||||
if (role?.includes('system')) {
|
||||
const result = await userBranchStore.fetchListOptionBranch();
|
||||
if (result && result.total > 0)
|
||||
registerAbleBranchOption.value = result.result;
|
||||
} else {
|
||||
const result = await userBranchStore.fetchListMyBranch(uid);
|
||||
if (result && result.total > 0)
|
||||
registerAbleBranchOption.value = result.result;
|
||||
}
|
||||
|
||||
// TODO: Assign (first) branch of the user as register branch of the data
|
||||
}
|
||||
|
||||
function customerFormUndo(close = true) {
|
||||
if (isFormDataDifferent()) {
|
||||
return customerConfirmUnsave(close);
|
||||
}
|
||||
resetForm();
|
||||
state.value.readonly = true;
|
||||
}
|
||||
|
||||
function customerConfirmUnsave(close = true) {
|
||||
dialog({
|
||||
color: 'warning',
|
||||
icon: 'mdi-alert',
|
||||
title: t('form.warning.title'),
|
||||
actionText: t('general.ok'),
|
||||
persistent: true,
|
||||
message: t('form.warning.unsave'),
|
||||
|
||||
action: () => {
|
||||
resetForm();
|
||||
state.value.readonly = true;
|
||||
|
||||
if (!state.value.drawerModal) {
|
||||
state.value.dialogModal = !close;
|
||||
} else {
|
||||
state.value.drawerModal = !close;
|
||||
}
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCustomerById(
|
||||
id: string,
|
||||
fetch?: (...args: unknown[]) => unknown,
|
||||
) {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
await customerStore.deleteById(id);
|
||||
await fetch();
|
||||
state.value.dialogModal = false;
|
||||
flowStore.rotate();
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
function validateTabField<T = CustomerBranchCreate>(
|
||||
value: T,
|
||||
fieldRequired: { [key: string]: (keyof T)[] },
|
||||
) {
|
||||
const list: string[] = [];
|
||||
|
||||
for (const tab in fieldRequired) {
|
||||
for (const field of fieldRequired[tab]) {
|
||||
if (!value[field] && !list.includes(tab)) list.push(tab);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
async function deleteCustomerBranchById(id: string) {
|
||||
return await new Promise((resolve) => {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
await customerStore.deleteBranchById(id);
|
||||
flowStore.rotate();
|
||||
resolve(true);
|
||||
},
|
||||
cancel: () => {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tabFieldRequired,
|
||||
registerAbleBranchOption,
|
||||
state,
|
||||
resetFormData,
|
||||
currentFormData,
|
||||
|
|
@ -370,13 +509,18 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
submitFormCustomer,
|
||||
addCurrentCustomerBranch,
|
||||
deleteAttachment,
|
||||
fetchListOfOptionBranch,
|
||||
customerFormUndo,
|
||||
customerConfirmUnsave,
|
||||
deleteCustomerById,
|
||||
validateTabField,
|
||||
deleteCustomerBranchById,
|
||||
};
|
||||
});
|
||||
|
||||
export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
||||
const customerStore = useCustomerStore();
|
||||
const customerFormStore = useCustomerForm();
|
||||
|
||||
const defaultFormData: CustomerBranchCreate & {
|
||||
id?: string;
|
||||
codeCustomer?: string;
|
||||
|
|
@ -581,11 +725,23 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
});
|
||||
|
||||
export const useEmployeeForm = defineStore('form-employee', () => {
|
||||
const { t } = useI18n();
|
||||
const customerStore = useCustomerStore();
|
||||
const employeeStore = useEmployeeStore();
|
||||
const flowStore = useFlowStore();
|
||||
const branchStore = useMyBranch();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const refreshImageState = ref(false);
|
||||
|
||||
const onCreateImageList = ref<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>({ selectedImage: '', list: [] });
|
||||
|
||||
const statusEmployeeCreate = ref<boolean>(false);
|
||||
|
||||
const state = ref<{
|
||||
dialogType: 'info' | 'create' | 'edit';
|
||||
imageDialog: boolean;
|
||||
|
|
@ -594,6 +750,8 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
drawerModal: boolean;
|
||||
isImageEdit: boolean;
|
||||
|
||||
currentBranchId: string;
|
||||
currentCustomerBranch?: CustomerBranch;
|
||||
currentEmployeeCode: string;
|
||||
currentEmployee: Employee | null;
|
||||
currentIndexPassport: number;
|
||||
|
|
@ -627,6 +785,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
| undefined;
|
||||
ocr: boolean;
|
||||
}>({
|
||||
currentBranchId: '',
|
||||
isImageEdit: false,
|
||||
currentIndexPassport: -1,
|
||||
currentIndexVisa: -1,
|
||||
|
|
@ -1089,6 +1248,8 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}) {
|
||||
let employeeId: string | undefined = undefined;
|
||||
|
||||
currentFromDataEmployee.value.firstName =
|
||||
currentFromDataEmployee.value.firstName.trim();
|
||||
currentFromDataEmployee.value.middleName =
|
||||
|
|
@ -1107,7 +1268,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
const res = await employeeStore.create(
|
||||
{
|
||||
...currentFromDataEmployee.value,
|
||||
customerBranchId: state.value.formDataEmployeeOwner?.id || '',
|
||||
customerBranchId: state.value.currentBranchId || '',
|
||||
|
||||
employeeWork: [],
|
||||
employeeCheckup: [],
|
||||
|
|
@ -1117,6 +1278,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
);
|
||||
|
||||
if (res) {
|
||||
employeeId = res.id;
|
||||
await assignFormDataEmployee(res.id);
|
||||
currentFromDataEmployee.value.id = res.id;
|
||||
state.value.statusSavePersonal = true;
|
||||
|
|
@ -1138,10 +1300,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
},
|
||||
);
|
||||
if (res) {
|
||||
employeeId = res.id;
|
||||
await assignFormDataEmployee(res.id);
|
||||
state.value.statusSavePersonal = true;
|
||||
}
|
||||
}
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
async function assignFormDataEmployee(id?: string) {
|
||||
|
|
@ -1183,7 +1347,19 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
employeePassport: structuredClone(
|
||||
payload.employeePassport?.length === 0
|
||||
? state.value.dialogModal
|
||||
? defaultFormData.employeePassport
|
||||
? defaultFormData.employeePassport.map((v) => ({
|
||||
...v,
|
||||
namePrefix: payload.namePrefix,
|
||||
firstName: payload.firstName,
|
||||
firstNameEN: payload.firstNameEN,
|
||||
middleName: payload.middleName,
|
||||
middleNameEN: payload.middleNameEN,
|
||||
lastName: payload.lastName,
|
||||
lastNameEN: payload.lastNameEN,
|
||||
gender: payload.gender,
|
||||
nationality: payload.nationality,
|
||||
birthDate: payload.dateOfBirth,
|
||||
}))
|
||||
: []
|
||||
: payload.employeePassport,
|
||||
),
|
||||
|
|
@ -1270,6 +1446,8 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
state.value.currentIndexVisa = -1;
|
||||
}
|
||||
|
||||
state.value.currentBranchId = payload.customerBranchId;
|
||||
|
||||
const foundBranch = await customerStore.fetchListCustomerBranchById(
|
||||
payload.customerBranchId,
|
||||
);
|
||||
|
|
@ -1325,17 +1503,17 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
issueDate: new Date(),
|
||||
type: '',
|
||||
expireDate: new Date(),
|
||||
birthDate: new Date(),
|
||||
birthDate: currentFromDataEmployee.value.dateOfBirth,
|
||||
workerStatus: '',
|
||||
nationality: '',
|
||||
gender: '',
|
||||
lastNameEN: '',
|
||||
lastName: '',
|
||||
middleNameEN: '',
|
||||
middleName: '',
|
||||
firstNameEN: '',
|
||||
firstName: '',
|
||||
namePrefix: '',
|
||||
nationality: currentFromDataEmployee.value.nationality,
|
||||
gender: currentFromDataEmployee.value.gender,
|
||||
lastNameEN: currentFromDataEmployee.value.lastNameEN,
|
||||
lastName: currentFromDataEmployee.value.lastName,
|
||||
middleNameEN: currentFromDataEmployee.value.middleNameEN,
|
||||
middleName: currentFromDataEmployee.value.middleName,
|
||||
firstNameEN: currentFromDataEmployee.value.firstNameEN,
|
||||
firstName: currentFromDataEmployee.value.firstName,
|
||||
namePrefix: currentFromDataEmployee.value.namePrefix,
|
||||
number: '',
|
||||
});
|
||||
|
||||
|
|
@ -1396,7 +1574,88 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
(currentFromDataEmployee.value.employeeWork?.length || 0) - 1;
|
||||
}
|
||||
|
||||
function employeeFormUndo(close = true) {
|
||||
if (isFormDataDifferent()) {
|
||||
return employeeConfirmUnsave(close);
|
||||
}
|
||||
resetFormDataEmployee();
|
||||
state.value.editReadonly = true;
|
||||
}
|
||||
|
||||
function employeeConfirmUnsave(close = true) {
|
||||
dialog({
|
||||
color: 'warning',
|
||||
icon: 'mdi-alert',
|
||||
title: t('form.warning.title'),
|
||||
actionText: t('general.ok'),
|
||||
persistent: true,
|
||||
message: t('form.warning.unsave'),
|
||||
action: () => {
|
||||
resetFormDataEmployee();
|
||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
||||
state.value.editReadonly = true;
|
||||
state.value.dialogModal = !close;
|
||||
state.value.drawerModal = !close;
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteEmployeeById(opts: {
|
||||
id?: string;
|
||||
type?: 'passport' | 'visa' | 'healthCheck' | 'work';
|
||||
index?: number;
|
||||
fetch?: (...args: unknown[]) => unknown;
|
||||
removeArray?: (...args: unknown[]) => unknown;
|
||||
}) {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
if (opts.type === 'passport' && opts.index !== undefined) {
|
||||
await deletePassport(opts.index);
|
||||
}
|
||||
|
||||
if (opts.type === 'visa' && opts.index !== undefined) {
|
||||
await deleteVisa(opts.index);
|
||||
}
|
||||
|
||||
if (opts.type === 'healthCheck' && opts.index !== undefined) {
|
||||
await deleteHealthCheck(opts.index);
|
||||
}
|
||||
|
||||
if (opts.type === 'work' && opts.index !== undefined) {
|
||||
await deleteWorkHistory(opts.index);
|
||||
} else {
|
||||
if (!!opts.id) {
|
||||
const result = await employeeStore.deleteById(opts.id);
|
||||
|
||||
if (result) {
|
||||
state.value.drawerModal = false;
|
||||
state.value.dialogModal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (route.name !== 'CustomerBranchManagement') {
|
||||
await opts.fetch?.();
|
||||
flowStore.rotate();
|
||||
}
|
||||
opts.removeArray?.();
|
||||
},
|
||||
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
refreshImageState,
|
||||
statusEmployeeCreate,
|
||||
onCreateImageList,
|
||||
state,
|
||||
currentFromDataEmployee,
|
||||
resetEmployeeData,
|
||||
|
|
@ -1423,5 +1682,9 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
employeeFilterOwnerBranch,
|
||||
|
||||
isFormDataDifferent,
|
||||
|
||||
employeeFormUndo,
|
||||
employeeConfirmUnsave,
|
||||
deleteEmployeeById,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ async function addStep() {
|
|||
flowData.value.step.push({
|
||||
responsibleInstitution: [],
|
||||
responsiblePersonId: [],
|
||||
responsibleGroup: [],
|
||||
value: [],
|
||||
detail: '',
|
||||
name: '',
|
||||
|
|
@ -166,6 +167,7 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
|
|||
id="flow-form-dialog"
|
||||
>
|
||||
<FormFlow
|
||||
v-model:user-in-table="userInTable"
|
||||
v-model:flow-data="flowData"
|
||||
v-model:register-branch-id="registerBranchId"
|
||||
@trigger-properties="triggerPropertiesDialog"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, watch } from 'vue';
|
||||
import { QSelect, QTableProps } from 'quasar';
|
||||
import { QTableProps } from 'quasar';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ import NoData from 'src/components/NoData.vue';
|
|||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const workflowStore = useWorkflowTemplate();
|
||||
|
|
@ -45,6 +46,7 @@ const pageState = reactive({
|
|||
addModal: false,
|
||||
viewDrawer: false,
|
||||
isDrawerEdit: true,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const fieldSelected = ref<('order' | 'name' | 'step')[]>([
|
||||
|
|
@ -68,7 +70,6 @@ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
|||
},
|
||||
]);
|
||||
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
const currWorkflowData = ref<WorkflowTemplate>();
|
||||
const formDataWorkflow = ref<WorkflowTemplatePayload>({
|
||||
status: 'CREATED',
|
||||
|
|
@ -102,6 +103,7 @@ const columns = [
|
|||
function triggerDialog(type: 'add' | 'edit' | 'view') {
|
||||
if (type === 'add') {
|
||||
registeredBranchId.value = '';
|
||||
userInTable.value = [];
|
||||
formDataWorkflow.value = {
|
||||
status: 'CREATED',
|
||||
name: '',
|
||||
|
|
@ -206,7 +208,7 @@ async function submit() {
|
|||
...formDataWorkflow.value,
|
||||
});
|
||||
} else {
|
||||
await workflowStore.creatWorkflowTemplate({
|
||||
await workflowStore.createWorkflowTemplate({
|
||||
registeredBranchId: registeredBranchId.value,
|
||||
...formDataWorkflow.value,
|
||||
});
|
||||
|
|
@ -222,7 +224,11 @@ function assignFormData(workflowData: WorkflowTemplate) {
|
|||
status: workflowData.status,
|
||||
name: workflowData.name,
|
||||
step: workflowData.step.map((s, i) => {
|
||||
userInTable.value[i] = { name: s.name, responsiblePerson: [] };
|
||||
userInTable.value[i] = {
|
||||
name: s.name,
|
||||
responsiblePerson: [],
|
||||
responsibleGroup: [],
|
||||
};
|
||||
s.responsiblePerson.forEach((p) => {
|
||||
userInTable.value[i].responsiblePerson.push({
|
||||
id: p.user.id,
|
||||
|
|
@ -236,12 +242,16 @@ function assignFormData(workflowData: WorkflowTemplate) {
|
|||
code: p.user.code,
|
||||
});
|
||||
});
|
||||
s.responsibleGroup.forEach((g) => {
|
||||
userInTable.value[i].responsibleGroup.push(g);
|
||||
});
|
||||
return {
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
detail: s.detail,
|
||||
messengerByArea: s.messengerByArea || false,
|
||||
value: s.value.length > 0 ? JSON.parse(JSON.stringify(s.value)) : [],
|
||||
responsibleGroup: s.responsibleGroup.map((g) => g),
|
||||
responsiblePersonId: s.responsiblePerson.map((p) => p.userId),
|
||||
responsibleInstitution: JSON.parse(
|
||||
JSON.stringify(s.responsibleInstitution),
|
||||
|
|
@ -282,6 +292,8 @@ async function fetchWorkflowList(mobileFetch?: boolean) {
|
|||
: statusFilter.value === 'statusACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
if (res) {
|
||||
workflowData.value =
|
||||
|
|
@ -311,11 +323,14 @@ watch(
|
|||
fetchWorkflowList();
|
||||
},
|
||||
);
|
||||
watch([() => pageState.inputSearch, workflowPageSize], () => {
|
||||
workflowData.value = [];
|
||||
workflowPage.value = 1;
|
||||
fetchWorkflowList();
|
||||
});
|
||||
watch(
|
||||
[() => pageState.inputSearch, workflowPageSize, () => pageState.searchDate],
|
||||
() => {
|
||||
workflowData.value = [];
|
||||
workflowPage.value = 1;
|
||||
fetchWorkflowList();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
|
|
@ -392,26 +407,44 @@ watch([() => pageState.inputSearch, workflowPageSize], () => {
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="pageState.searchDate"
|
||||
:active="$q.screen.lt.md && statusFilter !== 'all'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'all' },
|
||||
{ label: $t('general.active'), value: 'statusACTIVE' },
|
||||
{
|
||||
label: $t('general.inactive'),
|
||||
value: 'statusINACTIVE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
|
|
@ -509,12 +542,12 @@ watch([() => pageState.inputSearch, workflowPageSize], () => {
|
|||
class="col surface-2 flex items-center justify-center"
|
||||
>
|
||||
<NoData
|
||||
v-if="pageState.total !== 0"
|
||||
v-if="pageState.total !== 0 || pageState.searchDate.length > 0"
|
||||
:not-found="!!pageState.inputSearch"
|
||||
/>
|
||||
|
||||
<CreateButton
|
||||
v-if="pageState.total === 0"
|
||||
v-if="pageState.total === 0 && pageState.searchDate.length === 0"
|
||||
@click="triggerDialog('add')"
|
||||
label="general.add"
|
||||
:i18n-args="{ text: $t('flow.title') }"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { nextTick, ref, watch, reactive } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { onMounted } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { QSelect, useQuasar, type QTableProps } from 'quasar';
|
||||
import { useQuasar, type QTableProps } from 'quasar';
|
||||
|
||||
import DialogProperties from 'src/components/dialog/DialogProperties.vue';
|
||||
import ProductCardComponent from 'components/04_product-service/ProductCardComponent.vue';
|
||||
|
|
@ -33,6 +33,7 @@ import {
|
|||
SaveButton,
|
||||
UndoButton,
|
||||
ToggleButton,
|
||||
ImportButton,
|
||||
} from 'components/button';
|
||||
import TableProduct from 'src/components/04_product-service/TableProduct.vue';
|
||||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||
|
|
@ -40,7 +41,7 @@ import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
|||
import useFlowStore from 'stores/flow';
|
||||
|
||||
import { dateFormat } from 'src/utils/datetime';
|
||||
import { formatNumberDecimal, isRoleInclude } from 'stores/utils';
|
||||
import { formatNumberDecimal, isRoleInclude, notify } from 'stores/utils';
|
||||
const { getWorkflowTemplate } = useWorkflowTemplate();
|
||||
|
||||
import { Status } from 'stores/types';
|
||||
|
|
@ -67,6 +68,8 @@ import {
|
|||
} from 'src/stores/workflow-template/types';
|
||||
import { useWorkflowTemplate } from 'src/stores/workflow-template';
|
||||
import { deepEquals } from 'src/utils/arr';
|
||||
import { toRaw } from 'vue';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const flowStore = useFlowStore();
|
||||
const navigatorStore = useNavigator();
|
||||
|
|
@ -96,10 +99,13 @@ const {
|
|||
createWork,
|
||||
editWork,
|
||||
deleteWork,
|
||||
|
||||
importProduct,
|
||||
} = productServiceStore;
|
||||
|
||||
const { workNameItems } = storeToRefs(productServiceStore);
|
||||
const allStat = ref<{ mode: string; count: number }[]>([]);
|
||||
|
||||
const stat = ref<
|
||||
{
|
||||
icon: string;
|
||||
|
|
@ -161,8 +167,6 @@ const splitterModel = computed(() =>
|
|||
$q.screen.lt.md ? (productMode.value !== 'group' ? 0 : 100) : 25,
|
||||
);
|
||||
|
||||
const refFilterGroup = ref<InstanceType<typeof QSelect>>();
|
||||
const refFilterProductService = ref<InstanceType<typeof QSelect>>();
|
||||
const holdDialog = ref(false);
|
||||
const imageDialog = ref(false);
|
||||
const currentNode = ref<ProductGroup & { type: string }>();
|
||||
|
|
@ -520,6 +524,7 @@ const currentStatusGroupType = ref<Status>('CREATED');
|
|||
const currentIdGroupType = ref('');
|
||||
|
||||
const currentStatus = ref<Status | 'All'>('All');
|
||||
const searchDate = ref<string[]>([]);
|
||||
|
||||
// img
|
||||
const isImageEdit = ref<boolean>(false);
|
||||
|
|
@ -615,6 +620,8 @@ async function fetchListGroups(mobileFetch?: boolean) {
|
|||
: currentStatus.value === 'ACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
|
@ -675,6 +682,8 @@ async function fetchListOfProduct(mobileFetch?: boolean) {
|
|||
? 'ACTIVE'
|
||||
: undefined,
|
||||
productGroupId: currentIdGroup.value,
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
|
@ -720,6 +729,8 @@ async function fetchListOfService(mobileFetch?: boolean) {
|
|||
? 'ACTIVE'
|
||||
: undefined,
|
||||
productGroupId: currentIdGroup.value,
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
|
@ -1590,6 +1601,7 @@ async function enterNext(type: 'service' | 'product') {
|
|||
inputSearchProductAndService.value = '';
|
||||
currentStatus.value = 'All';
|
||||
filterStat.value = [];
|
||||
searchDate.value = [];
|
||||
|
||||
if (
|
||||
expandedTree.value.length > 1 &&
|
||||
|
|
@ -1745,7 +1757,7 @@ watch(currentStatus, async () => {
|
|||
flowStore.rotate();
|
||||
});
|
||||
|
||||
watch(inputSearch, async () => {
|
||||
watch([inputSearch, () => searchDate.value], async () => {
|
||||
if (productMode.value === 'group') {
|
||||
productGroup.value = [];
|
||||
currentPageGroup.value = 1;
|
||||
|
|
@ -1754,7 +1766,7 @@ watch(inputSearch, async () => {
|
|||
}
|
||||
});
|
||||
|
||||
watch(inputSearchProductAndService, async () => {
|
||||
watch([inputSearchProductAndService, () => searchDate.value], async () => {
|
||||
product.value = [];
|
||||
service.value = [];
|
||||
currentPageServiceAndProduct.value = 1;
|
||||
|
|
@ -1831,6 +1843,51 @@ function handleSubmitSameWorkflow() {
|
|||
);
|
||||
}
|
||||
|
||||
async function copy(id: string) {
|
||||
{
|
||||
const res = await fetchListServiceById(id);
|
||||
if (res) {
|
||||
formService.value = {
|
||||
code: res.code.slice(0, -3),
|
||||
name: res.name,
|
||||
detail: res.detail,
|
||||
attributes: res.attributes,
|
||||
work: res.work.map((v) => ({
|
||||
id: v.id,
|
||||
name: v.name,
|
||||
attributes: v.attributes,
|
||||
product: v.productOnWork.map((productOnWorkItem) => ({
|
||||
id: productOnWorkItem.product.id,
|
||||
installmentNo: productOnWorkItem.installmentNo,
|
||||
stepCount: productOnWorkItem.stepCount,
|
||||
})),
|
||||
})),
|
||||
status: res.status,
|
||||
productGroupId: res.productGroupId,
|
||||
selectedImage: res.selectedImage,
|
||||
installments: res.installments,
|
||||
};
|
||||
|
||||
workItems.value = res.work.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
attributes: item.attributes,
|
||||
product: item.productOnWork.map((productOnWorkItem) => {
|
||||
return {
|
||||
...productOnWorkItem.product,
|
||||
nameEn: productOnWorkItem.product.name,
|
||||
installmentNo: productOnWorkItem.installmentNo,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
dialogService.value = true;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => formService.value.attributes.workflowId,
|
||||
async (a, b) => {
|
||||
|
|
@ -1948,19 +2005,34 @@ watch(
|
|||
<template v-slot:prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilterGroup?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="searchDate"
|
||||
:active="$q.screen.lt.md && currentStatus !== 'All'"
|
||||
>
|
||||
<div class="q-mt-sm text-weight-medium">
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-model="currentStatus"
|
||||
for="select-status"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'All' },
|
||||
{ label: $t('general.active'), value: 'ACTIVE' },
|
||||
{
|
||||
label: $t('general.inactive'),
|
||||
value: 'INACTIVE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
|
|
@ -2115,26 +2187,44 @@ watch(
|
|||
<template v-slot:prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilterGroup?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="searchDate"
|
||||
:active="$q.screen.lt.md && currentStatus !== 'All'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="currentStatus"
|
||||
for="select-status"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'All' },
|
||||
{ label: $t('general.active'), value: 'ACTIVE' },
|
||||
{
|
||||
label: $t('general.inactive'),
|
||||
value: 'INACTIVE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-6" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilterGroup"
|
||||
v-model="currentStatus"
|
||||
for="select-status"
|
||||
outlined
|
||||
|
|
@ -2155,7 +2245,6 @@ watch(
|
|||
},
|
||||
]"
|
||||
></q-select>
|
||||
|
||||
<q-select
|
||||
v-if="modeView === false"
|
||||
id="select-field"
|
||||
|
|
@ -2178,7 +2267,6 @@ watch(
|
|||
multiple
|
||||
dense
|
||||
/>
|
||||
|
||||
<q-btn-toggle
|
||||
v-model="modeView"
|
||||
id="btn-mode"
|
||||
|
|
@ -2618,26 +2706,65 @@ watch(
|
|||
<template v-slot:prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilterProductService?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="searchDate"
|
||||
:active="$q.screen.lt.md && currentStatus !== 'All'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
:for="'field-select-status'"
|
||||
v-model="currentStatus"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'All' },
|
||||
{ label: $t('general.active'), value: 'ACTIVE' },
|
||||
{
|
||||
label: $t('general.inactive'),
|
||||
value: 'INACTIVE',
|
||||
},
|
||||
]"
|
||||
@update:model-value="fetchStatus()"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
<div
|
||||
class="flex q-mr-auto q-pl-sm"
|
||||
v-if="productAndServiceTab === 'product'"
|
||||
>
|
||||
<input ref="fileImport" type="file" hidden />
|
||||
<ImportButton
|
||||
type="file"
|
||||
import-file
|
||||
icon-only
|
||||
@file-selected="
|
||||
(file) => {
|
||||
importProduct(
|
||||
currentIdGroup,
|
||||
file,
|
||||
async () => await fetchListOfProduct(),
|
||||
);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row col-md-6" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilterProductService"
|
||||
:for="'field-select-status'"
|
||||
v-model="currentStatus"
|
||||
outlined
|
||||
|
|
@ -2659,7 +2786,6 @@ watch(
|
|||
]"
|
||||
@update:model-value="fetchStatus()"
|
||||
></q-select>
|
||||
|
||||
<q-select
|
||||
v-if="modeView === false"
|
||||
:hide-dropdown-icon="$q.screen.lt.sm"
|
||||
|
|
@ -2694,7 +2820,6 @@ watch(
|
|||
multiple
|
||||
dense
|
||||
/>
|
||||
|
||||
<q-btn-toggle
|
||||
v-model="modeView"
|
||||
id="btn-mode"
|
||||
|
|
@ -3127,8 +3252,14 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
:use-copy="productAndServiceTab === 'service'"
|
||||
:status="props.row.status"
|
||||
:id-name="props.row.name"
|
||||
@copy="
|
||||
() => {
|
||||
copy(props.row.id);
|
||||
}
|
||||
"
|
||||
@view="
|
||||
async () => {
|
||||
if (props.row.type === 'product') {
|
||||
|
|
@ -4422,6 +4553,7 @@ watch(
|
|||
@click="serviceTreeView = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SaveButton id="btn-info-basic-save" icon-only type="submit" />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { QSelect, QTableProps } from 'quasar';
|
||||
import { QTableProps } from 'quasar';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { useProperty } from 'src/stores/property';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
|
@ -14,19 +14,23 @@ import { FloatingActionButton, PaginationComponent } from 'src/components';
|
|||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||
import PropertyDialog from './PropertyDialog.vue';
|
||||
import { Property } from 'src/stores/property/types';
|
||||
import { dialog } from 'src/stores/utils';
|
||||
import { dialog, toCamelCase } from 'src/stores/utils';
|
||||
import CreateButton from 'src/components/AddButton.vue';
|
||||
import useOptionStore from 'stores/options';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const $q = useQuasar();
|
||||
const navigatorStore = useNavigator();
|
||||
const propertyStore = useProperty();
|
||||
const optionStore = useOptionStore();
|
||||
const {
|
||||
data: propertyData,
|
||||
page: propertyPage,
|
||||
pageSize: propertyPageSize,
|
||||
pageMax: propertyPageMax,
|
||||
} = storeToRefs(propertyStore);
|
||||
const { globalOption } = storeToRefs(optionStore);
|
||||
|
||||
const currPropertyData = ref<Property>();
|
||||
const formProperty = ref<Property>({
|
||||
|
|
@ -35,7 +39,6 @@ const formProperty = ref<Property>({
|
|||
type: {},
|
||||
});
|
||||
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
const fieldSelected = ref<('order' | 'name' | 'type')[]>([
|
||||
'order',
|
||||
'name',
|
||||
|
|
@ -115,6 +118,7 @@ const pageState = reactive({
|
|||
addModal: false,
|
||||
viewDrawer: false,
|
||||
isDrawerEdit: true,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
async function fetchPropertyList(mobileFetch?: boolean) {
|
||||
|
|
@ -131,6 +135,8 @@ async function fetchPropertyList(mobileFetch?: boolean) {
|
|||
: statusFilter.value === 'statusACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
if (res) {
|
||||
propertyData.value =
|
||||
|
|
@ -146,6 +152,7 @@ async function fetchPropertyList(mobileFetch?: boolean) {
|
|||
|
||||
function triggerDialog(type: 'add' | 'edit' | 'view') {
|
||||
if (type === 'add') {
|
||||
resetForm();
|
||||
pageState.addModal = true;
|
||||
pageState.isDrawerEdit = true;
|
||||
}
|
||||
|
|
@ -271,6 +278,31 @@ async function submit() {
|
|||
});
|
||||
}
|
||||
await fetchPropertyList($q.screen.xs);
|
||||
|
||||
// assign new property to global option
|
||||
const propList = propertyData.value.map((v) => {
|
||||
return {
|
||||
label: locale.value === 'eng' ? v.nameEN : v.name,
|
||||
value: toCamelCase(v.nameEN),
|
||||
type: v.type.type,
|
||||
};
|
||||
});
|
||||
|
||||
const existingValues = new Set(
|
||||
globalOption.value.propertiesField.map(
|
||||
(item: { value: string }) => item.value,
|
||||
),
|
||||
);
|
||||
const newProps = propList.filter((prop) => !existingValues.has(prop.value));
|
||||
if (newProps) {
|
||||
globalOption.value.propertiesField.splice(
|
||||
globalOption.value.propertiesField.length - 4,
|
||||
0,
|
||||
...newProps,
|
||||
);
|
||||
}
|
||||
|
||||
currPropertyData.value;
|
||||
resetForm();
|
||||
}
|
||||
|
||||
|
|
@ -288,11 +320,14 @@ watch(
|
|||
fetchPropertyList();
|
||||
},
|
||||
);
|
||||
watch([() => pageState.inputSearch, propertyPageSize], () => {
|
||||
propertyData.value = [];
|
||||
propertyPage.value = 1;
|
||||
fetchPropertyList();
|
||||
});
|
||||
watch(
|
||||
[() => pageState.inputSearch, propertyPageSize, () => pageState.searchDate],
|
||||
() => {
|
||||
propertyData.value = [];
|
||||
propertyPage.value = 1;
|
||||
fetchPropertyList();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
|
|
@ -369,26 +404,44 @@ watch([() => pageState.inputSearch, propertyPageSize], () => {
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="pageState.searchDate"
|
||||
:active="$q.screen.lt.md && statusFilter !== 'all'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'all' },
|
||||
{ label: $t('general.active'), value: 'statusACTIVE' },
|
||||
{
|
||||
label: $t('general.inactive'),
|
||||
value: 'statusINACTIVE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
|
|
@ -483,11 +536,11 @@ watch([() => pageState.inputSearch, propertyPageSize], () => {
|
|||
class="col surface-2 flex items-center justify-center"
|
||||
>
|
||||
<NoData
|
||||
v-if="pageState.total !== 0"
|
||||
v-if="pageState.total !== 0 || pageState.searchDate.length > 0"
|
||||
:not-found="!!pageState.inputSearch"
|
||||
/>
|
||||
<CreateButton
|
||||
v-if="pageState.total === 0"
|
||||
v-if="pageState.total === 0 && pageState.searchDate.length === 0"
|
||||
@click="triggerDialog('add')"
|
||||
label="general.add"
|
||||
:i18n-args="{ text: $t('flow.title') }"
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
import { onMounted, reactive, ref, watch, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// NOTE: Import stores
|
||||
import useCustomerStore from 'stores/customer';
|
||||
import { useQuotationStore } from 'src/stores/quotations';
|
||||
import { isRoleInclude } from 'stores/utils';
|
||||
import { dialog, isRoleInclude, notify, setPrefixName } from 'stores/utils';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import useFlowStore from 'src/stores/flow';
|
||||
import useMyBranch from 'stores/my-branch';
|
||||
|
|
@ -38,26 +40,44 @@ import { AddressForm } from 'components/form';
|
|||
import {
|
||||
EmployerFormBusiness,
|
||||
EmployerFormAbout,
|
||||
EmployerFormBasicInfo,
|
||||
EmployerFormBranch,
|
||||
} from 'src/pages/03_customer-management/components';
|
||||
|
||||
import { useCustomerForm } from 'src/pages/03_customer-management/form';
|
||||
import { Quotation } from 'src/stores/quotations/types';
|
||||
import TableQuotation from 'src/components/05_quotation/TableQuotation.vue';
|
||||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||
import { DialogContainer, DialogHeader } from 'src/components/dialog';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const $q = useQuasar();
|
||||
const quotationFormStore = useQuotationForm();
|
||||
const customerFormStore = useCustomerForm();
|
||||
const flowStore = useFlowStore();
|
||||
const userBranch = useMyBranch();
|
||||
const navigatorStore = useNavigator();
|
||||
const customerStore = useCustomerStore();
|
||||
|
||||
const {
|
||||
fetchListOfOptionBranch,
|
||||
customerFormUndo,
|
||||
deleteCustomerById,
|
||||
validateTabField,
|
||||
deleteCustomerBranchById,
|
||||
} = customerFormStore;
|
||||
|
||||
const {
|
||||
currentFormData: quotationFormData,
|
||||
currentFormState: quotationFormState,
|
||||
} = storeToRefs(quotationFormStore);
|
||||
const { state: customerFormState, currentFormData: customerFormData } =
|
||||
storeToRefs(customerFormStore);
|
||||
const {
|
||||
state: customerFormState,
|
||||
currentFormData: customerFormData,
|
||||
registerAbleBranchOption,
|
||||
tabFieldRequired,
|
||||
} = storeToRefs(customerFormStore);
|
||||
const { currentMyBranch } = storeToRefs(userBranch);
|
||||
|
||||
const fieldSelectedOption = computed(() => {
|
||||
|
|
@ -90,6 +110,7 @@ const pageState = reactive({
|
|||
quotationModal: false,
|
||||
employeeModal: false,
|
||||
receiptModal: false,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
pageState.fieldSelected = [...columnQuotation.map((v) => v.name)];
|
||||
|
|
@ -170,8 +191,7 @@ async function submitCustomer() {
|
|||
customerFormData.value.registeredBranchId = isRoleInclude(['system'])
|
||||
? branchId.value
|
||||
: currentMyBranch.value.id;
|
||||
await customerFormStore.submitFormCustomer();
|
||||
|
||||
await customerFormStore.addCurrentCustomerBranch();
|
||||
customerFormState.value.dialogModal = false;
|
||||
// customerFormState.value.dialogType = 'info';
|
||||
}
|
||||
|
|
@ -241,6 +261,16 @@ const {
|
|||
stats: quotationStats,
|
||||
} = storeToRefs(quotationStore);
|
||||
|
||||
const customerNameInfo = computed(() => {
|
||||
if (customerFormData.value.customerBranch === undefined) return;
|
||||
|
||||
const name =
|
||||
locale.value === 'eng'
|
||||
? `${customerFormData.value.customerBranch[0]?.firstNameEN} ${customerFormData.value.customerBranch[0]?.lastNameEN}`
|
||||
: `${customerFormData.value.customerBranch[0]?.firstName} ${customerFormData.value.customerBranch[0]?.lastName}`;
|
||||
return name || '-';
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
pageState.gridView = $q.screen.lt.md ? true : false;
|
||||
navigatorStore.current.title = 'quotation.title';
|
||||
|
|
@ -299,6 +329,8 @@ async function fetchQuotationList(mobileFetch?: boolean) {
|
|||
: 'Issued',
|
||||
query: pageState.inputSearch,
|
||||
urgentFirst: true,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -322,7 +354,12 @@ async function fetchQuotationList(mobileFetch?: boolean) {
|
|||
}
|
||||
|
||||
watch(
|
||||
[() => pageState.currentTab, () => pageState.inputSearch, quotationPageSize],
|
||||
[
|
||||
() => pageState.currentTab,
|
||||
() => pageState.inputSearch,
|
||||
() => pageState.searchDate,
|
||||
quotationPageSize,
|
||||
],
|
||||
() => {
|
||||
quotationPage.value = 1;
|
||||
quotationData.value = [];
|
||||
|
|
@ -489,6 +526,10 @@ async function storeDataLocal(id: string) {
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch v-model="pageState.searchDate" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||
|
|
@ -911,30 +952,49 @@ async function storeDataLocal(id: string) {
|
|||
|
||||
<!-- NOTE: START - Customer / Employer Form -->
|
||||
|
||||
<DialogForm
|
||||
hide-footer
|
||||
ref="formDialogRef"
|
||||
v-model:modal="customerFormState.dialogModal"
|
||||
:title="
|
||||
customerFormState.dialogType === 'create'
|
||||
? $t(`general.add`, {
|
||||
text: `${$t('customer.employer')} `,
|
||||
})
|
||||
: `${$t('customer.employer')} `
|
||||
"
|
||||
:submit="
|
||||
() => {
|
||||
submitCustomer();
|
||||
<DialogContainer
|
||||
:model-value="customerFormState.dialogModal"
|
||||
:on-open="
|
||||
async () => {
|
||||
customerFormStore.resetForm(customerFormState.dialogType === 'create');
|
||||
onCreateImageList = { selectedImage: '', list: [] };
|
||||
customerFormState.customerImageUrl = '';
|
||||
await fetchListOfOptionBranch();
|
||||
await customerFormStore.addCurrentCustomerBranch();
|
||||
}
|
||||
"
|
||||
:close="
|
||||
:on-close="
|
||||
() => {
|
||||
customerFormState.dialogModal = false;
|
||||
customerFormStore.resetForm(true);
|
||||
setDefaultCustomer();
|
||||
onCreateImageList = { selectedImage: '', list: [] };
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #header>
|
||||
<DialogHeader
|
||||
:title="
|
||||
customerFormState.dialogType === 'create'
|
||||
? $t(`general.add`, {
|
||||
text: `${$t('customer.employer')} `,
|
||||
})
|
||||
: `${$t('customer.employer')} `
|
||||
"
|
||||
>
|
||||
<template #title-after>
|
||||
<span
|
||||
:style="`color: hsla(var(--${customerFormData.customerType === 'PERS' ? 'teal-10-hsl' : 'violet-11-hsl'})/1)`"
|
||||
>
|
||||
:
|
||||
{{
|
||||
customerFormData.customerType === 'CORP'
|
||||
? $t('customer.employerLegalEntity')
|
||||
: $t('customer.employerNaturalPerson')
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
</DialogHeader>
|
||||
</template>
|
||||
|
||||
<div
|
||||
:class="{
|
||||
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
||||
|
|
@ -942,10 +1002,10 @@ async function storeDataLocal(id: string) {
|
|||
}"
|
||||
>
|
||||
<ProfileBanner
|
||||
prefix="dialog"
|
||||
v-if="customerFormData.customerBranch !== undefined"
|
||||
active
|
||||
hide-fade
|
||||
prefix="dialog"
|
||||
:fallback-cover="`/images/customer-${customerFormData.customerType}-banner-bg.jpg`"
|
||||
:img="
|
||||
customerFormState.customerImageUrl ||
|
||||
|
|
@ -961,13 +1021,22 @@ async function storeDataLocal(id: string) {
|
|||
"
|
||||
:title="
|
||||
customerFormData.customerType === 'PERS'
|
||||
? `${customerFormData.customerBranch[0]?.firstName || ''} ${customerFormData.customerBranch[0]?.lastName || ''}`
|
||||
: customerFormData.customerBranch[0]?.registerName || ''
|
||||
? setPrefixName(
|
||||
{
|
||||
namePrefix: customerFormData.customerBranch[0]?.namePrefix,
|
||||
firstName: customerFormData.customerBranch[0]?.firstName,
|
||||
lastName: customerFormData.customerBranch[0]?.lastName,
|
||||
firstNameEN: customerFormData.customerBranch[0]?.firstNameEN,
|
||||
lastNameEN: customerFormData.customerBranch[0]?.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
: customerFormData.customerBranch[0]?.registerName
|
||||
"
|
||||
:caption="
|
||||
customerFormData.customerType === 'PERS'
|
||||
? `${customerFormData.customerBranch[0]?.firstNameEN || ''} ${customerFormData.customerBranch[0]?.lastNameEN || ''}`
|
||||
: customerFormData.customerBranch[0]?.registerNameEN || ''
|
||||
? `${customerFormData.customerBranch[0]?.firstNameEN} ${customerFormData.customerBranch[0]?.lastNameEN}`
|
||||
: customerFormData.customerBranch[0]?.registerNameEN
|
||||
"
|
||||
@view="
|
||||
() => {
|
||||
|
|
@ -981,143 +1050,295 @@ async function storeDataLocal(id: string) {
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col"
|
||||
style="flex: 1; width: 100%; overflow-y: auto"
|
||||
id="customer-form"
|
||||
:class="{
|
||||
'q-px-lg q-pb-lg': $q.screen.gt.sm,
|
||||
'q-px-md q-pb-sm': !$q.screen.gt.sm,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
style="overflow-y: auto"
|
||||
class="row full-width full-height surface-1 rounded bordered relative-position"
|
||||
>
|
||||
<div class="col surface-1 full-height rounded bordered scroll row">
|
||||
<div
|
||||
:class="{
|
||||
'q-py-md q-px-lg': $q.screen.gt.sm,
|
||||
'q-py-sm q-px-lg': !$q.screen.gt.sm,
|
||||
}"
|
||||
style="position: absolute; z-index: 99999; top: 0; right: 0"
|
||||
class="col"
|
||||
style="height: 100%; max-height: 100; overflow-y: auto"
|
||||
v-if="$q.screen.gt.sm"
|
||||
>
|
||||
<div
|
||||
v-if="customerFormData.status !== 'INACTIVE'"
|
||||
class="surface-1 row rounded"
|
||||
>
|
||||
<SaveButton
|
||||
v-if="
|
||||
customerFormState.dialogType === 'edit' ||
|
||||
customerFormState.dialogType === 'create'
|
||||
"
|
||||
id="btn-info-basic-save"
|
||||
icon-only
|
||||
type="submit"
|
||||
/>
|
||||
<div class="q-py-md q-pl-md q-pr-sm">
|
||||
<SideMenu
|
||||
:menu="[
|
||||
{
|
||||
name: $t('form.field.basicInformation'),
|
||||
anchor: 'form-basic-info-customer',
|
||||
},
|
||||
{
|
||||
name: $t('customer.form.group.branch'),
|
||||
anchor: 'form-branch-customer-branch',
|
||||
useBtn: true,
|
||||
},
|
||||
...(customerFormData.customerBranch?.map((v, i) => ({
|
||||
name:
|
||||
i === 0
|
||||
? $t('customer.form.headQuarters.title')
|
||||
: $t('customer.form.branch.title', {
|
||||
name: i,
|
||||
}),
|
||||
anchor: `form-branch-customer-no-${i}`,
|
||||
sub: true,
|
||||
})) || []),
|
||||
]"
|
||||
background="transparent"
|
||||
:active="{
|
||||
background: 'hsla(var(--blue-6-hsl) / .2)',
|
||||
foreground: 'var(--blue-6)',
|
||||
}"
|
||||
scroll-element="#customer-form-content"
|
||||
>
|
||||
<template v-slot:btn-form-branch-customer-branch>
|
||||
<q-btn
|
||||
dense
|
||||
flat
|
||||
icon="mdi-plus"
|
||||
size="sm"
|
||||
rounded
|
||||
padding="0px 0px"
|
||||
style="color: var(--stone-9)"
|
||||
@click.stop="submitCustomer()"
|
||||
v-if="
|
||||
customerFormState.branchIndex === -1 &&
|
||||
!!customerFormState.editCustomerId &&
|
||||
customerFormState.dialogType !== 'create'
|
||||
"
|
||||
:disabled="!customerFormState.readonly"
|
||||
/>
|
||||
</template>
|
||||
</SideMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col full-height rounded scroll row q-py-md q-pl-md q-pr-sm"
|
||||
v-if="$q.screen.gt.sm"
|
||||
>
|
||||
<SideMenu
|
||||
:menu="[
|
||||
{
|
||||
name: $t('form.customerInformation'),
|
||||
anchor: 'form-information',
|
||||
},
|
||||
{
|
||||
name: $t('customerBranch.tab.business'),
|
||||
anchor: 'form-business',
|
||||
},
|
||||
{
|
||||
name: $t('form.address'),
|
||||
anchor: 'form-address',
|
||||
},
|
||||
]"
|
||||
background="transparent"
|
||||
:active="{
|
||||
background: 'hsla(var(--blue-6-hsl) / .2)',
|
||||
foreground: 'var(--blue-6)',
|
||||
}"
|
||||
scroll-element="#branch-form"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="col-12 col-md-10 full-height q-col-gutter-sm"
|
||||
class="col-12 col-md-10"
|
||||
:class="{
|
||||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
||||
'q-pa-sm': !$q.screen.gt.sm,
|
||||
}"
|
||||
id="branch-form"
|
||||
style="overflow-y: auto"
|
||||
id="customer-form-content"
|
||||
style="height: 100%; max-height: 100%; overflow-y: auto"
|
||||
>
|
||||
<EmployerFormAbout
|
||||
id="form-information"
|
||||
show-title
|
||||
:index="'0'"
|
||||
:customerType="customerFormData.customerType"
|
||||
:readonly="customerFormState.dialogType === 'info'"
|
||||
v-model:citizen-id="formDataCustomerBranch.citizenId"
|
||||
v-model:prefix-name="formDataCustomerBranch.namePrefix"
|
||||
v-model:first-name="formDataCustomerBranch.firstName"
|
||||
v-model:last-name="formDataCustomerBranch.lastName"
|
||||
v-model:first-name-en="formDataCustomerBranch.firstNameEN"
|
||||
v-model:last-name-en="formDataCustomerBranch.lastNameEN"
|
||||
v-model:gender="formDataCustomerBranch.gender"
|
||||
v-model:birth-date="formDataCustomerBranch.birthDate"
|
||||
v-model:customer-name="formDataCustomerBranch.customerName"
|
||||
v-model:legal-person-no="formDataCustomerBranch.legalPersonNo"
|
||||
v-model:register-name="formDataCustomerBranch.registerName"
|
||||
v-model:register-name-en="formDataCustomerBranch.registerNameEN"
|
||||
v-model:register-date="formDataCustomerBranch.registerDate"
|
||||
v-model:authorized-capital="
|
||||
formDataCustomerBranch.authorizedCapital
|
||||
<EmployerFormBasicInfo
|
||||
prefixId="form"
|
||||
v-if="
|
||||
customerFormData.customerBranch !== undefined &&
|
||||
customerFormData.customerBranch.length > 0
|
||||
"
|
||||
v-model:telephone-no="formDataCustomerBranch.telephoneNo"
|
||||
class="q-mb-xl"
|
||||
:readonly="
|
||||
(customerFormState.dialogType === 'edit' &&
|
||||
customerFormState.readonly === true) ||
|
||||
customerFormState.dialogType === 'info'
|
||||
"
|
||||
:action-disabled="customerFormState.branchIndex !== -1"
|
||||
id="form-basic-info-customer"
|
||||
:onCreate="customerFormState.dialogType === 'create'"
|
||||
@edit="
|
||||
((customerFormState.dialogType = 'edit'),
|
||||
(customerFormState.readonly = false))
|
||||
"
|
||||
@cancel="() => customerFormUndo(false)"
|
||||
@delete="
|
||||
customerFormState.editCustomerId &&
|
||||
deleteCustomerById(customerFormState.editCustomerId)
|
||||
"
|
||||
:customer-type="customerFormData.customerType"
|
||||
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
||||
v-model:customer-name="customerNameInfo"
|
||||
v-model:register-name="
|
||||
customerFormData.customerBranch[0].registerName
|
||||
"
|
||||
v-model:citizen-id="customerFormData.customerBranch[0].citizenId"
|
||||
v-model:legal-person-no="
|
||||
customerFormData.customerBranch[0].legalPersonNo
|
||||
"
|
||||
v-model:business-type="
|
||||
customerFormData.customerBranch[0].businessType
|
||||
"
|
||||
v-model:job-position="
|
||||
customerFormData.customerBranch[0].jobPosition
|
||||
"
|
||||
v-model:telephone-no="
|
||||
customerFormData.customerBranch[0].telephoneNo
|
||||
"
|
||||
v-model:branch-options="registerAbleBranchOption"
|
||||
/>
|
||||
<div class="row q-col-gutter-sm" id="form-branch-customer-branch">
|
||||
<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-briefcase-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
<span>{{ $t('customer.form.group.branch') }}</span>
|
||||
</div>
|
||||
|
||||
<EmployerFormBusiness
|
||||
id="form-business"
|
||||
show-title
|
||||
prefix-id="dialog"
|
||||
dense
|
||||
outlined
|
||||
:readonly="customerFormState.dialogType === 'info'"
|
||||
v-model:bussiness-type="formDataCustomerBranch.businessType"
|
||||
v-model:job-position="formDataCustomerBranch.jobPosition"
|
||||
v-model:job-description="formDataCustomerBranch.jobDescription"
|
||||
v-model:pay-date="formDataCustomerBranch.payDate"
|
||||
v-model:pay-date-en="formDataCustomerBranch.payDateEN"
|
||||
v-model:wage-rate="formDataCustomerBranch.wageRate"
|
||||
v-model:wage-rate-text="formDataCustomerBranch.wageRateText"
|
||||
/>
|
||||
<AddressForm
|
||||
id="form-address"
|
||||
prefix-id="employer"
|
||||
dense
|
||||
outlined
|
||||
use-employment
|
||||
:title="$t('form.address')"
|
||||
:addressTitle="$t('form.address')"
|
||||
:addressTitleEN="$t('form.address', { suffix: '(EN)' })"
|
||||
:readonly="customerFormState.dialogType === 'info'"
|
||||
v-model:bussiness-type="formDataCustomerBranch.businessType"
|
||||
v-model:address="formDataCustomerBranch.address"
|
||||
v-model:address-en="formDataCustomerBranch.addressEN"
|
||||
v-model:street="formDataCustomerBranch.street"
|
||||
v-model:street-en="formDataCustomerBranch.streetEN"
|
||||
v-model:moo="formDataCustomerBranch.moo"
|
||||
v-model:moo-en="formDataCustomerBranch.mooEN"
|
||||
v-model:soi="formDataCustomerBranch.soi"
|
||||
v-model:soi-en="formDataCustomerBranch.soiEN"
|
||||
v-model:province-id="formDataCustomerBranch.provinceId"
|
||||
v-model:district-id="formDataCustomerBranch.districtId"
|
||||
v-model:sub-district-id="formDataCustomerBranch.subDistrictId"
|
||||
v-model:home-code="formDataCustomerBranch.homeCode"
|
||||
/>
|
||||
<template
|
||||
v-for="(_, idx) in customerFormData.customerBranch"
|
||||
:key="idx"
|
||||
>
|
||||
<!-- v-if="customerFormData.customerBranch" -->
|
||||
<q-form
|
||||
class="full-width"
|
||||
greedy
|
||||
@submit.prevent="
|
||||
async () => {
|
||||
if (!customerFormData.customerBranch) return;
|
||||
|
||||
if (customerFormData.customerType === 'PERS') {
|
||||
tabFieldRequired.main = [
|
||||
'citizenId',
|
||||
'namePrefix',
|
||||
'firstName',
|
||||
'firstNameEN',
|
||||
'lastName',
|
||||
'lastNameEN',
|
||||
'gender',
|
||||
'birthDate',
|
||||
];
|
||||
}
|
||||
if (customerFormData.customerType === 'CORP') {
|
||||
tabFieldRequired.main = ['legalPersonNo', 'registerName'];
|
||||
}
|
||||
let tapIsUndefined = validateTabField(
|
||||
customerFormData.customerBranch?.[idx],
|
||||
tabFieldRequired,
|
||||
);
|
||||
|
||||
if (tapIsUndefined.length > 0) {
|
||||
return dialog({
|
||||
color: 'warning',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.incompleteDataEntry'),
|
||||
actionText: t('general.ok'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.incompleteDataEntry', {
|
||||
tap: `${tapIsUndefined.map((v: string) => t(`customerBranch.tab.${v}`)).join(', ')}`,
|
||||
}),
|
||||
action: async () => {
|
||||
return;
|
||||
},
|
||||
});
|
||||
}
|
||||
if (!customerFormData.customerBranch[idx].id) {
|
||||
let res: any;
|
||||
if (idx === 0) {
|
||||
res =
|
||||
await customerFormStore.submitFormCustomer(
|
||||
onCreateImageList,
|
||||
);
|
||||
} else {
|
||||
res = await customerStore.createBranch({
|
||||
...customerFormData.customerBranch[idx],
|
||||
customerId: customerFormState.editCustomerId || '',
|
||||
id: undefined,
|
||||
});
|
||||
}
|
||||
if (res) {
|
||||
customerFormState.readonly = true;
|
||||
notify('create', $t('general.success'));
|
||||
}
|
||||
} else {
|
||||
if (!customerFormState.editCustomerId) return;
|
||||
await customerStore.editBranchById(
|
||||
customerFormData.customerBranch[idx].id || '',
|
||||
{
|
||||
...customerFormData.customerBranch[idx],
|
||||
id: undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const uploadResult =
|
||||
customerFormData.customerBranch[idx].file?.map(
|
||||
async (v) => {
|
||||
if (!v.file) return;
|
||||
|
||||
const ext = v.file.name.split('.').at(-1);
|
||||
let filename = v.group + '-' + new Date().getTime();
|
||||
if (ext) filename += `.${ext}`;
|
||||
|
||||
const res = await customerStore.putAttachment({
|
||||
branchId:
|
||||
customerFormData.customerBranch?.[idx].id || '',
|
||||
file: v.file,
|
||||
filename,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
await customerFormStore.assignFormData(
|
||||
customerFormState.editCustomerId,
|
||||
);
|
||||
}
|
||||
},
|
||||
) || [];
|
||||
|
||||
for (const r of uploadResult) await r;
|
||||
|
||||
await customerFormStore.assignFormData(
|
||||
customerFormState.editCustomerId,
|
||||
);
|
||||
|
||||
customerFormStore.resetForm();
|
||||
}
|
||||
"
|
||||
>
|
||||
<!-- v-if="!!customerFormState.editCustomerId" -->
|
||||
|
||||
<EmployerFormBranch
|
||||
:index="idx"
|
||||
prefixId="form"
|
||||
v-if="customerFormData.customerBranch"
|
||||
v-model:customer="customerFormData"
|
||||
v-model:customer-branch="customerFormData.customerBranch[idx]"
|
||||
:onCreate="customerFormState.dialogType === 'create'"
|
||||
:customer-type="customerFormData.customerType"
|
||||
:readonly="customerFormState.branchIndex !== idx"
|
||||
:action-disabled="
|
||||
!customerFormState.readonly ||
|
||||
(customerFormState.branchIndex !== -1 &&
|
||||
customerFormState.branchIndex !== idx)
|
||||
"
|
||||
@edit="() => (customerFormState.branchIndex = idx)"
|
||||
@cancel="() => customerFormUndo(false)"
|
||||
@delete="
|
||||
async () => {
|
||||
if (!customerFormState.editCustomerId) return;
|
||||
|
||||
if (idx === 0) {
|
||||
deleteCustomerById(customerFormState.editCustomerId);
|
||||
return;
|
||||
}
|
||||
if (!!customerFormData.customerBranch?.[idx].id) {
|
||||
const action = await deleteCustomerBranchById(
|
||||
customerFormData.customerBranch[idx].id || '',
|
||||
);
|
||||
if (action) {
|
||||
await customerFormStore.assignFormData(
|
||||
customerFormState.editCustomerId,
|
||||
);
|
||||
}
|
||||
customerFormStore.resetForm();
|
||||
}
|
||||
}
|
||||
"
|
||||
@save="() => {}"
|
||||
/>
|
||||
</q-form>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogForm>
|
||||
</DialogContainer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { deleteItem } from 'stores/utils';
|
|||
|
||||
// NOTE Import Types
|
||||
import { RequestData, RequestDataStatus } from 'src/stores/request-list/types';
|
||||
import { View } from './types.ts';
|
||||
import { View } from './types';
|
||||
import {
|
||||
PayCondition,
|
||||
ProductRelation,
|
||||
|
|
@ -76,6 +76,7 @@ import { api } from 'src/boot/axios';
|
|||
import { RouterLink, useRoute } from 'vue-router';
|
||||
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
||||
import { convertTemplate } from 'src/utils/string-template';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
|
||||
type Node = {
|
||||
[key: string]: any;
|
||||
|
|
@ -163,7 +164,9 @@ const selectedWorkerItem = computed(() => {
|
|||
employeeName:
|
||||
locale.value === Lang.English
|
||||
? `${e.firstNameEN} ${e.lastNameEN}`
|
||||
: `${e.firstName} ${e.lastName}`,
|
||||
: e.firstName
|
||||
? `${e.firstName} ${e.lastName}`
|
||||
: `${e.firstNameEN} ${e.lastNameEN}`,
|
||||
birthDate: dateFormatJS({ date: e.dateOfBirth }),
|
||||
gender: e.gender,
|
||||
age: calculateAge(e.dateOfBirth),
|
||||
|
|
@ -211,6 +214,17 @@ const attachmentData = ref<
|
|||
url?: string;
|
||||
}[]
|
||||
>([]);
|
||||
const hideBtnApproveInvoice = computed(() => {
|
||||
const role = getRole();
|
||||
const allowedRoles = [
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'head_of_accountant',
|
||||
'accountant',
|
||||
];
|
||||
return !role || !role.some((r) => allowedRoles.includes(r));
|
||||
});
|
||||
|
||||
const getToolbarConfig = computed(() => {
|
||||
const toolbar = [['left', 'center', 'justify'], ['toggle'], ['clip']];
|
||||
|
|
@ -578,8 +592,9 @@ async function convertDataToFormSubmit() {
|
|||
}
|
||||
}),
|
||||
...newWorkerList.value.map((v) => {
|
||||
const { attachment, ...payload } = v;
|
||||
return payload;
|
||||
{
|
||||
return v.id;
|
||||
}
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
|
@ -1850,7 +1865,7 @@ function covertToNode() {
|
|||
name: selectedWorker.map(
|
||||
(v, i) =>
|
||||
`${i + 1}. ` +
|
||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
||||
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''} ${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||
),
|
||||
},
|
||||
})
|
||||
|
|
@ -2317,6 +2332,7 @@ function covertToNode() {
|
|||
"
|
||||
>
|
||||
<MainButton
|
||||
v-if="!hideBtnApproveInvoice"
|
||||
solid
|
||||
icon="mdi-account-multiple-check-outline"
|
||||
color="207 96% 32%"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Icon } from '@iconify/vue';
|
|||
import { ref, watch, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { View } from './types.ts';
|
||||
import { View } from './types';
|
||||
|
||||
import { formatNumberDecimal, commaInput } from 'stores/utils';
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ import SelectInput from 'src/components/shared/SelectInput.vue';
|
|||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { precisionRound } from 'src/utils/arithmetic';
|
||||
import { PayCondition } from 'src/stores/quotations/types.ts';
|
||||
import { PayCondition } from 'src/stores/quotations/types';
|
||||
|
||||
defineEmits<{
|
||||
(e: 'changePayType', type: PayCondition): void;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -221,6 +221,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
|
|||
newWorkerList.value.push({
|
||||
//passportNo: obj.data.passportNo,
|
||||
//documentExpireDate: obj.data.documentExpireDate,
|
||||
id: obj.data.id,
|
||||
lastNameEN: obj.data.lastNameEN,
|
||||
lastName: obj.data.lastName,
|
||||
middleNameEN: obj.data.middleNameEN,
|
||||
|
|
|
|||
|
|
@ -600,7 +600,7 @@ function print() {
|
|||
details?.worker.map(
|
||||
(v, i) =>
|
||||
`${i + 1}. ` +
|
||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
||||
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||
) || [],
|
||||
},
|
||||
}) || '-'
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import DrawerInfo from 'src/components/DrawerInfo.vue';
|
|||
import DialogForm from 'src/components/DialogForm.vue';
|
||||
import ProfileBanner from 'src/components/ProfileBanner.vue';
|
||||
import SideMenu from 'src/components/SideMenu.vue';
|
||||
import FormBank from 'src/components/01_branch-management/FormBank.vue';
|
||||
import FormBasicInfoAgencies from 'src/components/07_agencies-management/FormBasicInfoAgencies.vue';
|
||||
import {
|
||||
UndoButton,
|
||||
|
|
@ -19,6 +20,8 @@ import {
|
|||
} from 'src/components/button';
|
||||
import AddressForm from 'src/components/form/AddressForm.vue';
|
||||
import ImageUploadDialog from 'src/components/ImageUploadDialog.vue';
|
||||
import { BankBook } from 'src/stores/branch/types';
|
||||
import QrCodeUploadDialog from 'src/components/QrCodeUploadDialog.vue';
|
||||
|
||||
const institutionStore = useInstitution();
|
||||
|
||||
|
|
@ -27,10 +30,14 @@ const drawerModel = defineModel<boolean>('drawerModel', {
|
|||
required: true,
|
||||
default: false,
|
||||
});
|
||||
const onCreateImageList = defineModel<{
|
||||
const imageListOnCreate = defineModel<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>('onCreateImageList', { default: { selectedImage: '', list: [] } });
|
||||
}>('imageListOnCreate', { default: { selectedImage: '', list: [] } });
|
||||
const deletesStatusQrCodeBankImag = defineModel<number[]>(
|
||||
'deletesStatusQrCodeBankImag',
|
||||
{ default: [] },
|
||||
);
|
||||
|
||||
const imageState = reactive({
|
||||
imageDialog: false,
|
||||
|
|
@ -43,6 +50,14 @@ const imageState = reactive({
|
|||
const imageFile = ref<File | null>(null);
|
||||
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
||||
|
||||
const qrCodeDialog = ref(false);
|
||||
const qrCodeImageUrl = ref<string>('');
|
||||
const currentIndexQrCodeBank = ref<number>(-1);
|
||||
const statusQrCodeFile = ref<File | undefined>(undefined);
|
||||
const refQrCodeUpload = ref();
|
||||
const statusQrCodeUrl = ref<string>('');
|
||||
const statusDeletesQrCode = ref<boolean>(false);
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
readonly?: boolean;
|
||||
|
|
@ -73,6 +88,9 @@ const data = defineModel<InstitutionPayload>('data', {
|
|||
group: '',
|
||||
name: '',
|
||||
nameEN: '',
|
||||
contactName: '',
|
||||
contactEmail: '',
|
||||
contactTel: '',
|
||||
code: '',
|
||||
addressEN: '',
|
||||
address: '',
|
||||
|
|
@ -88,6 +106,19 @@ const data = defineModel<InstitutionPayload>('data', {
|
|||
selectedImage: '',
|
||||
},
|
||||
});
|
||||
const formBankBook = defineModel<BankBook[]>('formBankBook', {
|
||||
default: [
|
||||
{
|
||||
bankName: '',
|
||||
accountNumber: '',
|
||||
bankBranch: '',
|
||||
accountName: '',
|
||||
accountType: '',
|
||||
currentlyUse: true,
|
||||
bankUrl: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function viewImage() {
|
||||
imageState.imageDialog = true;
|
||||
|
|
@ -141,10 +172,39 @@ async function submitImage(name: string) {
|
|||
function clearImageState() {
|
||||
imageState.imageDialog = false;
|
||||
imageFile.value = null;
|
||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
||||
imageListOnCreate.value = { selectedImage: '', list: [] };
|
||||
imageState.refreshImageState = false;
|
||||
}
|
||||
|
||||
function triggerEditQrCodeBank(opts?: { save?: boolean }) {
|
||||
if (opts?.save === undefined) {
|
||||
qrCodeDialog.value = true;
|
||||
statusDeletesQrCode.value = false;
|
||||
statusQrCodeUrl.value =
|
||||
formBankBook.value[currentIndexQrCodeBank.value].bankUrl || '';
|
||||
statusDeletesQrCode.value = false;
|
||||
} else {
|
||||
formBankBook.value[currentIndexQrCodeBank.value].bankUrl =
|
||||
statusQrCodeUrl.value;
|
||||
|
||||
formBankBook.value[currentIndexQrCodeBank.value].bankQr =
|
||||
statusQrCodeFile.value;
|
||||
|
||||
if (statusDeletesQrCode.value === true) {
|
||||
deletesStatusQrCodeBankImag.value.push(currentIndexQrCodeBank.value);
|
||||
}
|
||||
if (statusDeletesQrCode.value === false) {
|
||||
deletesStatusQrCodeBankImag.value =
|
||||
deletesStatusQrCodeBankImag.value.filter(
|
||||
(item) => item !== currentIndexQrCodeBank.value,
|
||||
);
|
||||
}
|
||||
|
||||
currentIndexQrCodeBank.value = -1;
|
||||
statusDeletesQrCode.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => imageFile.value,
|
||||
() => {
|
||||
|
|
@ -159,7 +219,6 @@ watch(
|
|||
imageList.value
|
||||
? (imageList.value.selectedImage = data.value.selectedImage || '')
|
||||
: '';
|
||||
console.log(imageState.imageUrl);
|
||||
imageState.refreshImageState = false;
|
||||
},
|
||||
);
|
||||
|
|
@ -237,11 +296,15 @@ watch(
|
|||
:menu="[
|
||||
{
|
||||
name: $t('form.field.basicInformation'),
|
||||
anchor: 'agencies-basic-info',
|
||||
anchor: 'agencies-form-basic-info',
|
||||
},
|
||||
{
|
||||
name: $t('general.address'),
|
||||
anchor: 'agencies-address-info',
|
||||
anchor: 'agencies-form-address-info',
|
||||
},
|
||||
{
|
||||
name: $t('agencies.bankInfo'),
|
||||
anchor: 'agencies-form-bank-info',
|
||||
},
|
||||
]"
|
||||
background="transparent"
|
||||
|
|
@ -275,14 +338,17 @@ watch(
|
|||
</div>
|
||||
</div>
|
||||
<FormBasicInfoAgencies
|
||||
id="agencies-basic-info"
|
||||
id="agencies-form-basic-info"
|
||||
class="q-mb-xl"
|
||||
v-model:group="data.group"
|
||||
v-model:name="data.name"
|
||||
v-model:name-en="data.nameEN"
|
||||
v-model:contact-name="data.contactName"
|
||||
v-model:email="data.contactEmail"
|
||||
v-model:contact-tel="data.contactTel"
|
||||
/>
|
||||
<AddressForm
|
||||
id="agencies-address-info"
|
||||
id="agencies-form-address-info"
|
||||
dense
|
||||
:prefix-id="''"
|
||||
v-model:address="data.address"
|
||||
|
|
@ -297,6 +363,25 @@ watch(
|
|||
v-model:district-id="data.districtId"
|
||||
v-model:sub-district-id="data.subDistrictId"
|
||||
/>
|
||||
<FormBank
|
||||
id="agencies-form-bank-info"
|
||||
title="agencies.bankInfo"
|
||||
class="q-pt-xl"
|
||||
dense
|
||||
v-model:bank-book-list="formBankBook"
|
||||
@view-qr="
|
||||
(i) => {
|
||||
currentIndexQrCodeBank = i;
|
||||
triggerEditQrCodeBank();
|
||||
}
|
||||
"
|
||||
@edit-qr="
|
||||
(i) => {
|
||||
currentIndexQrCodeBank = i;
|
||||
refQrCodeUpload && refQrCodeUpload.browse();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogForm>
|
||||
|
|
@ -427,13 +512,17 @@ watch(
|
|||
name: $t('general.address'),
|
||||
anchor: 'agencies-address-info',
|
||||
},
|
||||
{
|
||||
name: $t('agencies.bankInfo'),
|
||||
anchor: 'agencies-bank-info',
|
||||
},
|
||||
]"
|
||||
background="transparent"
|
||||
:active="{
|
||||
background: 'hsla(var(--blue-6-hsl) / .2)',
|
||||
foreground: 'var(--blue-6)',
|
||||
}"
|
||||
scroll-element="#agencies-form-content"
|
||||
scroll-element="#agencies-view-content"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -444,7 +533,7 @@ watch(
|
|||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
'q-pa-sm': !$q.screen.gt.sm,
|
||||
}"
|
||||
id="user-form-content"
|
||||
id="agencies-view-content"
|
||||
style="height: 100%; max-height: 100; overflow-y: auto"
|
||||
>
|
||||
<FormBasicInfoAgencies
|
||||
|
|
@ -455,6 +544,9 @@ watch(
|
|||
v-model:group="data.group"
|
||||
v-model:name="data.name"
|
||||
v-model:name-en="data.nameEN"
|
||||
v-model:contact-name="data.contactName"
|
||||
v-model:email="data.contactEmail"
|
||||
v-model:contact-tel="data.contactTel"
|
||||
/>
|
||||
<AddressForm
|
||||
id="agencies-address-info"
|
||||
|
|
@ -473,6 +565,26 @@ watch(
|
|||
v-model:district-id="data.districtId"
|
||||
v-model:sub-district-id="data.subDistrictId"
|
||||
/>
|
||||
<FormBank
|
||||
id="agencies-bank-info"
|
||||
title="agencies.bankInfo"
|
||||
class="q-pt-xl"
|
||||
dense
|
||||
:readonly
|
||||
v-model:bank-book-list="formBankBook"
|
||||
@view-qr="
|
||||
(i) => {
|
||||
currentIndexQrCodeBank = i;
|
||||
triggerEditQrCodeBank();
|
||||
}
|
||||
"
|
||||
@edit-qr="
|
||||
(i) => {
|
||||
currentIndexQrCodeBank = i;
|
||||
refQrCodeUpload && refQrCodeUpload.browse();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -482,7 +594,7 @@ watch(
|
|||
<ImageUploadDialog
|
||||
v-model:dialog-state="imageState.imageDialog"
|
||||
v-model:file="imageFile"
|
||||
v-model:on-create-data-list="onCreateImageList"
|
||||
v-model:on-create-data-list="imageListOnCreate"
|
||||
v-model:image-url="imageState.imageUrl"
|
||||
v-model:data-list="imageList"
|
||||
:on-create="model"
|
||||
|
|
@ -512,5 +624,31 @@ watch(
|
|||
</div>
|
||||
</template>
|
||||
</ImageUploadDialog>
|
||||
|
||||
<QrCodeUploadDialog
|
||||
ref="refQrCodeUpload"
|
||||
v-model:dialog-state="qrCodeDialog"
|
||||
v-model:file="statusQrCodeFile as File"
|
||||
v-model:image-url="statusQrCodeUrl"
|
||||
@save="
|
||||
(_file) => {
|
||||
qrCodeDialog = false;
|
||||
if (currentIndexQrCodeBank !== -1) {
|
||||
triggerEditQrCodeBank({ save: true });
|
||||
}
|
||||
}
|
||||
"
|
||||
@clear="statusDeletesQrCode = true"
|
||||
clearButton
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
class="full-width full-height flex items-center justify-center"
|
||||
style="color: gray"
|
||||
>
|
||||
<q-icon size="15rem" name="mdi-qrcode" />
|
||||
</div>
|
||||
</template>
|
||||
</QrCodeUploadDialog>
|
||||
</template>
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { QSelect, QTableProps } from 'quasar';
|
||||
import { QTableProps } from 'quasar';
|
||||
import { dialog } from 'src/stores/utils';
|
||||
import { onMounted, reactive, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
|
@ -21,6 +21,7 @@ import FloatingActionButton from 'src/components/FloatingActionButton.vue';
|
|||
import CreateButton from 'src/components/AddButton.vue';
|
||||
import NoData from 'src/components/NoData.vue';
|
||||
import AgenciesDialog from './AgenciesDialog.vue';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
|
|
@ -78,13 +79,17 @@ const pageState = reactive({
|
|||
addModal: false,
|
||||
viewDrawer: false,
|
||||
isDrawerEdit: true,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const deletesStatusQrCodeBankImag = ref<number[]>([]);
|
||||
const blankFormData: InstitutionPayload = {
|
||||
group: '',
|
||||
code: '',
|
||||
name: '',
|
||||
nameEN: '',
|
||||
contactName: '',
|
||||
contactEmail: '',
|
||||
contactTel: '',
|
||||
addressEN: '',
|
||||
address: '',
|
||||
soi: '',
|
||||
|
|
@ -98,14 +103,23 @@ const blankFormData: InstitutionPayload = {
|
|||
provinceId: '',
|
||||
selectedImage: '',
|
||||
status: 'CREATED',
|
||||
bank: [
|
||||
{
|
||||
bankName: '',
|
||||
accountNumber: '',
|
||||
bankBranch: '',
|
||||
accountName: '',
|
||||
accountType: '',
|
||||
currentlyUse: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
const refAgenciesDialog = ref();
|
||||
const formData = ref<InstitutionPayload>(structuredClone(blankFormData));
|
||||
const currAgenciesData = ref<Institution>();
|
||||
const onCreateImageList = ref<{
|
||||
const imageListOnCreate = ref<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>({ selectedImage: '', list: [] });
|
||||
|
|
@ -160,6 +174,18 @@ function assignFormData(data: Institution) {
|
|||
provinceId: data.provinceId,
|
||||
selectedImage: data.selectedImage,
|
||||
status: data.status,
|
||||
contactEmail: data.contactEmail,
|
||||
contactName: data.contactName,
|
||||
contactTel: data.contactTel,
|
||||
bank: data.bank.map((v) => ({
|
||||
bankName: v.bankName,
|
||||
accountNumber: v.accountNumber,
|
||||
bankBranch: v.bankBranch,
|
||||
accountName: v.accountName,
|
||||
accountType: v.accountType,
|
||||
currentlyUse: v.currentlyUse,
|
||||
bankUrl: `${baseUrl}/institution/${data.id}/bank-qr/${v.id}?ts=${Date.now()}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +195,9 @@ async function submit(opt?: { selectedImage: string }) {
|
|||
code: formData.value.code,
|
||||
name: formData.value.name,
|
||||
nameEN: formData.value.nameEN,
|
||||
contactName: formData.value.contactName,
|
||||
contactEmail: formData.value.contactEmail,
|
||||
contactTel: formData.value.contactTel,
|
||||
addressEN: formData.value.addressEN,
|
||||
address: formData.value.address,
|
||||
soi: formData.value.soi,
|
||||
|
|
@ -181,7 +210,11 @@ async function submit(opt?: { selectedImage: string }) {
|
|||
districtId: formData.value.districtId,
|
||||
provinceId: formData.value.provinceId,
|
||||
status: formData.value.status,
|
||||
bank: formData.value.bank.map((v) => ({
|
||||
...v,
|
||||
})),
|
||||
};
|
||||
console.log('payload', payload);
|
||||
if (
|
||||
(pageState.isDrawerEdit && currAgenciesData.value?.id) ||
|
||||
(opt?.selectedImage && currAgenciesData.value?.id)
|
||||
|
|
@ -192,6 +225,7 @@ async function submit(opt?: { selectedImage: string }) {
|
|||
id: currAgenciesData.value.id,
|
||||
selectedImage: opt?.selectedImage || undefined,
|
||||
}),
|
||||
{ indexDeleteQrCodeBank: deletesStatusQrCodeBankImag.value },
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -211,7 +245,7 @@ async function submit(opt?: { selectedImage: string }) {
|
|||
...payload,
|
||||
code: formData.value.group || '',
|
||||
},
|
||||
onCreateImageList.value,
|
||||
imageListOnCreate.value,
|
||||
);
|
||||
|
||||
await fetchData($q.screen.xs);
|
||||
|
|
@ -251,6 +285,8 @@ async function fetchData(mobileFetch?: boolean) {
|
|||
: statusFilter.value === 'statusACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -320,7 +356,7 @@ onMounted(async () => {
|
|||
});
|
||||
|
||||
watch(
|
||||
() => [pageState.inputSearch, statusFilter.value],
|
||||
() => [pageState.inputSearch, statusFilter.value, pageState.searchDate],
|
||||
() => {
|
||||
page.value = 1;
|
||||
data.value = [];
|
||||
|
|
@ -403,26 +439,44 @@ watch(
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="pageState.searchDate"
|
||||
:active="$q.screen.lt.md && statusFilter !== 'all'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{ label: $t('general.all'), value: 'all' },
|
||||
{ label: $t('general.active'), value: 'statusACTIVE' },
|
||||
{
|
||||
label: $t('general.inactive'),
|
||||
value: 'statusINACTIVE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
v-model="statusFilter"
|
||||
outlined
|
||||
dense
|
||||
|
|
@ -922,7 +976,9 @@ watch(
|
|||
v-model="pageState.addModal"
|
||||
v-model:drawer-model="pageState.viewDrawer"
|
||||
v-model:data="formData"
|
||||
v-model:on-create-image-list="onCreateImageList"
|
||||
v-model:form-bank-book="formData.bank"
|
||||
v-model:image-list-on-create="imageListOnCreate"
|
||||
v-model:deletes-status-qr-code-bank-imag="deletesStatusQrCodeBankImag"
|
||||
/>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { useRequestList } from 'src/stores/request-list';
|
|||
const props = defineProps<{
|
||||
readonly?: boolean;
|
||||
step: Step;
|
||||
requestWorkId: string;
|
||||
}>();
|
||||
|
||||
const requestListStore = useRequestList();
|
||||
|
|
@ -116,7 +117,7 @@ function assignToForm() {
|
|||
<FormGroupHead>
|
||||
{{ $t('quotation.templateForm') }}
|
||||
</FormGroupHead>
|
||||
<FormIssue :readonly="!state.isEdit" />
|
||||
<FormIssue :request-work-id="requestWorkId" :readonly="!state.isEdit" />
|
||||
</section>
|
||||
</main>
|
||||
</q-expansion-item>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,49 @@
|
|||
import { ref } from 'vue';
|
||||
import { MainButton } from 'components/button';
|
||||
import SelectInput from 'src/components/shared/SelectInput.vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { api } from 'src/boot/axios';
|
||||
import { baseUrl } from 'src/stores/utils';
|
||||
|
||||
defineProps<{
|
||||
const prop = defineProps<{
|
||||
readonly?: boolean;
|
||||
requestWorkId: string;
|
||||
}>();
|
||||
const templateForm = defineModel<string>();
|
||||
const templateFormOption = ref<
|
||||
{ label: string; labelEN: string; value: string }[]
|
||||
>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const { data: docTemplate, status } = await api.get<string[]>(
|
||||
'/doc-template',
|
||||
{ params: { templateGroup: 'Request' } },
|
||||
);
|
||||
|
||||
if (status < 400) {
|
||||
templateFormOption.value = docTemplate.map((v) => ({
|
||||
label: v,
|
||||
labelEN: v,
|
||||
value: v,
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
async function formDownload() {
|
||||
const res = await fetch(
|
||||
baseUrl +
|
||||
'/doc-template/' +
|
||||
templateForm.value +
|
||||
`?templateGroup=Request&data=request-work&dataId=${prop.requestWorkId}`,
|
||||
);
|
||||
const blob = await res.blob();
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.download = templateForm.value;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -24,7 +59,6 @@ const templateFormOption = ref<
|
|||
<SelectInput
|
||||
id="quotation-branch"
|
||||
style="grid-column: span 2"
|
||||
incremental
|
||||
v-model="templateForm"
|
||||
class="full-width"
|
||||
:readonly
|
||||
|
|
@ -32,21 +66,14 @@ const templateFormOption = ref<
|
|||
:label="$t('quotation.templateForm')"
|
||||
:option-label="$i18n.locale === 'eng' ? 'labelEN' : 'label'"
|
||||
/>
|
||||
<MainButton
|
||||
outlined
|
||||
icon="mdi-play-box-outline"
|
||||
color="207 96% 32%"
|
||||
class="full-width"
|
||||
style="grid-column: span 1"
|
||||
>
|
||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||
</MainButton>
|
||||
<MainButton
|
||||
solid
|
||||
icon="mdi-pencil-outline"
|
||||
color="207 96% 32%"
|
||||
class="full-width"
|
||||
style="grid-column: span 1"
|
||||
:disabled="!templateForm"
|
||||
@click="formDownload"
|
||||
>
|
||||
{{ $t('general.designForm') }}
|
||||
</MainButton>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { QSelect, useQuasar } from 'quasar';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
// NOTE: Components
|
||||
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
||||
|
|
@ -24,6 +24,9 @@ import { RequestData, RequestDataStatus } from 'src/stores/request-list/types';
|
|||
import { dialogWarningClose } from 'src/stores/utils';
|
||||
import { CancelButton, SaveButton } from 'src/components/button';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
import FloatingActionButton from 'src/components/FloatingActionButton.vue';
|
||||
import RequestListAction from './RequestListAction .vue';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const $q = useQuasar();
|
||||
const navigatorStore = useNavigator();
|
||||
|
|
@ -32,7 +35,7 @@ const requestListStore = useRequestList();
|
|||
const { t } = useI18n();
|
||||
const { data, stats, page, pageMax, pageSize } = storeToRefs(requestListStore);
|
||||
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
const requestListActionData = ref<RequestData[]>();
|
||||
|
||||
// NOTE: Variable
|
||||
const pageState = reactive({
|
||||
|
|
@ -45,6 +48,8 @@ const pageState = reactive({
|
|||
rejectCancelDialog: false,
|
||||
rejectCancelReason: '',
|
||||
requestId: '',
|
||||
requestListActionDialog: false,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const fieldSelectedOption = computed(() => {
|
||||
|
|
@ -60,6 +65,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
|
|||
query: pageState.inputSearch,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
requestDataStatus:
|
||||
pageState.statusFilter === 'None' ? undefined : pageState.statusFilter,
|
||||
// responsibleOnly: true,
|
||||
|
|
@ -131,6 +138,32 @@ async function submitRejectCancel() {
|
|||
}
|
||||
}
|
||||
|
||||
async function openRequestListDialog() {
|
||||
const ret = await requestListStore.getRequestDataList({
|
||||
page: 1,
|
||||
pageSize: 999,
|
||||
incomplete: true,
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
requestListActionData.value = ret.result;
|
||||
}
|
||||
|
||||
pageState.requestListActionDialog = true;
|
||||
}
|
||||
|
||||
async function submitRequestListAction(data: {
|
||||
form: { responsibleUserLocal: boolean; responsibleUserId: string };
|
||||
selected: RequestData[];
|
||||
}) {
|
||||
const res = await requestListStore.updateMessenger(
|
||||
data.selected.map((v) => v.id),
|
||||
data.form.responsibleUserId,
|
||||
);
|
||||
|
||||
if (res) pageState.requestListActionDialog = false;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
pageState.gridView = $q.screen.lt.md ? true : false;
|
||||
navigatorStore.current.title = 'requestList.title';
|
||||
|
|
@ -140,13 +173,27 @@ onMounted(async () => {
|
|||
await fetchList({ rotateFlowId: true });
|
||||
});
|
||||
|
||||
watch([() => pageState.inputSearch, () => pageState.statusFilter], () => {
|
||||
page.value = 1;
|
||||
data.value = [];
|
||||
fetchList({ rotateFlowId: true });
|
||||
});
|
||||
watch(
|
||||
[
|
||||
() => pageState.inputSearch,
|
||||
() => pageState.statusFilter,
|
||||
() => pageState.searchDate,
|
||||
],
|
||||
() => {
|
||||
page.value = 1;
|
||||
data.value = [];
|
||||
fetchList({ rotateFlowId: true });
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
hide-icon
|
||||
style="z-index: 999"
|
||||
icon="mdi-account-outline"
|
||||
@click.stop="openRequestListDialog"
|
||||
></FloatingActionButton>
|
||||
|
||||
<div class="column full-height no-wrap">
|
||||
<!-- SEC: stat -->
|
||||
<section class="text-body-2 q-mb-xs flex items-center">
|
||||
|
|
@ -239,26 +286,62 @@ watch([() => pageState.inputSearch, () => pageState.statusFilter], () => {
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="pageState.searchDate"
|
||||
:active="$q.screen.lt.md && pageState.statusFilter !== 'None'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="pageState.statusFilter"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{
|
||||
label: $t('general.all'),
|
||||
value: 'None',
|
||||
},
|
||||
{
|
||||
label: $t('requestList.status.Pending'),
|
||||
value: RequestDataStatus.Pending,
|
||||
},
|
||||
{
|
||||
label: $t('requestList.status.Ready'),
|
||||
value: RequestDataStatus.Ready,
|
||||
},
|
||||
{
|
||||
label: $t('requestList.status.InProgress'),
|
||||
value: RequestDataStatus.InProgress,
|
||||
},
|
||||
{
|
||||
label: $t('requestList.status.Completed'),
|
||||
value: RequestDataStatus.Completed,
|
||||
},
|
||||
{
|
||||
label: $t('requestList.status.Canceled'),
|
||||
value: RequestDataStatus.Canceled,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
v-model="pageState.statusFilter"
|
||||
outlined
|
||||
dense
|
||||
|
|
@ -477,7 +560,7 @@ watch([() => pageState.inputSearch, () => pageState.statusFilter], () => {
|
|||
@click="() => (pageState.rejectCancelDialog = false)"
|
||||
/>
|
||||
<SaveButton
|
||||
label="ยืนยัน"
|
||||
:label="$t('general.confirm')"
|
||||
class="q-ml-sm"
|
||||
icon="mdi-check"
|
||||
solid
|
||||
|
|
@ -485,6 +568,13 @@ watch([() => pageState.inputSearch, () => pageState.statusFilter], () => {
|
|||
/>
|
||||
</template>
|
||||
</DialogFormContainer>
|
||||
|
||||
<RequestListAction
|
||||
v-if="requestListActionData"
|
||||
v-model="pageState.requestListActionDialog"
|
||||
:request-list="requestListActionData"
|
||||
@submit="submitRequestListAction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style></style>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const props = defineProps<{
|
|||
readonly?: boolean;
|
||||
step: Step;
|
||||
responsibleAreaDistrictId?: string;
|
||||
defaultMessenger?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -85,7 +86,8 @@ function assignToForm() {
|
|||
companyDuty: attributesForm.value.companyDuty ?? false,
|
||||
companyDutyCost: attributesForm.value.companyDutyCost ?? 30,
|
||||
responsibleUserLocal: attributesForm.value.responsibleUserLocal ?? true,
|
||||
responsibleUserId: attributesForm.value.responsibleUserId ?? '',
|
||||
responsibleUserId:
|
||||
attributesForm.value.responsibleUserId || props.defaultMessenger,
|
||||
individualDuty: attributesForm.value.individualDuty ?? false,
|
||||
individualDutyCost: attributesForm.value.individualDutyCost ?? 10,
|
||||
}),
|
||||
|
|
|
|||
238
src/pages/08_request-list/RequestListAction .vue
Normal file
238
src/pages/08_request-list/RequestListAction .vue
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { RequestData } from 'src/stores/request-list';
|
||||
import { DialogContainer, DialogHeader } from 'src/components/dialog';
|
||||
import {
|
||||
BackButton,
|
||||
CancelButton,
|
||||
MainButton,
|
||||
SaveButton,
|
||||
} from 'src/components/button';
|
||||
import FormResponsibleUser from './FormResponsibleUser.vue';
|
||||
import FormGroupHead from './FormGroupHead.vue';
|
||||
import TableRequestList from './TableRequestList.vue';
|
||||
import { column } from './constants';
|
||||
import useAddressStore from 'src/stores/address';
|
||||
|
||||
defineProps<{
|
||||
requestList: RequestData[];
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
(
|
||||
e: 'submit',
|
||||
data: {
|
||||
form: { responsibleUserLocal: boolean; responsibleUserId: string };
|
||||
selected: RequestData[];
|
||||
},
|
||||
): void;
|
||||
}>();
|
||||
|
||||
enum Step {
|
||||
RequestList = 1,
|
||||
Configure = 2,
|
||||
}
|
||||
|
||||
const open = defineModel<boolean>({ default: false });
|
||||
const step = ref<Step>(Step.RequestList);
|
||||
const selected = ref<RequestData[]>([]);
|
||||
const listSameArea = ref<string[]>([]);
|
||||
const form = reactive({
|
||||
responsibleUserLocal: false,
|
||||
responsibleUserId: '',
|
||||
});
|
||||
|
||||
function reset() {
|
||||
step.value = Step.RequestList;
|
||||
selected.value = [];
|
||||
form.responsibleUserLocal = false;
|
||||
form.responsibleUserId = '';
|
||||
}
|
||||
|
||||
function prev() {
|
||||
step.value = Step.RequestList;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selected.value,
|
||||
async () => {
|
||||
if (selected.value.length === 1) {
|
||||
const districtId = selected.value[0].quotation.customerBranch.districtId;
|
||||
const ret = await useAddressStore().listSameOfficeArea(districtId);
|
||||
if (ret) listSameArea.value = ret;
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<DialogContainer v-model="open" :onOpen="reset">
|
||||
<template #header>
|
||||
<DialogHeader :title="$t('requestList.action.title')" />
|
||||
</template>
|
||||
|
||||
<div class="surface-0 q-pa-md">
|
||||
<div class="stepper-wrapper">
|
||||
<div class="stepper">
|
||||
<template
|
||||
v-for="(label, i) in [
|
||||
$t('menu.product'),
|
||||
$t('requestList.action.configure'),
|
||||
]"
|
||||
:key="i"
|
||||
>
|
||||
<span class="step" :class="{ ['step__active']: step > i }">
|
||||
<span class="step-outer"><span class="step-inner" /></span>
|
||||
<span class="step-label">{{ label }}</span>
|
||||
</span>
|
||||
<span
|
||||
class="step-connector"
|
||||
:class="{ ['step-connector__active']: step > i + 1 }"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="surface-1 q-pa-md col full-width scroll">
|
||||
<TableRequestList
|
||||
v-if="step === Step.RequestList"
|
||||
v-model:selected="selected"
|
||||
hide-action
|
||||
hide-view
|
||||
checkable
|
||||
:list-same-area="listSameArea"
|
||||
:columns="column"
|
||||
:rows="requestList"
|
||||
:visible-columns="[...column.map((col) => col.name)]"
|
||||
/>
|
||||
|
||||
<template v-if="step === Step.Configure">
|
||||
<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
|
||||
>
|
||||
<template #header>
|
||||
<span>
|
||||
{{ $t('requestList.employeeMessenger') }}
|
||||
</span>
|
||||
</template>
|
||||
<FormGroupHead>
|
||||
{{
|
||||
$t('general.select', { msg: $t('requestList.employeeMessenger') })
|
||||
}}
|
||||
</FormGroupHead>
|
||||
|
||||
<FormResponsibleUser
|
||||
:district-id="listSameArea[0]"
|
||||
v-model:responsible-user-id="form.responsibleUserId"
|
||||
v-model:responsible-user-local="form.responsibleUserLocal"
|
||||
/>
|
||||
</q-expansion-item>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="q-gutter-x-xs q-ml-auto">
|
||||
<CancelButton
|
||||
v-if="step === Step.RequestList"
|
||||
id="btn-cancel"
|
||||
outlined
|
||||
@click="
|
||||
reset();
|
||||
open = false;
|
||||
"
|
||||
/>
|
||||
<BackButton
|
||||
v-if="step === Step.Configure"
|
||||
id="btn-back"
|
||||
outlined
|
||||
@click="prev"
|
||||
/>
|
||||
<MainButton
|
||||
icon="mdi-check"
|
||||
color="207 96% 32%"
|
||||
solid
|
||||
id="btn-next"
|
||||
v-if="step === Step.RequestList"
|
||||
@click="step = Step.Configure"
|
||||
>
|
||||
{{ $t('general.next') }}
|
||||
</MainButton>
|
||||
<SaveButton
|
||||
v-if="step === Step.Configure"
|
||||
id="btn-save"
|
||||
solid
|
||||
@click="$emit('submit', { form, selected })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</DialogContainer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-inline: 25%;
|
||||
|
||||
& > .step {
|
||||
--__color: var(--gray-5);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: 0.25rem;
|
||||
|
||||
&.step__active {
|
||||
--__color: var(--brand-1);
|
||||
}
|
||||
|
||||
& > .step-label {
|
||||
position: absolute;
|
||||
font-weight: 600;
|
||||
color: var(--__color);
|
||||
white-space: nowrap;
|
||||
top: 2rem;
|
||||
}
|
||||
|
||||
& > .step-outer {
|
||||
display: inline-flex;
|
||||
border: 2px solid var(--__color);
|
||||
border-radius: 50%;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > .step-inner {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
background-color: var(--__color);
|
||||
width: 0.7rem;
|
||||
height: 0.7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .step-connector {
|
||||
display: block;
|
||||
border-bottom: 2px solid var(--gray-5);
|
||||
flex-grow: 1;
|
||||
|
||||
&.step-connector__active {
|
||||
border-color: var(--brand-1);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -54,6 +54,7 @@ import { Invoice } from 'src/stores/payment/types';
|
|||
import { CreatedBy } from 'src/stores/types';
|
||||
import { getUserId } from 'src/services/keycloak';
|
||||
import { QuotationFull } from 'src/stores/quotations/types';
|
||||
import useUserStore from 'src/stores/user';
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
|
||||
|
|
@ -62,7 +63,9 @@ const route = useRoute();
|
|||
const optionStore = useOptionStore();
|
||||
const requestListStore = useRequestList();
|
||||
const flowTemplateStore = useWorkflowTemplate();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const currentUserGroup = ref<string[]>([]);
|
||||
const workList = ref<RequestWork[]>([]);
|
||||
const statusFile = ref<Attributes>({
|
||||
customer: {},
|
||||
|
|
@ -158,6 +161,10 @@ onMounted(async () => {
|
|||
initTheme();
|
||||
initLang();
|
||||
|
||||
const result = await userStore.fetchUserGroup();
|
||||
|
||||
currentUserGroup.value = result.map((v) => v.name);
|
||||
|
||||
// get data
|
||||
await getData();
|
||||
});
|
||||
|
|
@ -283,26 +290,38 @@ async function triggerViewFile(opt: {
|
|||
if (!opt.download) window.open(url, '_blank');
|
||||
}
|
||||
|
||||
const responsiblePersonList = computed(() => {
|
||||
const temp = workList.value?.reduce<Record<string, CreatedBy[]>>(
|
||||
(acc, curr: RequestWork) => {
|
||||
curr.productService.service?.workflow?.step.forEach((v) => {
|
||||
const key = v.order.toString();
|
||||
const responsibleList = computed(() => {
|
||||
const temp = workList.value?.reduce<
|
||||
Record<string, { user: CreatedBy[]; group: string[] }>
|
||||
>((acc, curr: RequestWork) => {
|
||||
curr.productService.service?.workflow?.step.forEach((v) => {
|
||||
const key = v.order.toString();
|
||||
const responsibleGroup = (
|
||||
v.responsibleGroup as unknown as { group: string }[]
|
||||
).map((v) => v.group);
|
||||
|
||||
if (!acc[key]) acc[key] = v.responsiblePerson.map((v) => v.user);
|
||||
if (!acc[key]) {
|
||||
acc[key] = {
|
||||
user: v.responsiblePerson.map((v) => v.user),
|
||||
group: responsibleGroup,
|
||||
};
|
||||
}
|
||||
|
||||
const current = acc[key];
|
||||
const current = acc[key];
|
||||
|
||||
v.responsiblePerson.forEach((lhs) => {
|
||||
if (current.find((rhs) => rhs.id === lhs.userId)) return;
|
||||
current.push(lhs.user);
|
||||
});
|
||||
v.responsiblePerson.forEach((lhs) => {
|
||||
if (current.user.find((rhs) => rhs.id === lhs.userId)) return;
|
||||
current.user.push(lhs.user);
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
responsibleGroup.forEach((lhs) => {
|
||||
if (current.group.find((rhs) => rhs === lhs)) return;
|
||||
current.group.push(lhs);
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return temp || {};
|
||||
});
|
||||
|
|
@ -438,6 +457,24 @@ async function submitRejectCancel() {
|
|||
pageState.rejectCancelDialog = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toCustomer(customer: RequestData['quotation']['customerBranch']) {
|
||||
const url = new URL(
|
||||
`/customer-management?tab=customer&id=${customer.customerId}`,
|
||||
window.location.origin,
|
||||
);
|
||||
|
||||
window.open(url.toString(), '_blank');
|
||||
}
|
||||
|
||||
function toEmployee(employee: RequestData['employee']) {
|
||||
const url = new URL(
|
||||
`/customer-management?tab=employee&id=${employee.id}`,
|
||||
window.location.origin,
|
||||
);
|
||||
|
||||
window.open(url.toString(), '_blank');
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="column surface-0 fullscreen" v-if="data">
|
||||
|
|
@ -478,11 +515,11 @@ async function submitRejectCancel() {
|
|||
<span class="app-text-muted">{{ $t('flow.responsiblePerson') }}</span>
|
||||
<span>
|
||||
<template
|
||||
v-if="responsiblePersonList[pageState.currentStep]?.length"
|
||||
v-if="responsibleList[pageState.currentStep]?.user.length"
|
||||
>
|
||||
<AvatarGroup
|
||||
:data="
|
||||
(responsiblePersonList[pageState.currentStep] || []).map(
|
||||
:data="[
|
||||
...(responsibleList[pageState.currentStep].user || []).map(
|
||||
(v) => ({
|
||||
name:
|
||||
$i18n.locale === 'eng'
|
||||
|
|
@ -494,8 +531,12 @@ async function submitRejectCancel() {
|
|||
: `/no-img-female.png`
|
||||
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
||||
}),
|
||||
)
|
||||
"
|
||||
),
|
||||
...responsibleList[pageState.currentStep].group.map((g) => ({
|
||||
name: `${$t('general.group')} ${g}`,
|
||||
imgUrl: '/img-group.png',
|
||||
})),
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
|
|
@ -701,6 +742,7 @@ async function submitRejectCancel() {
|
|||
}"
|
||||
>
|
||||
<DataDisplay
|
||||
clickable
|
||||
class="col"
|
||||
icon="mdi-account-settings-outline"
|
||||
:label="$t('customer.employer')"
|
||||
|
|
@ -710,8 +752,10 @@ async function submitRejectCancel() {
|
|||
noCode: true,
|
||||
}) || '-'
|
||||
"
|
||||
@label-click="toCustomer(data.quotation.customerBranch)"
|
||||
/>
|
||||
<DataDisplay
|
||||
clickable
|
||||
class="col"
|
||||
icon="mdi-account-settings-outline"
|
||||
:label="$t('customer.employee')"
|
||||
|
|
@ -720,6 +764,7 @@ async function submitRejectCancel() {
|
|||
locale: $i18n.locale,
|
||||
}) || '-'
|
||||
"
|
||||
@label-click="toEmployee(data.employee)"
|
||||
/>
|
||||
<DataDisplay
|
||||
class="col"
|
||||
|
|
@ -776,11 +821,15 @@ async function submitRejectCancel() {
|
|||
:cancel="data.requestDataStatus === RequestDataStatus.Canceled"
|
||||
:readonly="
|
||||
data.requestDataStatus === RequestDataStatus.Canceled ||
|
||||
(responsiblePersonList &&
|
||||
!!responsiblePersonList[pageState.currentStep]?.length &&
|
||||
!responsiblePersonList[pageState.currentStep]?.find(
|
||||
(responsibleList &&
|
||||
!responsibleList[pageState.currentStep]?.user.find(
|
||||
(v) => v.id === getUserId(),
|
||||
))
|
||||
) &&
|
||||
!responsibleList[pageState.currentStep]?.group.some((v) =>
|
||||
currentUserGroup.includes(v),
|
||||
)) ||
|
||||
(!!responsibleList[pageState.currentStep]?.user?.length &&
|
||||
!!responsibleList[pageState.currentStep]?.user?.length)
|
||||
"
|
||||
:order-able="value._messengerExpansion"
|
||||
:installment-info="getInstallmentInfo()"
|
||||
|
|
@ -873,6 +922,11 @@ async function submitRejectCancel() {
|
|||
:readonly="
|
||||
data.requestDataStatus === RequestDataStatus.Canceled
|
||||
"
|
||||
:default-messenger="
|
||||
value.stepStatus[pageState.currentStep - 1]
|
||||
? undefined
|
||||
: data.defaultMessengerId
|
||||
"
|
||||
:step="{
|
||||
step: pageState.currentStep,
|
||||
requestWorkId: value.id || '',
|
||||
|
|
@ -906,6 +960,7 @@ async function submitRejectCancel() {
|
|||
/>
|
||||
<FormExpansion
|
||||
v-if="value._formExpansion"
|
||||
:request-work-id="value.id"
|
||||
:readonly="
|
||||
data.requestDataStatus === RequestDataStatus.Canceled
|
||||
"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import useOptionStore from 'src/stores/options';
|
|||
|
||||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||
import { CreatedBy } from 'src/stores/types';
|
||||
import { dateFormatJS } from 'src/utils/datetime';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
|
@ -21,6 +22,9 @@ const props = withDefaults(
|
|||
grid?: boolean;
|
||||
visibleColumns?: string[];
|
||||
hideAction?: boolean;
|
||||
hideView?: boolean;
|
||||
checkable?: boolean;
|
||||
listSameArea?: string[];
|
||||
}>(),
|
||||
{
|
||||
row: () => [],
|
||||
|
|
@ -36,9 +40,16 @@ defineEmits<{
|
|||
(e: 'rejectCancel', data: RequestData): void;
|
||||
}>();
|
||||
|
||||
function responsiblePerson(quotation: QuotationFull): CreatedBy[] | undefined {
|
||||
const selected = defineModel<RequestData[]>('selected');
|
||||
|
||||
function responsiblePerson(quotation: QuotationFull) {
|
||||
const productServiceList = quotation.productServiceList;
|
||||
const tempPerson: CreatedBy[] = [];
|
||||
const tempGroup: {
|
||||
group: string;
|
||||
id: string;
|
||||
workflowTemplateStepId: string;
|
||||
}[] = [];
|
||||
|
||||
for (const v of productServiceList) {
|
||||
const tempStep = v.service?.workflow?.step;
|
||||
|
|
@ -49,7 +60,17 @@ function responsiblePerson(quotation: QuotationFull): CreatedBy[] | undefined {
|
|||
tempPerson.push(rhs.user);
|
||||
}
|
||||
});
|
||||
return tempPerson;
|
||||
tempStep.forEach((lhs) => {
|
||||
const newGroup = lhs.responsibleGroup as unknown as {
|
||||
group: string;
|
||||
id: string;
|
||||
workflowTemplateStepId: string;
|
||||
}[];
|
||||
for (const rhs of newGroup) {
|
||||
tempGroup.push(rhs);
|
||||
}
|
||||
});
|
||||
return { user: tempPerson, group: tempGroup };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,10 +113,39 @@ function getEmployeeName(
|
|||
return (
|
||||
{
|
||||
['eng']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstNameEN} ${employee?.lastNameEN}`,
|
||||
['tha']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstName} ${employee?.lastName}`,
|
||||
['tha']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstName || employee?.firstNameEN} ${employee?.lastName || employee?.lastNameEN}`,
|
||||
}[opts?.locale || 'eng'] || '-'
|
||||
);
|
||||
}
|
||||
|
||||
function toCustomer(customer: RequestData['quotation']['customerBranch']) {
|
||||
const url = new URL(
|
||||
`/customer-management?tab=customer&id=${customer.customerId}`,
|
||||
window.location.origin,
|
||||
);
|
||||
|
||||
window.open(url.toString(), '_blank');
|
||||
}
|
||||
|
||||
function toEmployee(employee: RequestData['employee']) {
|
||||
const url = new URL(
|
||||
`/customer-management?tab=employee&id=${employee.id}`,
|
||||
window.location.origin,
|
||||
);
|
||||
|
||||
window.open(url.toString(), '_blank');
|
||||
}
|
||||
|
||||
function handleCheckAll() {
|
||||
const filteredRows = props.rows.filter((row) =>
|
||||
props.listSameArea?.includes(row.quotation.customerBranch.districtId),
|
||||
);
|
||||
if (selected.value.length === filteredRows.length) {
|
||||
selected.value = [];
|
||||
} else {
|
||||
selected.value = filteredRows;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-table
|
||||
|
|
@ -106,12 +156,36 @@ function getEmployeeName(
|
|||
card-container-class="q-col-gutter-sm"
|
||||
:rows-per-page-options="[0]"
|
||||
class="full-width"
|
||||
selection="multiple"
|
||||
v-model:selected="selected"
|
||||
:selected-rows-label="
|
||||
(n) =>
|
||||
$t('general.selected', {
|
||||
number: n,
|
||||
msg: $t('general.list'),
|
||||
})
|
||||
"
|
||||
:no-data-label="$t('general.noDataTable')"
|
||||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr
|
||||
style="background-color: hsla(var(--info-bg) / 0.07)"
|
||||
:props="props"
|
||||
>
|
||||
<q-th v-if="checkable">
|
||||
<q-checkbox
|
||||
v-if="selected.length > 0"
|
||||
:model-value="
|
||||
selected.length ===
|
||||
rows.filter((row) =>
|
||||
listSameArea?.includes(row.quotation.customerBranch.districtId),
|
||||
).length
|
||||
"
|
||||
size="sm"
|
||||
@click="handleCheckAll"
|
||||
/>
|
||||
<div v-else style="width: 35px; height: 35px"></div>
|
||||
</q-th>
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
{{ col.label && $t(col.label) }}
|
||||
</q-th>
|
||||
|
|
@ -125,9 +199,30 @@ function getEmployeeName(
|
|||
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||
>
|
||||
<q-tr
|
||||
:class="{ urgent: props.row.quotation.urgent, dark: $q.dark.isActive }"
|
||||
:class="{
|
||||
urgent: props.row.quotation.urgent,
|
||||
dark: $q.dark.isActive,
|
||||
'disabled-row':
|
||||
selected &&
|
||||
selected.length > 0 &&
|
||||
!listSameArea.includes(
|
||||
props.row.quotation.customerBranch.districtId,
|
||||
),
|
||||
}"
|
||||
class="text-center"
|
||||
>
|
||||
<q-td v-if="checkable">
|
||||
<q-checkbox
|
||||
:disable="
|
||||
selected.length > 0 &&
|
||||
!listSameArea.includes(
|
||||
props.row.quotation.customerBranch.districtId,
|
||||
)
|
||||
"
|
||||
v-model="props.selected"
|
||||
size="sm"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('order')">
|
||||
{{ props.rowIndex + 1 }}
|
||||
</q-td>
|
||||
|
|
@ -138,21 +233,50 @@ function getEmployeeName(
|
|||
</div>
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('employer')" class="text-left">
|
||||
{{
|
||||
getCustomerName(props.row, {
|
||||
noCode: true,
|
||||
locale: $i18n.locale,
|
||||
}) || '-'
|
||||
}}
|
||||
<span
|
||||
class="link"
|
||||
@click="toCustomer(props.row.quotation.customerBranch)"
|
||||
>
|
||||
{{
|
||||
getCustomerName(props.row, {
|
||||
noCode: true,
|
||||
locale: $i18n.locale,
|
||||
}) || '-'
|
||||
}}
|
||||
</span>
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('employee')" class="text-left">
|
||||
{{ getEmployeeName(props.row, { locale: $i18n.locale }) || '-' }}
|
||||
<span class="link" @click="toEmployee(props.row.employee)">
|
||||
{{ getEmployeeName(props.row, { locale: $i18n.locale }) || '-' }}
|
||||
</span>
|
||||
</q-td>
|
||||
|
||||
<q-td
|
||||
v-if="visibleColumns.includes('employeePassport')"
|
||||
class="text-left"
|
||||
>
|
||||
{{
|
||||
props.row.employee.employeePassport.length !== 0
|
||||
? props.row.employee.employeePassport[0].number
|
||||
: '-'
|
||||
}}
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('dataOffice')" class="text-left">
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
? props.row.dataOffice.nameEN
|
||||
: props.row.dataOffice.name
|
||||
}}
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('createdAt')" class="text-left">
|
||||
{{ dateFormatJS({ date: props.row.createdAt }) }}
|
||||
</q-td>
|
||||
|
||||
<q-td v-if="visibleColumns.includes('quotationCode')">
|
||||
{{ props.row.quotation.code || '-' }}
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('responsiblePerson')">
|
||||
<AvatarGroup
|
||||
<!-- <AvatarGroup
|
||||
:data="
|
||||
responsiblePerson(props.row.quotation)?.map((v) => {
|
||||
return {
|
||||
|
|
@ -168,7 +292,26 @@ function getEmployeeName(
|
|||
};
|
||||
})
|
||||
"
|
||||
/>
|
||||
/> -->
|
||||
<AvatarGroup
|
||||
:data="[
|
||||
...responsiblePerson(props.row.quotation).user.map((v) => ({
|
||||
name:
|
||||
$i18n.locale === 'eng'
|
||||
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||
: `${v.firstName} ${v.lastName}`,
|
||||
imgUrl: !v.selectedImage
|
||||
? v.gender === 'male'
|
||||
? `/no-img-man.png`
|
||||
: `/no-img-female.png`
|
||||
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
||||
})),
|
||||
...responsiblePerson(props.row.quotation).group.map((g) => ({
|
||||
name: `${$t('general.group')} ${g.group}`,
|
||||
imgUrl: '/img-group.png',
|
||||
})),
|
||||
]"
|
||||
></AvatarGroup>
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('status')">
|
||||
<BadgeComponent
|
||||
|
|
@ -215,6 +358,7 @@ function getEmployeeName(
|
|||
</q-td>
|
||||
<q-td class="text-right">
|
||||
<q-btn
|
||||
v-if="!hideView"
|
||||
:id="`btn-eye-${props.row.code}`"
|
||||
icon="mdi-eye-outline"
|
||||
size="sm"
|
||||
|
|
@ -313,22 +457,29 @@ function getEmployeeName(
|
|||
</div>
|
||||
<div class="col-8">
|
||||
<AvatarGroup
|
||||
v-if="(responsiblePerson(props.row.quotation) ?? []).length > 0"
|
||||
:data="
|
||||
responsiblePerson(props.row.quotation)?.map((v) => {
|
||||
return {
|
||||
name:
|
||||
$i18n.locale === 'eng'
|
||||
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||
: `${v.firstName} ${v.lastName}`,
|
||||
imgUrl: !v.selectedImage
|
||||
? v.gender === 'male'
|
||||
? `/no-img-man.png`
|
||||
: `/no-img-female.png`
|
||||
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
||||
};
|
||||
})
|
||||
v-if="
|
||||
(responsiblePerson(props.row.quotation).user ?? []).length >
|
||||
0 ||
|
||||
(responsiblePerson(props.row.quotation).group ?? []).length >
|
||||
0
|
||||
"
|
||||
:data="[
|
||||
...responsiblePerson(props.row.quotation).user.map((v) => ({
|
||||
name:
|
||||
$i18n.locale === 'eng'
|
||||
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||
: `${v.firstName} ${v.lastName}`,
|
||||
imgUrl: !v.selectedImage
|
||||
? v.gender === 'male'
|
||||
? `/no-img-man.png`
|
||||
: `/no-img-female.png`
|
||||
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
||||
})),
|
||||
...responsiblePerson(props.row.quotation).group.map((g) => ({
|
||||
name: `${$t('general.group')} ${g.group}`,
|
||||
imgUrl: '/img-group.png',
|
||||
})),
|
||||
]"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
|
|
@ -406,4 +557,15 @@ function getEmployeeName(
|
|||
background: var(--red-8);
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
color: hsl(var(--info-bg));
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.disabled-row {
|
||||
opacity: 0.3;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,24 @@ export const column = [
|
|||
label: 'customer.employee',
|
||||
field: 'employee',
|
||||
},
|
||||
{
|
||||
name: 'employeePassport',
|
||||
align: 'center',
|
||||
label: 'customerEmployee.form.passportNo',
|
||||
field: 'employeePassport',
|
||||
},
|
||||
{
|
||||
name: 'dataOffice',
|
||||
align: 'center',
|
||||
label: 'requestList.dataOffice',
|
||||
field: 'dataOffice',
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
align: 'center',
|
||||
label: 'general.createdAt',
|
||||
field: 'createdAt',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'quotationCode',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import useFlowStore from 'src/stores/flow';
|
|||
import { pageTabs, column, pageTabsReceive } from './constants';
|
||||
import { dialogWarningClose, isRoleInclude } from 'src/stores/utils';
|
||||
import { PaginationResult } from 'src/types';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
|
|
@ -48,6 +49,7 @@ const pageState = reactive({
|
|||
isMessenger: isRoleInclude(['messenger']),
|
||||
receiveDialog: false,
|
||||
isReceiveScan: false,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const taskOrderList = ref<TaskOrder[]>([]);
|
||||
|
|
@ -69,6 +71,8 @@ async function fetchTaskOrderList(opts?: { page?: number; pageSize?: number }) {
|
|||
pageSize: opts?.pageSize || pageSize.value,
|
||||
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
||||
userTaskStatus: pageState.currentTab as UserTaskStatus,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
} else {
|
||||
res = await taskOrderStore.getTaskOrderList({
|
||||
|
|
@ -76,6 +80,8 @@ async function fetchTaskOrderList(opts?: { page?: number; pageSize?: number }) {
|
|||
pageSize: opts?.pageSize || pageSize.value,
|
||||
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
||||
taskOrderStatus: pageState.currentTab as TaskOrderStatus | undefined,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
}
|
||||
if (res) {
|
||||
|
|
@ -157,6 +163,7 @@ watch(
|
|||
() => pageState.inputSearch,
|
||||
() => pageSize.value,
|
||||
() => pageState.statusFilter,
|
||||
() => pageState.searchDate,
|
||||
],
|
||||
() => {
|
||||
fetchTaskOrderList();
|
||||
|
|
@ -299,6 +306,10 @@ watch(
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch v-model="pageState.searchDate" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||
|
|
|
|||
|
|
@ -160,7 +160,12 @@ const emit = defineEmits<{
|
|||
</q-tooltip>
|
||||
</div>
|
||||
<div class="text-caption app-text-muted">
|
||||
{{ props.row.code || '-' }}
|
||||
{{
|
||||
(props.row.taskOrderStatus === TaskOrderStatus.Complete &&
|
||||
props.row.codeProductReceived
|
||||
? props.row.codeProductReceived
|
||||
: props.row.code) || '-'
|
||||
}}
|
||||
</div>
|
||||
</q-td>
|
||||
<q-td v-if="visibleColumns.includes('issueBranch')">
|
||||
|
|
|
|||
|
|
@ -227,6 +227,12 @@ export const productColumn = [
|
|||
label: 'taskOrder.productList',
|
||||
field: 'productList',
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
align: 'center',
|
||||
label: 'general.status',
|
||||
field: 'status',
|
||||
},
|
||||
{
|
||||
name: 'amountOfEmployee',
|
||||
align: 'center',
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ function closeAble() {
|
|||
:branch="branch"
|
||||
:institution="data.institution"
|
||||
:details="{
|
||||
code: data.code,
|
||||
code: data.codeProductReceived ?? data.code,
|
||||
name: data.taskName,
|
||||
contactName: data.contactName,
|
||||
contactTel: data.contactTel,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { baseUrl, formatNumberDecimal, commaInput } from 'src/stores/utils';
|
|||
import { precisionRound } from 'src/utils/arithmetic';
|
||||
import { useConfigStore } from 'stores/config';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
||||
import { TaskStatus } from 'src/stores/task-order/types';
|
||||
|
||||
const currentBtnOpen = ref<boolean[]>([]);
|
||||
const configStore = useConfigStore();
|
||||
|
|
@ -30,7 +32,10 @@ const props = defineProps<{
|
|||
readonly?: boolean;
|
||||
agentPrice?: boolean;
|
||||
taskList: {
|
||||
product: RequestWork['productService']['product'];
|
||||
product: RequestWork['productService']['product'] & {
|
||||
taskStatus?: TaskStatus;
|
||||
totalNotStatusComplete?: number;
|
||||
};
|
||||
list: RequestWork[];
|
||||
}[];
|
||||
creditNote?: boolean;
|
||||
|
|
@ -111,6 +116,26 @@ function calcPrice(
|
|||
|
||||
return precisionRound(priceNoVat * amount + rawVatTotal);
|
||||
}
|
||||
|
||||
function taskOrderStatus(value: TaskStatus) {
|
||||
if ([TaskStatus.Pending].includes(value)) {
|
||||
return '--blue-6-hsl';
|
||||
}
|
||||
if ([TaskStatus.InProgress, TaskStatus.Validate].includes(value)) {
|
||||
return '--orange-5-hsl';
|
||||
}
|
||||
if (
|
||||
[
|
||||
TaskStatus.Canceled,
|
||||
TaskStatus.Restart,
|
||||
TaskStatus.Redo,
|
||||
TaskStatus.Failed,
|
||||
].includes(value)
|
||||
) {
|
||||
return '--red-5-hsl';
|
||||
}
|
||||
return '--green-8-hsl';
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-expansion-item
|
||||
|
|
@ -144,7 +169,8 @@ function calcPrice(
|
|||
(v) =>
|
||||
v.name !== 'discount' &&
|
||||
v.name !== 'priceBeforeVat' &&
|
||||
v.name !== 'vat',
|
||||
v.name !== 'vat' &&
|
||||
v.name !== 'status',
|
||||
)
|
||||
: productColumn
|
||||
"
|
||||
|
|
@ -173,7 +199,10 @@ function calcPrice(
|
|||
<template
|
||||
v-slot:body="props: {
|
||||
row: {
|
||||
product: RequestWork['productService']['product'];
|
||||
product: RequestWork['productService']['product'] & {
|
||||
taskStatus?: TaskStatus;
|
||||
totalNotStatusComplete?: number;
|
||||
};
|
||||
list: RequestWork[];
|
||||
};
|
||||
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||
|
|
@ -203,6 +232,14 @@ function calcPrice(
|
|||
</q-avatar>
|
||||
{{ props.row.product.name }}
|
||||
</q-td>
|
||||
<q-td class="text-left" v-if="!creditNote">
|
||||
<BadgeComponent
|
||||
hide-icon
|
||||
:hsla-color="taskOrderStatus(props.row.product.taskStatus)"
|
||||
:title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`"
|
||||
/>
|
||||
</q-td>
|
||||
|
||||
<q-td>
|
||||
{{ props.row.list.length }}
|
||||
</q-td>
|
||||
|
|
|
|||
|
|
@ -280,7 +280,10 @@ let taskListGroup = computed(() => {
|
|||
|
||||
const cacheData = currentFormData.value.taskList.reduce<
|
||||
{
|
||||
product: RequestWork['productService']['product'];
|
||||
product: RequestWork['productService']['product'] & {
|
||||
taskStatus?: TaskStatus;
|
||||
totalNotStatusComplete?: number;
|
||||
};
|
||||
list: (RequestWork & {
|
||||
_template?: {
|
||||
id: string;
|
||||
|
|
@ -289,15 +292,15 @@ let taskListGroup = computed(() => {
|
|||
step: number;
|
||||
responsibleInstitution: (string | { group: string })[];
|
||||
} | null;
|
||||
taskStatus?: TaskStatus;
|
||||
failedComment?: string;
|
||||
failedType?: string;
|
||||
})[];
|
||||
}[]
|
||||
>((acc, curr) => {
|
||||
if (
|
||||
const isNotComplete =
|
||||
fullTaskOrder.value?.taskOrderStatus === TaskOrderStatus.Complete &&
|
||||
curr.taskStatus !== TaskStatus.Complete
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
curr.taskStatus !== TaskStatus.Complete;
|
||||
|
||||
const task = curr.requestWorkStep;
|
||||
const step = curr.step;
|
||||
|
|
@ -308,9 +311,18 @@ let taskListGroup = computed(() => {
|
|||
let exist = acc.find(
|
||||
(item) => task.requestWork.productService.productId == item.product.id,
|
||||
);
|
||||
const record = Object.assign(task.requestWork, {
|
||||
_template: getTemplateData(task.requestWork, step),
|
||||
});
|
||||
|
||||
const record = Object.assign(
|
||||
{
|
||||
...task.requestWork,
|
||||
taskStatus: curr.taskStatus,
|
||||
failedComment: curr.failedComment || '',
|
||||
failedType: curr.failedType || '',
|
||||
},
|
||||
{
|
||||
_template: getTemplateData(task.requestWork, step),
|
||||
},
|
||||
);
|
||||
|
||||
const template = getTemplateData(task.requestWork, step);
|
||||
|
||||
|
|
@ -323,10 +335,18 @@ let taskListGroup = computed(() => {
|
|||
}
|
||||
|
||||
if (exist) {
|
||||
exist.list.push(task.requestWork);
|
||||
exist.list.push(record);
|
||||
if (isNotComplete) {
|
||||
exist.product.totalNotStatusComplete =
|
||||
(exist.product.totalNotStatusComplete || undefined) + 1;
|
||||
}
|
||||
} else {
|
||||
acc.push({
|
||||
product: task.requestWork.productService.product,
|
||||
product: {
|
||||
...task.requestWork.productService.product,
|
||||
taskStatus: curr.taskStatus || TaskStatus.Pending,
|
||||
totalNotStatusComplete: isNotComplete ? 1 : undefined,
|
||||
},
|
||||
list: [record],
|
||||
});
|
||||
}
|
||||
|
|
@ -897,9 +917,14 @@ watch(
|
|||
v-model:registered-branch-id="currentFormData.registeredBranchId"
|
||||
v-model:institution-id="currentFormData.institutionId"
|
||||
v-model:task-name="currentFormData.taskName"
|
||||
v-model:code="currentFormData.code"
|
||||
v-model:contact-name="currentFormData.contactName"
|
||||
v-model:contact-tel="currentFormData.contactTel"
|
||||
:code="
|
||||
view === TaskOrderStatus.Complete &&
|
||||
currentFormData.codeProductReceived
|
||||
? currentFormData.codeProductReceived
|
||||
: currentFormData.code
|
||||
"
|
||||
:task-list-group="
|
||||
taskListGroup.length === 0 && state.mode === 'create'
|
||||
"
|
||||
|
|
@ -985,6 +1010,7 @@ watch(
|
|||
"
|
||||
/>
|
||||
<!-- TODO: blind remark, urgent -->
|
||||
{{ console.log(taskListGroup) }}
|
||||
<RemarkExpansion
|
||||
v-if="
|
||||
view === TaskOrderStatus.Pending ||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// NOTE: Library
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { QSelect, useQuasar } from 'quasar';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
// NOTE: Components
|
||||
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
||||
|
|
@ -18,13 +18,13 @@ import { columns, hslaColors } from './constants';
|
|||
import useFlowStore from 'src/stores/flow';
|
||||
import { useInvoice } from 'src/stores/payment';
|
||||
import { Invoice, PaymentDataStatus } from 'src/stores/payment/types';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const $q = useQuasar();
|
||||
const navigatorStore = useNavigator();
|
||||
const flowStore = useFlowStore();
|
||||
const invoiceStore = useInvoice();
|
||||
const { data, stats, page, pageMax, pageSize } = storeToRefs(invoiceStore);
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
|
||||
// NOTE: Variable
|
||||
const pageState = reactive({
|
||||
|
|
@ -34,6 +34,7 @@ const pageState = reactive({
|
|||
fieldSelected: [...columns.map((v) => v.name)],
|
||||
gridView: false,
|
||||
total: 0,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const fieldSelectedOption = computed(() => {
|
||||
|
|
@ -56,6 +57,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
|
|||
: undefined,
|
||||
quotationOnly: true,
|
||||
debitNoteOnly: false,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
if (ret) {
|
||||
data.value = $q.screen.xs ? [...data.value, ...ret.result] : ret.result;
|
||||
|
|
@ -89,8 +92,6 @@ function triggerView(opts: { quotationId: string }) {
|
|||
}
|
||||
|
||||
function viewDocExample(quotationId: string, codeInvoice: string) {
|
||||
console.log(codeInvoice);
|
||||
|
||||
localStorage.setItem(
|
||||
'quotation-preview',
|
||||
JSON.stringify({
|
||||
|
|
@ -124,6 +125,7 @@ watch(
|
|||
() => pageState.inputSearch,
|
||||
() => pageState.statusFilter,
|
||||
() => pageSize.value,
|
||||
() => pageState.searchDate,
|
||||
],
|
||||
() => {
|
||||
page.value = 1;
|
||||
|
|
@ -207,26 +209,50 @@ watch(
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="pageState.searchDate"
|
||||
:active="$q.screen.lt.md && pageState.statusFilter !== 'None'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="pageState.statusFilter"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{
|
||||
label: $t('general.all'),
|
||||
value: 'None',
|
||||
},
|
||||
{
|
||||
label: $t('invoice.status.PaymentWait'),
|
||||
value: PaymentDataStatus.Wait,
|
||||
},
|
||||
{
|
||||
label: $t('invoice.status.PaymentSuccess'),
|
||||
value: PaymentDataStatus.Success,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-if="$q.screen.gt.sm"
|
||||
v-model="pageState.statusFilter"
|
||||
outlined
|
||||
dense
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { pageTabs, columns, hslaColors } from './constants';
|
|||
import { CreditNoteStatus, useCreditNote } from 'src/stores/credit-note';
|
||||
import TableCreditNote from './TableCreditNote.vue';
|
||||
import { dialogWarningClose } from 'src/stores/utils';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const $q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
|
@ -46,6 +47,7 @@ const pageState = reactive({
|
|||
total: 0,
|
||||
|
||||
creditDialog: false,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const fieldSelectedOption = computed(() => {
|
||||
|
|
@ -64,6 +66,8 @@ async function getList(opts?: { page?: number; pageSize?: number }) {
|
|||
pageSize: opts?.pageSize || pageSize.value,
|
||||
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
||||
creditNoteStatus: pageState.currentTab as CreditNoteStatus | undefined,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
|
@ -133,6 +137,7 @@ watch(
|
|||
() => pageState.inputSearch,
|
||||
() => pageSize.value,
|
||||
() => pageState.statusFilter,
|
||||
() => pageState.searchDate,
|
||||
],
|
||||
() => {
|
||||
getList();
|
||||
|
|
@ -228,6 +233,10 @@ watch(
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch v-model="pageState.searchDate" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { storeToRefs } from 'pinia';
|
|||
import { QTableSlots } from 'quasar';
|
||||
import { CreditNote, useCreditNote } from 'src/stores/credit-note';
|
||||
|
||||
import { columns } from './constants.ts';
|
||||
import { columns } from './constants';
|
||||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||
|
||||
const creditNote = useCreditNote();
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@ function calcPricePerUnit(product: RequestWork['productService']['product']) {
|
|||
function calcPrice(
|
||||
product: RequestWork['productService']['product'],
|
||||
amount: number,
|
||||
vat: number = 0,
|
||||
) {
|
||||
const pricePerUnit = agentPrice.value ? product.agentPrice : product.price;
|
||||
|
||||
|
|
@ -256,7 +257,8 @@ function calcPrice(
|
|||
: pricePerUnit;
|
||||
const priceDiscountNoVat = priceNoVat * amount - 0;
|
||||
|
||||
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07);
|
||||
const rawVatTotal =
|
||||
vat === 0 ? 0 : priceDiscountNoVat * (config.value?.vat || 0.07);
|
||||
|
||||
return precisionRound(priceNoVat * amount + rawVatTotal);
|
||||
}
|
||||
|
|
@ -346,7 +348,7 @@ function closeAble() {
|
|||
<td style="text-align: center">
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
calcPrice(v.product.product, v.list.length),
|
||||
calcPrice(v.product.product, v.list.length, v.product.vat),
|
||||
2,
|
||||
)
|
||||
}}
|
||||
|
|
@ -431,7 +433,7 @@ function closeAble() {
|
|||
class="column set-width bg-color full-height"
|
||||
style="padding: 12px"
|
||||
>
|
||||
({{ ThaiBahtText(summaryPrice.finalPrice) }})
|
||||
({{ ThaiBahtText(precisionRound(summaryPrice.finalPrice)) }})
|
||||
</div>
|
||||
<div
|
||||
class="row text-right border-5 items-center"
|
||||
|
|
@ -494,7 +496,7 @@ function closeAble() {
|
|||
details?.worker.map(
|
||||
(v, i) =>
|
||||
`${i + 1}. ` +
|
||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
||||
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||
) || [],
|
||||
},
|
||||
}) || '-'
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { pageTabs, columns, hslaColors } from './constants';
|
|||
import { DebitNoteStatus, useDebitNote } from 'src/stores/debit-note';
|
||||
import { dialogWarningClose } from 'src/stores/utils';
|
||||
import { useQuasar } from 'quasar';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const $q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
|
@ -46,6 +47,7 @@ const pageState = reactive({
|
|||
total: 0,
|
||||
|
||||
debitDialog: false,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const fieldSelectedOption = computed(() => {
|
||||
|
|
@ -68,6 +70,8 @@ async function getList(opts?: { page?: number; pageSize?: number }) {
|
|||
? undefined
|
||||
: pageState.currentTab) as DebitNoteStatus,
|
||||
includeRegisteredBranch: true,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
|
@ -149,6 +153,7 @@ watch(
|
|||
() => pageState.inputSearch,
|
||||
() => pageSize.value,
|
||||
() => pageState.statusFilter,
|
||||
() => pageState.searchDate,
|
||||
],
|
||||
() => getList(),
|
||||
);
|
||||
|
|
@ -256,6 +261,10 @@ watch(
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch v-model="pageState.searchDate" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { storeToRefs } from 'pinia';
|
|||
import { QTableSlots } from 'quasar';
|
||||
import { DebitNote, useDebitNote } from 'src/stores/debit-note';
|
||||
|
||||
import { columns } from './constants.ts';
|
||||
import { columns } from './constants';
|
||||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||
|
||||
const debitNote = useDebitNote();
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ function print() {
|
|||
details?.worker.map(
|
||||
(v, i) =>
|
||||
`${i + 1}. ` +
|
||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
||||
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||
) || [],
|
||||
},
|
||||
}) || '-'
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import { columns, hslaColors } from './constants';
|
|||
import useFlowStore from 'src/stores/flow';
|
||||
import { usePayment, useReceipt } from 'src/stores/payment';
|
||||
import { PaymentDataStatus } from 'src/stores/payment/types';
|
||||
import { QSelect, useQuasar } from 'quasar';
|
||||
import { useQuasar } from 'quasar';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
||||
const $q = useQuasar();
|
||||
const navigatorStore = useNavigator();
|
||||
|
|
@ -26,7 +27,6 @@ const receiptStore = useReceipt();
|
|||
const { data, page, pageMax, pageSize } = storeToRefs(receiptStore);
|
||||
|
||||
// NOTE: Variable
|
||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
||||
|
||||
const pageState = reactive({
|
||||
hideStat: false,
|
||||
|
|
@ -35,6 +35,7 @@ const pageState = reactive({
|
|||
fieldSelected: [...columns.map((v) => v.name)],
|
||||
gridView: false,
|
||||
total: 0,
|
||||
searchDate: [],
|
||||
});
|
||||
|
||||
const fieldSelectedOption = computed(() => {
|
||||
|
|
@ -49,6 +50,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
|
|||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
query: pageState.inputSearch,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
});
|
||||
if (ret) {
|
||||
data.value = $q.screen.xs ? [...data.value, ...ret.result] : ret.result;
|
||||
|
|
@ -95,6 +98,7 @@ watch(
|
|||
() => pageState.inputSearch,
|
||||
() => pageState.statusFilter,
|
||||
() => pageSize.value,
|
||||
() => pageState.searchDate,
|
||||
],
|
||||
() => {
|
||||
page.value = 1;
|
||||
|
|
@ -172,25 +176,43 @@ watch(
|
|||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
||||
<span class="row">
|
||||
<q-separator vertical />
|
||||
<q-btn
|
||||
icon="mdi-filter-variant"
|
||||
unelevated
|
||||
class="q-ml-sm"
|
||||
padding="4px"
|
||||
size="sm"
|
||||
rounded
|
||||
@click="refFilter?.showPopup"
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch
|
||||
v-model="pageState.searchDate"
|
||||
:active="$q.screen.lt.md && pageState.statusFilter !== 'None'"
|
||||
>
|
||||
<div
|
||||
v-if="$q.screen.lt.md"
|
||||
class="q-mt-sm text-weight-medium"
|
||||
>
|
||||
{{ $t('general.status') }}
|
||||
</div>
|
||||
<q-select
|
||||
v-if="$q.screen.lt.md"
|
||||
ref="refFilter"
|
||||
v-model="pageState.statusFilter"
|
||||
outlined
|
||||
dense
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
map-options
|
||||
emit-value
|
||||
:for="'field-select-status'"
|
||||
:options="[
|
||||
{
|
||||
label: $t('general.all'),
|
||||
value: 'None',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</span>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-5" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
v-if="$q.screen.gt.sm"
|
||||
ref="refFilter"
|
||||
v-model="pageState.statusFilter"
|
||||
outlined
|
||||
|
|
|
|||
9
src/quasar.d.ts
vendored
9
src/quasar.d.ts
vendored
|
|
@ -1,9 +0,0 @@
|
|||
/* eslint-disable */
|
||||
|
||||
// Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package
|
||||
// Removing this would break `quasar/wrappers` imports as those typings are declared
|
||||
// into `@quasar/app-vite`
|
||||
// As a side effect, since `@quasar/app-vite` reference `quasar` to augment it,
|
||||
// this declaration also apply `quasar` own
|
||||
// augmentations (eg. adds `$q` into Vue component context)
|
||||
/// <reference types="@quasar/app-vite" />
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { route } from 'quasar/wrappers';
|
||||
import { defineRouter } from '#q-app/wrappers';
|
||||
import {
|
||||
createMemoryHistory,
|
||||
createRouter,
|
||||
|
|
@ -17,7 +17,7 @@ import routes from './routes';
|
|||
* with the Router instance.
|
||||
*/
|
||||
|
||||
export default route(function (/* { store, ssrContext } */) {
|
||||
export default defineRouter(function (/* { store, ssrContext } */) {
|
||||
const createHistory = process.env.SERVER
|
||||
? createMemoryHistory
|
||||
: process.env.VUE_ROUTER_MODE === 'history'
|
||||
|
|
|
|||
|
|
@ -155,6 +155,16 @@ const routes: RouteRecordRaw[] = [
|
|||
name: 'ManualView',
|
||||
component: () => import('pages/00_manual/ViewPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/troubleshooting',
|
||||
name: 'Troubleshooting',
|
||||
component: () => import('pages/00_manual/MainPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/troubleshooting/:category/:page',
|
||||
name: 'TroubleshootingView',
|
||||
component: () => import('pages/00_manual/ViewPage.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
|||
10
src/shims-vue.d.ts
vendored
10
src/shims-vue.d.ts
vendored
|
|
@ -1,10 +0,0 @@
|
|||
/* eslint-disable */
|
||||
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
// Mocks all files ending in `.vue` showing them as plain Vue instances
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
|
@ -102,12 +102,25 @@ const useAddressStore = defineStore('api-address', () => {
|
|||
return subDistrict.value[districtId];
|
||||
}
|
||||
|
||||
async function listSameOfficeArea(districtId: string) {
|
||||
const res = await api.post<string[]>(
|
||||
`/employment-office/list-same-office-area`,
|
||||
{ districtId: districtId },
|
||||
);
|
||||
|
||||
if (!res) return false;
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
return {
|
||||
fetchOffice,
|
||||
fetchOfficeById,
|
||||
fetchProvince,
|
||||
fetchDistrictByProvinceId,
|
||||
fetchSubDistrictByProvinceId,
|
||||
|
||||
listSameOfficeArea,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ const useBranchStore = defineStore('api-branch', () => {
|
|||
withHead?: boolean;
|
||||
activeOnly?: boolean;
|
||||
headOfficeId?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
},
|
||||
Data extends Pagination<Branch[]>,
|
||||
>(opts?: Options): Promise<Data | false> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { api } from 'src/boot/axios';
|
||||
import { ref } from 'vue';
|
||||
import type { AppConfig } from './types';
|
||||
|
||||
export const useConfigStore = defineStore('config-store', () => {
|
||||
const data = ref<AppConfig>();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
type AppConfig = {
|
||||
export type AppConfig = {
|
||||
vat: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@ import {
|
|||
CreditNoteStatus as Status,
|
||||
CreditNotePayload as Payload,
|
||||
CreditNotePaybackStatus,
|
||||
} from './types.ts';
|
||||
} from './types';
|
||||
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';
|
||||
import { api } from 'src/boot/axios';
|
||||
import { PaginationResult } from 'src/types';
|
||||
import { manageAttachment, manageFile } from '../utils';
|
||||
|
||||
const ENDPOINT = 'credit-note';
|
||||
|
||||
export * from './types.ts';
|
||||
export * from './types';
|
||||
|
||||
export async function getCreditNoteStats() {
|
||||
const res = await api.get<Record<Status, number>>(`/${ENDPOINT}/stats`);
|
||||
|
|
@ -28,6 +28,8 @@ export async function getCreditNoteList(params?: {
|
|||
pageSize?: number;
|
||||
query?: string;
|
||||
creditNoteStatus?: Status;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}) {
|
||||
const res = await api.get<PaginationResult<Data>>(`/${ENDPOINT}`, {
|
||||
params,
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ const useCustomerStore = defineStore('api-customer', () => {
|
|||
includeBranch?: boolean;
|
||||
status?: 'CREATED' | 'ACTIVE' | 'INACTIVE';
|
||||
customerType?: CustomerType;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
},
|
||||
Data extends Pagination<
|
||||
(Customer &
|
||||
|
|
@ -500,6 +502,6 @@ const useCustomerStore = defineStore('api-customer', () => {
|
|||
};
|
||||
});
|
||||
|
||||
export * from './types.ts';
|
||||
export * from './types';
|
||||
|
||||
export default useCustomerStore;
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ import {
|
|||
DebitNote as Data,
|
||||
DebitNoteStatus as Status,
|
||||
DebitNotePayload as Payload,
|
||||
} from './types.ts';
|
||||
} from './types';
|
||||
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';
|
||||
import { api } from 'src/boot/axios';
|
||||
import { PaginationResult } from 'src/types';
|
||||
import { manageAttachment, manageFile } from '../utils';
|
||||
|
||||
const ENDPOINT = 'debit-note';
|
||||
|
||||
export * from './types.ts';
|
||||
export * from './types';
|
||||
|
||||
export async function getDebitNoteStats() {
|
||||
const res = await api.get<Record<Status, number>>(`/${ENDPOINT}/stats`);
|
||||
|
|
@ -28,6 +28,8 @@ export async function getDebitNoteList(params?: {
|
|||
query?: string;
|
||||
status?: Status;
|
||||
includeRegisteredBranch?: boolean;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}) {
|
||||
const res = await api.get<PaginationResult<Data>>(`/${ENDPOINT}`, {
|
||||
params,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ const useEmployeeStore = defineStore('api-employee', () => {
|
|||
customerId?: string;
|
||||
customerBranchId?: string;
|
||||
activeOnly?: boolean;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
payload?: { passport?: string[] };
|
||||
}) {
|
||||
const { payload, ...params } = opts || {};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ export const useInstitution = defineStore('institution-store', () => {
|
|||
group?: string;
|
||||
status?: Status;
|
||||
payload?: { group?: string[] };
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
activeOnly?: boolean;
|
||||
}) {
|
||||
const { payload, ...params } = opts || {};
|
||||
|
||||
|
|
@ -72,18 +75,67 @@ export const useInstitution = defineStore('institution-store', () => {
|
|||
}
|
||||
}
|
||||
|
||||
if (res.data.bank && data.bank.length > 0) {
|
||||
for (let i = 0; i < data.bank?.length; i++) {
|
||||
if (data.bank[i].bankQr) {
|
||||
await api
|
||||
.put(
|
||||
`/institution/${res.data.id}/bank-qr/${res.data.bank[i].id}`,
|
||||
data.bank[i].bankQr,
|
||||
{
|
||||
headers: { 'Content-Type': data.bank[i].bankQr?.type },
|
||||
onUploadProgress: (e) => console.log(e),
|
||||
},
|
||||
)
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.status < 400) {
|
||||
return res.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function editInstitution(data: InstitutionPayload & { id: string }) {
|
||||
async function editInstitution(
|
||||
data: InstitutionPayload & { id: string },
|
||||
opts?: { indexDeleteQrCodeBank?: number[] },
|
||||
) {
|
||||
const res = await api.put(`/institution/${data.id}`, {
|
||||
...data,
|
||||
id: undefined,
|
||||
group: undefined,
|
||||
});
|
||||
|
||||
if (!!res.data.bank && !!data.bank.length) {
|
||||
for (let i = 0; i < data.bank?.length; i++) {
|
||||
if (data.bank[i].bankQr) {
|
||||
console.log(i);
|
||||
console.log(data.bank[i].bankQr);
|
||||
await api
|
||||
.put(
|
||||
`/institution/${res.data.id}/bank-qr/${res.data.bank[i].id}`,
|
||||
data.bank[i].bankQr,
|
||||
{
|
||||
headers: { 'Content-Type': data.bank[i].bankQr?.type },
|
||||
onUploadProgress: (e) => console.log(e),
|
||||
},
|
||||
)
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.indexDeleteQrCodeBank && opts.indexDeleteQrCodeBank.length > 0) {
|
||||
console.log('delete');
|
||||
opts.indexDeleteQrCodeBank.forEach(async (i) => {
|
||||
await api
|
||||
.delete(`/institution/${res.data.id}/bank-qr/${res.data.bank[i].id}`)
|
||||
.catch((e) => console.error(e));
|
||||
});
|
||||
}
|
||||
|
||||
if (res.status < 400) {
|
||||
return res.data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { District, Province, SubDistrict } from '../address';
|
||||
import { BankBook } from '../branch/types';
|
||||
import { Status } from '../types';
|
||||
|
||||
export type Institution = {
|
||||
|
|
@ -25,6 +26,11 @@ export type Institution = {
|
|||
districtId: string;
|
||||
provinceId: string;
|
||||
status: Status;
|
||||
|
||||
contactName?: string | null;
|
||||
contactEmail?: string | null;
|
||||
contactTel?: string | null;
|
||||
bank: BankBook[];
|
||||
};
|
||||
|
||||
export type InstitutionPayload = {
|
||||
|
|
@ -34,6 +40,11 @@ export type InstitutionPayload = {
|
|||
group?: string;
|
||||
selectedImage?: string | null;
|
||||
|
||||
contactName?: string | null;
|
||||
contactEmail?: string | null;
|
||||
contactTel?: string | null;
|
||||
bank: BankBook[];
|
||||
|
||||
addressEN: string;
|
||||
address: string;
|
||||
soi?: string | null;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import { getToken } from 'src/services/keycloak';
|
|||
import { Manual } from './types';
|
||||
import { baseUrl } from '../utils';
|
||||
|
||||
const ENDPOINT = 'manual';
|
||||
const MANUAL_ENDPOINT = 'manual';
|
||||
const TROUBLESHOOTING_ENDPOINT = 'troubleshooting';
|
||||
|
||||
export async function getManual() {
|
||||
const res = await api.get<Manual[]>(`/${ENDPOINT}`);
|
||||
const res = await api.get<Manual[]>(`/${MANUAL_ENDPOINT}`);
|
||||
if (res.status < 400) {
|
||||
return res.data;
|
||||
}
|
||||
|
|
@ -20,7 +21,28 @@ export async function getManualByPage(opt: {
|
|||
pageName: string;
|
||||
}) {
|
||||
const res = await fetch(
|
||||
`${baseUrl}/${ENDPOINT}/${opt.category}/page/${opt.pageName}`,
|
||||
`${baseUrl}/${MANUAL_ENDPOINT}/${opt.category}/page/${opt.pageName}`,
|
||||
);
|
||||
if (res.status < 400) {
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getTroubleshooting() {
|
||||
const res = await api.get<Manual[]>(`/${TROUBLESHOOTING_ENDPOINT}`);
|
||||
if (res.status < 400) {
|
||||
return res.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getTroubleshootingByPage(opt: {
|
||||
category: string;
|
||||
pageName: string;
|
||||
}) {
|
||||
const res = await fetch(
|
||||
`${baseUrl}/${TROUBLESHOOTING_ENDPOINT}/${opt.category}/page/${opt.pageName}`,
|
||||
);
|
||||
if (res.status < 400) {
|
||||
return res;
|
||||
|
|
@ -30,11 +52,15 @@ export async function getManualByPage(opt: {
|
|||
|
||||
export const useManualStore = defineStore('manual-store', () => {
|
||||
const dataManual = ref<Manual[]>([]);
|
||||
const dataTroubleshooting = ref<Manual[]>([]);
|
||||
|
||||
return {
|
||||
getManual,
|
||||
getManualByPage,
|
||||
getTroubleshooting,
|
||||
getTroubleshootingByPage,
|
||||
|
||||
dataManual,
|
||||
dataTroubleshooting,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ const useOptionStore = defineStore('optionStore', () => {
|
|||
}
|
||||
|
||||
return {
|
||||
rawOption,
|
||||
globalOption,
|
||||
mapOption,
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue