Compare commits
367 commits
version-0.
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65dcd138db | ||
| e6d06b39da | |||
| 3cab6cc0e5 | |||
|
|
9994366c74 | ||
|
|
f4db5ad855 | ||
|
|
15a812b50e | ||
|
|
f7a8416e7a | ||
|
|
79d6482caa | ||
|
|
75d5c7dfe8 | ||
|
|
637eeab3c2 | ||
| 2b1e3b12a4 | |||
|
|
a1ed625d32 | ||
|
|
59a3f964c4 | ||
|
|
2afb5ea7e9 | ||
| aaf776639d | |||
| 04c463a717 | |||
| 8e65a1c5a2 | |||
| 21fc2d5d96 | |||
| 73f43c2a29 | |||
| 16ea66484d | |||
| 90f31a0c87 | |||
| d1785faed2 | |||
| f68e8cf675 | |||
| cd4b087fec | |||
|
|
8a0340f588 | ||
| 2b9c8aa613 | |||
|
|
eebd585554 | ||
|
|
db5262da42 | ||
|
|
72b0e89642 | ||
|
|
2320883cb6 | ||
|
|
0e6bee7b62 | ||
| 5c867a496d | |||
|
|
d06c26c3c8 | ||
|
|
c4f088c5cb | ||
| 6cf8cf28aa | |||
|
|
1249f67a0f | ||
|
|
05d38b1ab3 | ||
|
|
d09484a52a | ||
|
|
d8d02a679d | ||
|
|
11047e569d | ||
|
|
f3b5b25bf3 | ||
|
|
e817e8fd05 | ||
|
|
18e5517325 | ||
|
|
5e13864d4a | ||
| 61dca12e5a | |||
| aa908f0c3d | |||
| 80056f8e0b | |||
|
|
02bb682150 | ||
| d2acd6ba4c | |||
| d53eb15a88 | |||
|
|
35e23aa291 | ||
|
|
00f9b5f4c4 | ||
|
|
7846950802 | ||
|
|
ef8e294ae4 | ||
| 016a54e45e | |||
|
|
4e887fdff8 | ||
| ded56d103b | |||
|
|
0d708405f6 | ||
| 2099031fa8 | |||
|
|
fd12f32ab0 | ||
| f10103b5d0 | |||
|
|
d6e366f788 | ||
| 52c384f0fb | |||
|
|
fcafaeebc0 | ||
|
|
0e57a3daf6 | ||
|
|
0ad017309f | ||
| b9cfb6274b | |||
|
|
5becbae369 | ||
| 7c3a9818c2 | |||
| 56f0a86845 | |||
| 492f341e68 | |||
|
|
49897ff007 | ||
| c430b6082e | |||
|
|
e6f8870cdf | ||
|
|
67cde37e34 | ||
|
|
763ac07be7 | ||
|
|
c07efa7318 | ||
|
|
507141dca5 | ||
| 73b2d52fb0 | |||
|
|
e6ecd39d24 | ||
|
|
c0a2d3769d | ||
|
|
05f7c886d6 | ||
|
|
93c54c0dd1 | ||
|
|
ef4c84341c | ||
| 09b51d601e | |||
| c29e1d4ec5 | |||
|
|
d89925dee9 | ||
|
|
3e24a46f66 | ||
|
|
764d9bab3f | ||
|
|
e5b2114984 | ||
|
|
37c9f5fcd5 | ||
|
|
67a69b85e0 | ||
|
|
eb88cc4269 | ||
|
|
ec780f2018 | ||
|
|
31b4daf42b | ||
|
|
5fad663a6e | ||
|
|
d4a9be9236 | ||
|
|
e33191dcd4 | ||
|
|
fd28f36876 | ||
|
|
a05c1e7004 | ||
|
|
db2a094471 | ||
|
|
9aba48401a | ||
|
|
a3c51f5f52 | ||
|
|
d44850a9ae | ||
|
|
b86891c8c2 | ||
|
|
473e272328 | ||
|
|
02d02cf3a1 | ||
|
|
044a530b8d | ||
|
|
b21949712b | ||
|
|
42e545dd66 | ||
|
|
263c703e69 | ||
|
|
968aa04aa9 | ||
|
|
8ca3f784f1 | ||
|
|
d60f858582 | ||
|
|
4ec3506e62 | ||
|
|
f3342dfbda | ||
|
|
9b56896695 | ||
|
|
7d4b38369c | ||
|
|
b977f86de9 | ||
|
|
da52bfbcbd | ||
|
|
cdb38e301e | ||
|
|
7f56a6219a | ||
|
|
642dec8de9 | ||
|
|
1360aca7e9 | ||
|
|
dbca22f639 | ||
|
|
b5abf693c2 | ||
|
|
8ef2ca2e96 | ||
|
|
ad715b20a2 | ||
|
|
915ce6f70b | ||
|
|
12b49a2a07 | ||
|
|
40e6d1ba1c | ||
|
|
22e11cf699 | ||
|
|
af1f74bdda | ||
|
|
ed55d07e38 | ||
|
|
ac50ef1c7c | ||
|
|
d4f021d0e6 | ||
|
|
3ddea74b73 | ||
|
|
2ac31c2e4c | ||
|
|
e3f86136e7 | ||
|
|
577da39cf0 | ||
|
|
30d2126161 | ||
|
|
b86c0a1e7a | ||
|
|
d59642bcb3 | ||
|
|
0e5378455b | ||
|
|
0c59ef89ca | ||
|
|
052722eb14 | ||
|
|
db7f96fc02 | ||
|
|
04765a656a | ||
|
|
c3989768ed | ||
|
|
e19d3f05f1 | ||
|
|
fe2860d818 | ||
|
|
25216de820 | ||
|
|
76cfb5fcec | ||
|
|
7d1a32efb4 | ||
|
|
9999a49fa0 | ||
|
|
6b55701afb | ||
|
|
fc7a94d7a2 | ||
|
|
2ba4758e50 | ||
|
|
5bed71053e | ||
|
|
bc53399153 | ||
|
|
059c6d3afc | ||
|
|
6117867aba | ||
|
|
7d425332c3 | ||
|
|
9539adee36 | ||
|
|
14487ed849 | ||
|
|
4a195494d6 | ||
|
|
5e155cfb0c | ||
|
|
78a59e277f | ||
|
|
844cf176df | ||
|
|
a89de83fe9 | ||
|
|
2b310c667d | ||
|
|
7679c076a7 | ||
|
|
088f829146 | ||
|
|
b9f1d04105 | ||
|
|
34f5b6474b | ||
|
|
84591ae719 | ||
|
|
8b2e3f76c4 | ||
|
|
1e34f18366 | ||
|
|
f08c83c98b | ||
|
|
c481266654 | ||
|
|
a59e0c5157 | ||
|
|
a5791a1b54 | ||
|
|
f646b3c9ba | ||
|
|
9dcec6b4c6 | ||
|
|
03adabeabd | ||
|
|
942449e373 | ||
|
|
dd09a8cb23 | ||
|
|
33e040c21a | ||
|
|
ca57f4790c | ||
|
|
e957672c91 | ||
|
|
436bfa41bb | ||
|
|
d4cab27aaf | ||
|
|
f286f6a16e | ||
|
|
f516212b6c | ||
|
|
011c65dcf8 | ||
|
|
3c4573192f | ||
|
|
48a1800b54 | ||
|
|
963ed11073 | ||
|
|
8b933455d1 | ||
|
|
e9d995fa3e | ||
|
|
2269038e11 | ||
|
|
d880e1a1c5 | ||
|
|
5e76c4d50d | ||
|
|
2d7b0189ee | ||
|
|
c991e9e03f | ||
|
|
aaa448fa0c | ||
|
|
58231aa936 | ||
|
|
701b90d89a | ||
|
|
8799799214 | ||
|
|
57660d7991 | ||
|
|
9ab61d8ded | ||
|
|
09f368f516 | ||
|
|
d831cd0799 | ||
|
|
860b0b8f47 | ||
|
|
41d02273ee | ||
|
|
19ee1040d4 | ||
|
|
5c01882a34 | ||
|
|
5edff6a5a8 | ||
|
|
1b475933da | ||
|
|
40942fc420 | ||
|
|
060b5980dd | ||
|
|
04c47b2700 | ||
|
|
df9430af24 | ||
|
|
ccf1a55052 | ||
|
|
a74c4648c6 | ||
|
|
62ac7503a1 | ||
|
|
3f15ce3ca6 | ||
|
|
0916ce7af2 | ||
|
|
105e91a655 | ||
|
|
de281ea79f | ||
|
|
cd86e7718a | ||
|
|
0f252b3080 | ||
|
|
80c8a0d8b4 | ||
|
|
0150f80ba2 | ||
|
|
1efa90816a | ||
|
|
543c28e162 | ||
|
|
bcd54813d1 | ||
|
|
2b78abcd3b | ||
|
|
4d023a7c7c | ||
|
|
70e9755952 | ||
|
|
9a57056ffa | ||
|
|
8354b6b40a | ||
|
|
4d8eebdd04 | ||
|
|
5ec924fe14 | ||
|
|
0871a8b899 | ||
|
|
7a2be56ef4 | ||
|
|
c4c4b76973 | ||
|
|
2192041e35 | ||
|
|
c1ffbef565 | ||
|
|
46de2412df | ||
|
|
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 | ||
|
|
1101fa68d8 | ||
|
|
4e61762130 | ||
|
|
4a88a7fc55 | ||
|
|
3ff6715528 | ||
|
|
6ee9e67633 | ||
|
|
0150de9661 | ||
|
|
582ac9256a | ||
|
|
962f95c2bd | ||
|
|
53b9b392fe |
|
|
@ -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
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
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',
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
public/img-group.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
|
|
@ -1,14 +0,0 @@
|
|||
[
|
||||
{
|
||||
"label": "หน้าแรก",
|
||||
"labelEN": "Home Page",
|
||||
"category": "jws",
|
||||
"page": [
|
||||
{
|
||||
"name": "chapter-01-main",
|
||||
"label": "หลัก",
|
||||
"labelEN": "Main"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 7.4 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,20 +177,21 @@
|
|||
{ "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": [
|
||||
{
|
||||
"label": "Mr",
|
||||
"label": "MR",
|
||||
"value": "mr"
|
||||
},
|
||||
{
|
||||
"label": "Mrs",
|
||||
"label": "MRS",
|
||||
"value": "mrs"
|
||||
},
|
||||
{
|
||||
"label": "Miss",
|
||||
"label": "MISS",
|
||||
"value": "miss"
|
||||
}
|
||||
],
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -89,15 +89,7 @@ defineProps<{
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: hsla(0 0% 0% / 0.1);
|
||||
margin-bottom: var(--size-2);
|
||||
"
|
||||
/>
|
||||
<q-separator />
|
||||
<slot name="data"></slot>
|
||||
<template v-if="!$slots.data">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -159,42 +159,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
]"
|
||||
for="input-name-en"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
v-if="
|
||||
typeBranch !== 'headOffice' &&
|
||||
isRoleInclude(['head_of_admin', 'head_of_account'])
|
||||
"
|
||||
outlined
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
class="col-2"
|
||||
dense
|
||||
for="input-branch-status"
|
||||
:readonly="readonly || isRoleInclude(['head_of_account'])"
|
||||
:options="['Virtual', 'Branch']"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:label="$t('general.branchStatus')"
|
||||
:model-value="virtual ? 'Virtual' : 'Branch'"
|
||||
@update:model-value="(v) => (virtual = v === 'Virtual')"
|
||||
:rules="[(val) => val && val.length > 0]"
|
||||
:error-message="$t('form.error.required')"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
|
|
|
|||
|
|
@ -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 && !readonly"
|
||||
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="Prefix"
|
||||
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>
|
||||
|
||||
|
|
@ -326,16 +253,13 @@ watch(
|
|||
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'),
|
||||
]
|
||||
"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(v: string) =>
|
||||
!v ||
|
||||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
|
||||
$t('form.error.invalid'),
|
||||
]"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="readonly ? email || '-' : email"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
|
||||
|
|
@ -351,39 +275,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"
|
||||
|
|
@ -456,72 +357,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>
|
||||
|
|
|
|||
1656
src/components/03_customer-management/DialogEmployee.vue
Normal file
1772
src/components/03_customer-management/DrawerEmployee.vue
Normal file
|
|
@ -268,6 +268,7 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
|
||||
<div class="col-md col-6">
|
||||
<DatePicker
|
||||
:label="$t('customerEmployee.formHealthCheck.coverageStartDate')"
|
||||
v-model="checkup.coverageStartDate"
|
||||
:id="`${prefixId}-input-coverage-start-date`"
|
||||
:readonly="readonly || checkup.statusSave"
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import useOptionStore from 'stores/options';
|
|||
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
|
||||
import { dateFormat } from 'src/utils/datetime';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
const { locale } = useI18n();
|
||||
|
||||
|
|
@ -18,8 +20,8 @@ const issuePlace = defineModel<string>('issuePlace');
|
|||
const issueCountry = defineModel<string>('issueCountry');
|
||||
const issueDate = defineModel<Date | null | string>('issueDate');
|
||||
const type = defineModel<string>('type');
|
||||
const expireDate = defineModel<Date>('expireDate');
|
||||
const birthDate = defineModel<Date>('birthDate');
|
||||
const expireDate = defineModel<Date | string>('expireDate');
|
||||
const birthDate = defineModel<Date | string>('birthDate');
|
||||
const workerStatus = defineModel<string>('workerStatus');
|
||||
const nationality = defineModel<string>('nationality');
|
||||
const gender = defineModel<string>('gender');
|
||||
|
|
@ -32,6 +34,8 @@ const firstName = defineModel<string>('firstName');
|
|||
const namePrefix = defineModel<string>('namePrefix');
|
||||
const passportNumber = defineModel<string>('passportNumber');
|
||||
|
||||
const file = defineModel<File>('file');
|
||||
|
||||
const passportValidator = /[a-zA-Z]{1}[a-zA-Z0-9]{1}[0-9]{5,7}$/;
|
||||
|
||||
const genderOptions = ref<Record<string, unknown>[]>([]);
|
||||
|
|
@ -103,7 +107,7 @@ onMounted(() => {
|
|||
'label',
|
||||
);
|
||||
passportIssuingCountryFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.nationality),
|
||||
ref(optionStore.rawOption?.eng.nationality),
|
||||
passportIssuingCountryOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -121,13 +125,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 +156,7 @@ watch(
|
|||
);
|
||||
|
||||
passportIssuingCountryFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.nationality),
|
||||
ref(optionStore.rawOption?.eng.nationality),
|
||||
passportIssuingCountryOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -164,19 +168,43 @@ 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',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function browse() {
|
||||
inputFile?.click();
|
||||
}
|
||||
|
||||
const inputFile = (() => {
|
||||
const _element = document.createElement('input');
|
||||
_element.type = 'file';
|
||||
_element.accept = 'image/jpeg,image/png';
|
||||
_element.addEventListener('change', change);
|
||||
return _element;
|
||||
})();
|
||||
|
||||
async function change(e: Event) {
|
||||
const _element = e.target as HTMLInputElement | null;
|
||||
const _file = _element?.files?.[0];
|
||||
|
||||
if (_file) {
|
||||
const newFileName = `passport-${dateFormat(new Date().toISOString())}-${_file.name}`;
|
||||
const renamedFile = new File([_file], newFileName, { type: _file.type });
|
||||
|
||||
file.value = renamedFile;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => namePrefix.value,
|
||||
(v) => {
|
||||
|
|
@ -221,20 +249,14 @@ watch(
|
|||
</div>
|
||||
|
||||
<div class="q-col-gutter-sm" :class="{ row: $q.screen.gt.sm }">
|
||||
<div
|
||||
class="col row justify-center q-col-gutter-sml"
|
||||
style="max-height: 50%"
|
||||
v-if="!ocr"
|
||||
>
|
||||
<q-avatar
|
||||
style="border: 1px dashed; border-color: black"
|
||||
square
|
||||
size="100px"
|
||||
font-size="50px"
|
||||
color="grey-4"
|
||||
text-color="grey"
|
||||
icon="mdi-image-outline"
|
||||
/>
|
||||
<div v-if="!ocr">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="mdi-upload-box-outline"
|
||||
@click="() => browse()"
|
||||
:disable="readonly"
|
||||
></q-btn>
|
||||
</div>
|
||||
<div
|
||||
class="row q-col-gutter-sm"
|
||||
|
|
@ -258,7 +280,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="
|
||||
|
|
|
|||
|
|
@ -28,20 +28,22 @@ const arrivalAt = defineModel<string>('arrivalAt');
|
|||
const arrivalTMNo = defineModel<string>('arrivalTmNo');
|
||||
const arrivalTM = defineModel<string>('arrivalTm');
|
||||
const mrz = defineModel<string>('mrz');
|
||||
const entryCount = defineModel<number>('entryCount');
|
||||
const entryCount = defineModel<number | string>('entryCount');
|
||||
const issuePlace = defineModel<string>('issuePlace');
|
||||
const issueCountry = defineModel<string>('issueCountry');
|
||||
const issueDate = defineModel<Date | null | string>('visaIssueDate');
|
||||
const type = defineModel<string>('visaType');
|
||||
const expireDate = defineModel<Date>('expireDate');
|
||||
const type = defineModel<string>('type');
|
||||
const expireDate = defineModel<Date | string>('expireDate');
|
||||
const remark = defineModel<string>('remark');
|
||||
const workerType = defineModel<string>('workerType');
|
||||
const number = defineModel<string>('visaNumber');
|
||||
const number = defineModel<string>('number');
|
||||
const reportDate = defineModel<Date | null | string>('reportDate');
|
||||
|
||||
const calculatedVisaDate = computed(() => {
|
||||
if (!issueDate.value) return undefined;
|
||||
return calculate90DayNext(issueDate.value);
|
||||
});
|
||||
//
|
||||
// const calculatedVisaDate = computed(() => {
|
||||
// if (!issueDate.value) return undefined;
|
||||
// return calculate90DayNext(issueDate.value);
|
||||
// });
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
|
|
@ -78,6 +80,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 +100,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 +121,14 @@ onMounted(() => {
|
|||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
visaIssueCountryFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.nationality),
|
||||
visaIssueCountryOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
visaTypeFilter = selectFilterOptionRefMod(
|
||||
optionStore.globalOption.nationality,
|
||||
optionStore.globalOption.visaType,
|
||||
visaTypeOptions,
|
||||
'label',
|
||||
);
|
||||
|
|
@ -120,6 +140,10 @@ watch(
|
|||
);
|
||||
},
|
||||
);
|
||||
//
|
||||
// watch([() => issueDate.value], () => {
|
||||
// reportDate.value = calculate90DayNext(issueDate.value);
|
||||
// });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -133,7 +157,7 @@ watch(
|
|||
name="mdi-passport"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
{{ title }}
|
||||
{{ $t(title) }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -353,10 +377,12 @@ watch(
|
|||
<DatePicker
|
||||
:id="`${prefixId}-date-picker-visa-issuance`"
|
||||
:readonly
|
||||
:disabled="!readonly"
|
||||
:label="$t('customerEmployee.form.visa90Day')"
|
||||
:model-value="calculatedVisaDate"
|
||||
v-model="reportDate"
|
||||
clearable
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -422,11 +448,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) : '')
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ const prop = withDefaults(
|
|||
inTable?: boolean;
|
||||
addButton?: boolean;
|
||||
prefixId?: string;
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
}>(),
|
||||
{
|
||||
gridView: false,
|
||||
|
|
@ -139,8 +141,9 @@ defineEmits<{
|
|||
<q-avatar size="md">
|
||||
<q-img
|
||||
:src="
|
||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
||||
`/images/employee-avatar-${props.row.gender}.png`
|
||||
props.row.selectedImage
|
||||
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
|
||||
: `/images/employee-avatar-${props.row.gender}.png`
|
||||
"
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
|
|
@ -265,9 +268,10 @@ defineEmits<{
|
|||
@click.stop="$emit('view', props.row)"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="!inTable"
|
||||
v-if="!inTable && !hideAction"
|
||||
:id-name="props.row.firstName"
|
||||
:status="props.row.status"
|
||||
:hide-delete="hideDelete"
|
||||
@view="$emit('view', props.row)"
|
||||
@edit="$emit('edit', props.row)"
|
||||
@delete="$emit('delete', props.row)"
|
||||
|
|
@ -280,9 +284,11 @@ defineEmits<{
|
|||
<template v-slot:item="props">
|
||||
<div class="col-12 col-md-3 col-sm-6">
|
||||
<PersonCard
|
||||
history
|
||||
:hide-delete="hideDelete"
|
||||
:hide-action="hideAction"
|
||||
:id="`card-${props.row.firstNameEN}`"
|
||||
:field-selected="fieldSelected"
|
||||
history
|
||||
:prefix-id="props.row.firstNameEN ?? props.rowIndex"
|
||||
:data="{
|
||||
code: props.row.code,
|
||||
|
|
@ -290,9 +296,9 @@ defineEmits<{
|
|||
$i18n.locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName} `.trim(),
|
||||
img:
|
||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
||||
`/images/employee-avatar-${props.row.gender}.png`,
|
||||
img: props.row.selectedImage
|
||||
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
|
||||
: `/images/employee-avatar-${props.row.gender}.png`,
|
||||
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
|
||||
male: props.row.gender === 'male',
|
||||
female: props.row.gender === 'female',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -45,6 +54,7 @@ defineProps<{
|
|||
employeeOwnerOption?: CustomerBranch[];
|
||||
prefixId: string;
|
||||
showBtnSave?: boolean;
|
||||
disableCustomerSelect?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -107,180 +117,18 @@ 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
|
||||
id="form-select-customer-branch-id"
|
||||
for="form-select-customer-branch-id"
|
||||
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"
|
||||
simple
|
||||
required
|
||||
:readonly
|
||||
:disabled="disableCustomerSelect && !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,15 +22,18 @@ 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;
|
||||
onDrawer?: boolean;
|
||||
hideAction?: boolean;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const optionStore = useOptionStore();
|
||||
const workflowStore = useWorkflowTemplate();
|
||||
|
||||
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
|
||||
default: [],
|
||||
|
|
@ -43,7 +48,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 +56,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 +67,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 +85,7 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
|
|||
userInTable.value[stepIndex] = {
|
||||
name: flowData.value.step[stepIndex].name,
|
||||
responsiblePerson: [],
|
||||
responsibleGroup: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +109,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 +177,7 @@ watch(
|
|||
onMounted(async () => {
|
||||
role.value = getRole() || [];
|
||||
await getUserList();
|
||||
await getGroupList();
|
||||
await userStore.fetchHqOption();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -166,6 +202,7 @@ onMounted(async () => {
|
|||
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
|
||||
>
|
||||
<ToggleButton
|
||||
:disable="hideAction"
|
||||
class="q-mr-sm"
|
||||
two-way
|
||||
:model-value="flowData.status !== 'INACTIVE'"
|
||||
|
|
@ -467,92 +504,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 v-if="!readonly" #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 +654,7 @@ onMounted(async () => {
|
|||
{{ $t('general.noData') }}
|
||||
</q-item>
|
||||
<q-item
|
||||
v-else
|
||||
v-for="(person, i) in userList"
|
||||
dense
|
||||
:key="i"
|
||||
|
|
@ -655,6 +729,7 @@ onMounted(async () => {
|
|||
{{ $t('personnel.MESSENGER') }}
|
||||
</span>
|
||||
<q-item
|
||||
dense
|
||||
clickable
|
||||
@click="step.messengerByArea = !step.messengerByArea"
|
||||
class="column"
|
||||
|
|
@ -670,9 +745,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 +902,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 +915,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>
|
||||
|
|
|
|||
|
|
@ -167,26 +167,28 @@ withDefaults(
|
|||
<div class="col-12 full-width">
|
||||
<q-table
|
||||
:rows-per-page-options="[0]"
|
||||
:rows="[
|
||||
{
|
||||
label: $t('productService.product.salePrice'),
|
||||
pricePerUnit: price,
|
||||
calcVat,
|
||||
vatIncluded,
|
||||
},
|
||||
{
|
||||
label: $t('productService.product.agentPrice'),
|
||||
calcVat: agentPriceCalcVat,
|
||||
vatIncluded: agentPriceVatIncluded,
|
||||
pricePerUnit: agentPrice,
|
||||
},
|
||||
{
|
||||
label: $t('productService.product.processingPrice'),
|
||||
calcVat: serviceChargeCalcVat,
|
||||
vatIncluded: serviceChargeVatIncluded,
|
||||
pricePerUnit: serviceCharge,
|
||||
},
|
||||
]"
|
||||
:rows="
|
||||
[
|
||||
priceDisplay.price && {
|
||||
label: $t('productService.product.salePrice'),
|
||||
pricePerUnit: price,
|
||||
calcVat,
|
||||
vatIncluded,
|
||||
},
|
||||
priceDisplay.agentPrice && {
|
||||
label: $t('productService.product.agentPrice'),
|
||||
calcVat: agentPriceCalcVat,
|
||||
vatIncluded: agentPriceVatIncluded,
|
||||
pricePerUnit: agentPrice,
|
||||
},
|
||||
priceDisplay.serviceCharge && {
|
||||
label: $t('productService.product.processingPrice'),
|
||||
calcVat: serviceChargeCalcVat,
|
||||
vatIncluded: serviceChargeVatIncluded,
|
||||
pricePerUnit: serviceCharge,
|
||||
},
|
||||
].filter(Boolean)
|
||||
"
|
||||
:columns
|
||||
hide-bottom
|
||||
bordered
|
||||
|
|
@ -244,16 +246,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 +276,8 @@ withDefaults(
|
|||
flat
|
||||
outlined
|
||||
dense
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="vatIncluded"
|
||||
></q-select>
|
||||
<q-select
|
||||
|
|
@ -285,6 +292,8 @@ withDefaults(
|
|||
flat
|
||||
outlined
|
||||
dense
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="agentPriceVatIncluded"
|
||||
></q-select>
|
||||
<q-select
|
||||
|
|
@ -299,6 +308,8 @@ withDefaults(
|
|||
flat
|
||||
outlined
|
||||
dense
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="serviceChargeVatIncluded"
|
||||
></q-select>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ watch(
|
|||
(c, o) => {
|
||||
const list = c.map((v: { name: string }) => v.name);
|
||||
const oldList = o.map((v: { name: string }) => v.name);
|
||||
const index = oldList.indexOf(workName.value || '');
|
||||
const index = workName.value ? oldList.indexOf(workName.value) : -1;
|
||||
if (index === -1) return;
|
||||
|
||||
if (
|
||||
list[index] !== oldList[index] &&
|
||||
|
|
@ -704,8 +705,8 @@ watch(
|
|||
}
|
||||
|
||||
: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;
|
||||
|
|
@ -735,8 +736,8 @@ watch(
|
|||
}
|
||||
|
||||
: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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { DeleteButton, EditButton, SaveButton, UndoButton } from '../button';
|
||||
import { scrollToElement } from 'stores/utils';
|
||||
// import { useI18n } from 'vue-i18n';
|
||||
// import { storeToRefs } from 'pinia';
|
||||
|
||||
|
|
@ -138,11 +137,8 @@ watch(
|
|||
@click="
|
||||
() => {
|
||||
assignClone();
|
||||
if (nameList[nameList.length - 1].name === '') {
|
||||
$emit('delete', cloneList[cloneList.length - 1].id, true);
|
||||
cloneList = cloneList.filter((item) => item.name !== '');
|
||||
nameList = nameList.filter((item) => item.name !== '');
|
||||
}
|
||||
cloneList = cloneList.filter((item) => item.name !== '');
|
||||
nameList = nameList.filter((item) => item.name !== '');
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ defineProps<{
|
|||
readonly?: boolean;
|
||||
onDrawer?: boolean;
|
||||
inputOnly?: boolean;
|
||||
disableToggle?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -76,6 +77,7 @@ defineEmits<{
|
|||
<ToggleButton
|
||||
class="q-mr-sm"
|
||||
two-way
|
||||
:disable="disableToggle"
|
||||
:model-value="status !== 'INACTIVE'"
|
||||
@click="
|
||||
() => {
|
||||
|
|
@ -195,8 +197,8 @@ defineEmits<{
|
|||
}
|
||||
|
||||
: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;
|
||||
|
|
@ -208,15 +210,15 @@ defineEmits<{
|
|||
}
|
||||
|
||||
: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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,18 @@
|
|||
import SelectCustomer from '../shared/select/SelectCustomer.vue';
|
||||
import SelectBranch from '../shared/select/SelectBranch.vue';
|
||||
|
||||
import { CustomerBranch } from 'src/stores/customer';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const branchId = defineModel<string>('branchId');
|
||||
const customerBranchId = defineModel<string>('customerBranchId');
|
||||
const agentPrice = defineModel<boolean>('agentPrice');
|
||||
const special = defineModel<boolean>('special');
|
||||
|
||||
const customerBranchOption = defineModel<CustomerBranch>(
|
||||
'customerBranchOption',
|
||||
);
|
||||
|
||||
defineProps<{
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
|
|
@ -67,8 +74,12 @@ defineEmits<{
|
|||
required
|
||||
:readonly
|
||||
/>
|
||||
|
||||
<SelectCustomer
|
||||
id="about-select-customer-branch-id"
|
||||
for="about-select-customer-branch-id"
|
||||
v-model:value="customerBranchId"
|
||||
v-model:value-option="customerBranchOption"
|
||||
:label="$t('quotation.customer')"
|
||||
:creatable-disabled-text="`(${$t('form.error.selectField', {
|
||||
field: $t('quotation.branchVirtual'),
|
||||
|
|
@ -88,14 +99,14 @@ defineEmits<{
|
|||
|
||||
<style scoped>
|
||||
:deep(
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one
|
||||
) {
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one
|
||||
) {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
:deep(
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two
|
||||
) {
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two
|
||||
) {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -58,16 +58,15 @@ const currentBtnOpen = ref<{ title: string; opened: boolean[] }[]>([
|
|||
|
||||
function calcPrice(c: (typeof rows.value)[number]) {
|
||||
const originalPrice = c.pricePerUnit;
|
||||
const finalPriceWithVat = precisionRound(
|
||||
originalPrice + originalPrice * (config.value?.vat || 0.07),
|
||||
const finalPricePerUnit = precisionRound(
|
||||
originalPrice +
|
||||
(c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
||||
? originalPrice * (config.value?.vat || 0.07)
|
||||
: 0),
|
||||
);
|
||||
const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
||||
const price = finalPricePerUnit * c.amount - c.discount;
|
||||
|
||||
const price = finalPriceNoVat * c.amount - c.discount;
|
||||
const vat = c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
||||
? (finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07)
|
||||
: 0;
|
||||
return precisionRound(price + vat);
|
||||
return precisionRound(price);
|
||||
}
|
||||
|
||||
const discount4Show = ref<string[]>([]);
|
||||
|
|
@ -435,8 +434,20 @@ watch(
|
|||
<q-td align="right">
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
props.row.pricePerUnit * props.row.amount -
|
||||
props.row.discount,
|
||||
props.row.product[
|
||||
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
|
||||
]
|
||||
? precisionRound(
|
||||
(props.row.pricePerUnit *
|
||||
(1 + (config?.vat || 0.07)) *
|
||||
props.row.amount -
|
||||
props.row.discount) /
|
||||
(1 + (config?.vat || 0.07)),
|
||||
)
|
||||
: precisionRound(
|
||||
props.row.pricePerUnit * props.row.amount -
|
||||
props.row.discount,
|
||||
),
|
||||
2,
|
||||
)
|
||||
}}
|
||||
|
|
@ -448,9 +459,12 @@ watch(
|
|||
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
|
||||
]
|
||||
? precisionRound(
|
||||
(props.row.pricePerUnit * props.row.amount -
|
||||
props.row.discount) *
|
||||
(config?.vat || 0.07),
|
||||
((props.row.pricePerUnit *
|
||||
(1 + (config?.vat || 0.07)) *
|
||||
props.row.amount -
|
||||
props.row.discount) /
|
||||
(1 + (config?.vat || 0.07))) *
|
||||
0.07,
|
||||
)
|
||||
: 0,
|
||||
2,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { QTableProps } from 'quasar';
|
||||
import { dateFormat } from 'src/utils/datetime';
|
||||
import { dateFormat, dateFormatJS } from 'src/utils/datetime';
|
||||
|
||||
import { formatNumberDecimal } from 'stores/utils';
|
||||
|
||||
|
|
@ -19,6 +19,8 @@ const props = withDefaults(
|
|||
page?: number;
|
||||
pageSize?: number;
|
||||
hideBtnPreview?: boolean;
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
}>(),
|
||||
{
|
||||
row: () => [],
|
||||
|
|
@ -84,11 +86,11 @@ defineEmits<{
|
|||
</q-td>
|
||||
|
||||
<q-td v-if="visibleColumns.includes('createdAt')">
|
||||
{{ dateFormat(props.row.createdAt) }}
|
||||
{{ dateFormatJS({ date: props.row.createdAt }) }}
|
||||
</q-td>
|
||||
|
||||
<q-td v-if="visibleColumns.includes('dueDate')">
|
||||
{{ dateFormat(props.row.dueDate) }}
|
||||
{{ dateFormatJS({ date: props.row.dueDate }) }}
|
||||
</q-td>
|
||||
|
||||
<q-td v-if="visibleColumns.includes('contactName')">
|
||||
|
|
@ -147,12 +149,12 @@ defineEmits<{
|
|||
flat
|
||||
@click.stop="$emit('view', props.row)"
|
||||
/>
|
||||
|
||||
<KebabAction
|
||||
v-if="!hideAction"
|
||||
:idName="`btn-kebab-${props.row.workName}`"
|
||||
status="'ACTIVE'"
|
||||
hide-toggle
|
||||
hide-delete
|
||||
:hide-delete
|
||||
:hide-edit="hideEdit"
|
||||
@view="$emit('view', props.row)"
|
||||
@edit="$emit('edit', props.row)"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SelectInput from '../shared/SelectInput.vue';
|
||||
import useOptionStore from 'src/stores/options';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { useInstitution } from 'src/stores/institution';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
||||
|
|
@ -10,12 +14,31 @@ defineProps<{
|
|||
readonly?: boolean;
|
||||
onDrawer?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'deleteAttachment', name: string): void;
|
||||
}>();
|
||||
|
||||
const attachmentRef = ref();
|
||||
|
||||
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: '' });
|
||||
const attachment = defineModel<File[]>('attachment');
|
||||
const attachmentList =
|
||||
defineModel<{ name: string; url: string }[]>('attachmentList');
|
||||
|
||||
type Options = { label: string; value: string };
|
||||
|
||||
function openNewTab(url: string) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
function deleteAttachment(name: string) {
|
||||
emit('deleteAttachment', name);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
|
|
@ -35,7 +58,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,10 +85,15 @@ 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')]"
|
||||
:rules="[
|
||||
(val) => !!val || $t('form.error.required'),
|
||||
(val) =>
|
||||
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
|
||||
$t('form.error.branchNameField'),
|
||||
]"
|
||||
/>
|
||||
<q-input
|
||||
for="input-agencies-name-en"
|
||||
|
|
@ -73,10 +101,159 @@ 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"
|
||||
:rules="
|
||||
nameEn
|
||||
? [
|
||||
(val) =>
|
||||
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
|
||||
$t('form.error.branchNameENField'),
|
||||
]
|
||||
: []
|
||||
"
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
<q-file
|
||||
v-if="!readonly"
|
||||
ref="attachmentRef"
|
||||
for="input-attachment"
|
||||
dense
|
||||
outlined
|
||||
multiple
|
||||
append
|
||||
:readonly
|
||||
:label="$t('personnel.form.attachment')"
|
||||
class="col"
|
||||
v-model="attachment"
|
||||
>
|
||||
<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="attachmentList && attachmentList?.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 attachmentList"
|
||||
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
|
||||
v-if="!readonly"
|
||||
id="delete-file"
|
||||
rounded
|
||||
flat
|
||||
dense
|
||||
unelevated
|
||||
size="md"
|
||||
icon="mdi-trash-can-outline"
|
||||
class="app-text-negative"
|
||||
@click.stop="deleteAttachment(item.name)"
|
||||
/>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -27,26 +27,38 @@ withDefaults(
|
|||
class="app-text-muted q-pr-sm"
|
||||
:width="iconSize || '2rem'"
|
||||
/>
|
||||
<span class="row col">
|
||||
<span class="col-12 app-text-muted-2" style="font-size: 10px">
|
||||
<span :id="`dd-wrapper-${label}`" class="row col">
|
||||
<span
|
||||
:id="`dd-label-${label}`"
|
||||
class="col-12 app-text-muted-2"
|
||||
style="font-size: 10px"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<span class="col-12 ellipsis">
|
||||
<span :id="`dd-value-wrapper-${label}`" class="col-12 ellipsis">
|
||||
<slot name="value">
|
||||
<span
|
||||
:class="{ 'link cursor-pointer': clickable }"
|
||||
v-if="typeof value === 'string'"
|
||||
@click="$emit('labelClick', value, null)"
|
||||
@click="clickable ? $emit('labelClick', value, null) : undefined"
|
||||
:id="`link-${value}`"
|
||||
:for="`link-${value}`"
|
||||
>
|
||||
{{ value }}
|
||||
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
|
||||
</span>
|
||||
<span v-else :class="{ 'link cursor-pointer': clickable }">
|
||||
<span
|
||||
:id="`dd-value-${label}`"
|
||||
v-else
|
||||
:class="{ 'link cursor-pointer': clickable }"
|
||||
>
|
||||
<span
|
||||
v-for="(item, index) in value"
|
||||
:key="index"
|
||||
@click="$emit('labelClick', item, index)"
|
||||
class="link cursor-pointer"
|
||||
:id="`link-${item}`"
|
||||
:for="`link-${item}`"
|
||||
>
|
||||
{{ item }}
|
||||
<span v-if="index < value.length - 1">, </span>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ defineProps<{
|
|||
const quotationId = defineModel<string>('quotationId', {
|
||||
required: true,
|
||||
});
|
||||
const isDebitNote = defineModel<boolean>('isDebitNote', {
|
||||
required: false,
|
||||
default: false,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
|
|
@ -37,6 +41,7 @@ const quotationId = defineModel<string>('quotationId', {
|
|||
cancelIncludeDebitNote: true,
|
||||
hasCancel: true,
|
||||
}"
|
||||
@selected="(v) => (isDebitNote = v.isDebitNote)"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -42,6 +42,15 @@ defineProps<{
|
|||
|
||||
const modal = defineModel('modal', { default: false });
|
||||
const currentTab = defineModel<string>('currentTab');
|
||||
|
||||
async function onValidationError(ref: any) {
|
||||
const el = ref.$el as Element;
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-dialog
|
||||
|
|
@ -60,6 +69,7 @@ const currentTab = defineModel<string>('currentTab');
|
|||
@submit.prevent
|
||||
@validation-success="submit"
|
||||
class="column full-height"
|
||||
@validation-error="onValidationError"
|
||||
>
|
||||
<!-- header -->
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
UndoButton,
|
||||
} from 'components/button';
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title: string;
|
||||
category?: string;
|
||||
|
|
@ -42,10 +42,24 @@ const drawerOpen = defineModel<boolean>('drawerOpen', {
|
|||
const myForm = ref();
|
||||
|
||||
function reset() {
|
||||
if (props.beforeClose) {
|
||||
drawerOpen.value = props.beforeClose
|
||||
? props.beforeClose()
|
||||
: !drawerOpen.value;
|
||||
}
|
||||
if (myForm.value) {
|
||||
myForm.value.resetValidation();
|
||||
}
|
||||
}
|
||||
|
||||
async function onValidationError(ref: any) {
|
||||
const el = ref.$el as Element;
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-drawer
|
||||
|
|
@ -53,7 +67,6 @@ function reset() {
|
|||
@show="show"
|
||||
@before-hide="reset"
|
||||
@hide="close"
|
||||
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
|
||||
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
|
||||
v-model="drawerOpen"
|
||||
behavior="mobile"
|
||||
|
|
@ -66,6 +79,7 @@ function reset() {
|
|||
greedy
|
||||
@submit.prevent
|
||||
@validation-success="submit"
|
||||
@validation-error="onValidationError"
|
||||
>
|
||||
<div
|
||||
class="column justify-between full-height"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ defineProps<{
|
|||
color="grey"
|
||||
icon="mdi-close"
|
||||
v-close-popup
|
||||
@click="cancel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
const pageSize = defineModel<number>({ required: true });
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
fetchData?: (...args: unknown[]) => void;
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -10,7 +17,12 @@ const pageSize = defineModel<number>({ required: true });
|
|||
:key="v"
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="pageSize = v"
|
||||
@click="
|
||||
() => {
|
||||
pageSize = v;
|
||||
fetchData();
|
||||
}
|
||||
"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ v }}</q-item-label>
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ const smallBanner = ref(false);
|
|||
|
||||
<ToggleButton
|
||||
v-if="useToggle"
|
||||
:disable="readonly"
|
||||
two-way
|
||||
:model-value="toggleStatus !== 'INACTIVE'"
|
||||
@click="$emit('update:toggleStatus', toggleStatus)"
|
||||
|
|
@ -265,6 +266,7 @@ const smallBanner = ref(false);
|
|||
class="app-text-muted full-width"
|
||||
align="left"
|
||||
v-if="typeof tabsList === 'object'"
|
||||
@update:model-value="(v) => $emit('update:currentTab', v)"
|
||||
>
|
||||
<q-tab
|
||||
v-for="tab in tabsList"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { BranchWithChildren } from 'stores/branch/types';
|
||||
import KebabAction from './shared/KebabAction.vue';
|
||||
import { isRoleInclude } from 'stores/utils';
|
||||
|
||||
const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', {
|
||||
default: [],
|
||||
|
|
@ -17,6 +16,7 @@ withDefaults(
|
|||
labelKey?: string;
|
||||
childrenKey: string;
|
||||
action?: boolean;
|
||||
hideCreate?: boolean;
|
||||
}>(),
|
||||
{
|
||||
color: 'transparent',
|
||||
|
|
@ -96,7 +96,9 @@ defineEmits<{
|
|||
expandedTree[expandedTree.length - 1] === node.id,
|
||||
}"
|
||||
>
|
||||
{{ node.name }}
|
||||
{{
|
||||
$i18n.locale === 'eng' ? node.nameEN || node.name : node.name
|
||||
}}
|
||||
</span>
|
||||
<span class="app-text-muted text-caption ellipsis">
|
||||
{{ node.code }}
|
||||
|
|
@ -120,11 +122,7 @@ defineEmits<{
|
|||
/>
|
||||
|
||||
<q-btn
|
||||
v-if="
|
||||
node.isHeadOffice &&
|
||||
typeTree === 'branch' &&
|
||||
isRoleInclude(['head_of_admin', 'admin', 'system'])
|
||||
"
|
||||
v-if="node.isHeadOffice && typeTree === 'branch' && !hideCreate"
|
||||
:id="`create-sub-branch-btn-${node.name}`"
|
||||
@click.stop="$emit('create', node)"
|
||||
icon="mdi-file-plus-outline"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ defineEmits<{
|
|||
(e: 'click', v: MouseEvent): void;
|
||||
}>();
|
||||
defineProps<{
|
||||
id?: string;
|
||||
icon?: string;
|
||||
color: string;
|
||||
iconOnly?: boolean;
|
||||
|
|
@ -18,6 +19,7 @@ defineProps<{
|
|||
|
||||
<template>
|
||||
<button
|
||||
:id="id"
|
||||
@click="(e) => $emit('click', e)"
|
||||
class="main-btn"
|
||||
:class="{
|
||||
|
|
|
|||
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>
|
||||
|
|
@ -10,6 +10,7 @@ defineProps<{
|
|||
outlined?: boolean;
|
||||
disabled?: boolean;
|
||||
dark?: boolean;
|
||||
color?: string;
|
||||
|
||||
label?: string;
|
||||
icon?: string;
|
||||
|
|
@ -23,7 +24,7 @@ defineProps<{
|
|||
@click="(e) => $emit('click', e)"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:icon="icon || 'mdi-content-save-outline'"
|
||||
color="207 96% 32%"
|
||||
:color="color || '207 96% 32%'"
|
||||
:title="iconOnly ? $t('general.save') : undefined"
|
||||
>
|
||||
{{ label || $t('general.save') }}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ function update(value: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
async function onValidationError(ref: any) {
|
||||
const el = ref.$el as Element;
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
|
||||
const state = defineModel({ default: false });
|
||||
</script>
|
||||
|
||||
|
|
@ -41,6 +50,7 @@ const state = defineModel({ default: false });
|
|||
}"
|
||||
>
|
||||
<q-form
|
||||
@validation-error="onValidationError"
|
||||
@submit.prevent="(e) => $emit('submit', e)"
|
||||
@reset="$emit('reset')"
|
||||
greedy
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { formatAddress } from 'src/utils/address';
|
|||
import useOptionStore from 'stores/options';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
addressTitle?: string;
|
||||
addressTitleEN?: string;
|
||||
|
|
@ -30,6 +30,7 @@ defineProps<{
|
|||
|
||||
useEmployment?: boolean;
|
||||
useWorkPlace?: boolean;
|
||||
useForeignAddress?: boolean;
|
||||
}>();
|
||||
|
||||
const addressStore = useAddressStore();
|
||||
|
|
@ -57,6 +58,25 @@ const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
|
|||
const zipCode = defineModel<string | null | undefined>('zipCode');
|
||||
const sameWithEmployer = defineModel<boolean>('sameWithEmployer');
|
||||
|
||||
const provinceTextEN = defineModel<string | null | undefined>(
|
||||
'provinceTextEn',
|
||||
{
|
||||
default: '',
|
||||
},
|
||||
);
|
||||
const districtTextEN = defineModel<string | null | undefined>(
|
||||
'districtTextEn',
|
||||
{
|
||||
default: '',
|
||||
},
|
||||
);
|
||||
const subDistrictTextEN = defineModel<string | null | undefined>(
|
||||
'subDistrictTextEn',
|
||||
{
|
||||
default: '',
|
||||
},
|
||||
);
|
||||
|
||||
const homeCode = defineModel<string | null | undefined>('homeCode');
|
||||
const employmentOffice = defineModel<string | null | undefined>(
|
||||
'employmentOffice',
|
||||
|
|
@ -64,6 +84,7 @@ const employmentOffice = defineModel<string | null | undefined>(
|
|||
const employmentOfficeEN = defineModel<string | null | undefined>(
|
||||
'employmentOfficeEn',
|
||||
);
|
||||
const addressForeign = defineModel<boolean>('addressForeign');
|
||||
|
||||
const addrOptions = reactive<{
|
||||
provinceOps: Province[];
|
||||
|
|
@ -78,14 +99,18 @@ const addrOptions = reactive<{
|
|||
const area = ref<Office[]>([]);
|
||||
|
||||
const fullAddress = computed(() => {
|
||||
const province = provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = subDistrictOptions.value.find(
|
||||
(v) => v.id === subDistrictId.value,
|
||||
);
|
||||
const province = addressForeign.value
|
||||
? { id: '1', name: provinceId.value }
|
||||
: provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = addressForeign.value
|
||||
? { id: '1', name: districtId.value }
|
||||
: districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = addressForeign.value
|
||||
? { id: '1', name: subDistrictId.value }
|
||||
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
|
||||
|
||||
if (province && district && sDistrict) {
|
||||
const fullAddress = formatAddress({
|
||||
if (province?.name && district?.name && sDistrict?.name) {
|
||||
const fullAddressText = formatAddress({
|
||||
address: address.value,
|
||||
addressEN: addressEN.value,
|
||||
moo: moo.value ? moo.value : '',
|
||||
|
|
@ -97,21 +122,26 @@ const fullAddress = computed(() => {
|
|||
province: province as unknown as Province,
|
||||
district: district as unknown as District,
|
||||
subDistrict: sDistrict as unknown as SubDistrict,
|
||||
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
|
||||
});
|
||||
return fullAddress;
|
||||
return fullAddressText;
|
||||
}
|
||||
return '-';
|
||||
});
|
||||
|
||||
const fullAddressEN = computed(() => {
|
||||
const province = provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = subDistrictOptions.value.find(
|
||||
(v) => v.id === subDistrictId.value,
|
||||
);
|
||||
const province = addressForeign.value
|
||||
? { nameEN: provinceTextEN.value }
|
||||
: provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = addressForeign.value
|
||||
? { nameEN: districtTextEN.value }
|
||||
: districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = addressForeign.value
|
||||
? { nameEN: subDistrictTextEN.value }
|
||||
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
|
||||
|
||||
if (province && district && sDistrict) {
|
||||
const fullAddress = formatAddress({
|
||||
if (province?.nameEN && district?.nameEN && sDistrict?.nameEN) {
|
||||
const fullAddressText = formatAddress({
|
||||
address: address.value,
|
||||
addressEN: addressEN.value,
|
||||
moo: moo.value ? moo.value : '',
|
||||
|
|
@ -124,8 +154,9 @@ const fullAddressEN = computed(() => {
|
|||
district: district as unknown as District,
|
||||
subDistrict: sDistrict as unknown as SubDistrict,
|
||||
en: true,
|
||||
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
|
||||
});
|
||||
return fullAddress;
|
||||
return fullAddressText;
|
||||
}
|
||||
return '-';
|
||||
});
|
||||
|
|
@ -149,7 +180,7 @@ async function fetchProvince() {
|
|||
}
|
||||
|
||||
async function fetchDistrict() {
|
||||
if (!provinceId.value) return;
|
||||
if (!provinceId.value || addressForeign.value) return;
|
||||
|
||||
const result = await addressStore.fetchDistrictByProvinceId(provinceId.value);
|
||||
if (result) addrOptions.districtOps = result;
|
||||
|
|
@ -168,7 +199,7 @@ async function fetchDistrict() {
|
|||
}
|
||||
|
||||
async function fetchSubDistrict() {
|
||||
if (!districtId.value) return;
|
||||
if (!districtId.value || addressForeign.value) return;
|
||||
const result = await addressStore.fetchSubDistrictByProvinceId(
|
||||
districtId.value,
|
||||
);
|
||||
|
|
@ -255,6 +286,16 @@ onMounted(async () => {
|
|||
await fetchSubDistrict();
|
||||
});
|
||||
|
||||
function clearAddress() {
|
||||
provinceId.value = null;
|
||||
districtId.value = null;
|
||||
subDistrictId.value = null;
|
||||
provinceTextEN.value = null;
|
||||
districtTextEN.value = null;
|
||||
subDistrictTextEN.value = null;
|
||||
zipCode.value = null;
|
||||
}
|
||||
|
||||
watch(provinceId, fetchDistrict);
|
||||
watch(districtId, fetchSubDistrict);
|
||||
|
||||
|
|
@ -313,6 +354,15 @@ watchEffect(async () => {
|
|||
{{ $t('customerEmployee.form.addressCustom') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="useForeignAddress" class="text-caption q-ml-md app-text-muted">
|
||||
<q-checkbox
|
||||
size="xs"
|
||||
v-model="addressForeign"
|
||||
@update:model-value="clearAddress"
|
||||
/>
|
||||
{{ $t('personnel.form.addressForeign') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-y-md">
|
||||
|
|
@ -449,7 +499,24 @@ watchEffect(async () => {
|
|||
(v) => (typeof v === 'string' ? (street = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="provinceId"
|
||||
:dense="dense"
|
||||
:label="$t('form.province')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-province-${indexId}` : 'input-province'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -493,7 +560,24 @@ watchEffect(async () => {
|
|||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="districtId"
|
||||
:dense="dense"
|
||||
:label="$t('form.district')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-district-${indexId}` : 'input-district'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -536,7 +620,25 @@ watchEffect(async () => {
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="subDistrictId"
|
||||
:dense="dense"
|
||||
:label="$t('form.district')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-${indexId}` : 'input-sub-district'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -580,17 +682,27 @@ watchEffect(async () => {
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
:key="Number(addressForeign)"
|
||||
hide-bottom-space
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
readonly
|
||||
:disable="!addressForeign && !readonly && !sameWithEmployer"
|
||||
:readonly="!addressForeign || readonly"
|
||||
:label="$t('form.zipCode')"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="
|
||||
addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? ''
|
||||
!addressForeign
|
||||
? (addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? '')
|
||||
: zipCode
|
||||
"
|
||||
@update:model-value="(v) => (zipCode = v.toString())"
|
||||
:rules="
|
||||
!addressForeign
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
|
|
@ -689,7 +801,24 @@ watchEffect(async () => {
|
|||
(v) => (typeof v === 'string' ? (streetEN = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="provinceTextEN"
|
||||
:dense="dense"
|
||||
label="Province"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-province-en-${indexId}` : 'input-province-en'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -732,7 +861,25 @@ watchEffect(async () => {
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="districtTextEN"
|
||||
:dense="dense"
|
||||
label="District"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-district-en-${indexId}` : 'input-district-en'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -775,7 +922,25 @@ watchEffect(async () => {
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="subDistrictTextEN"
|
||||
:dense="dense"
|
||||
label="Sub-District"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-en-${indexId}` : 'input-sub-district-en'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -819,19 +984,28 @@ watchEffect(async () => {
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
:key="Number(addressForeign)"
|
||||
hide-bottom-space
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
readonly
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
:readonly="!addressForeign || readonly"
|
||||
:disable="!addressForeign && !readonly && !sameWithEmployer"
|
||||
zip="zip-en"
|
||||
label="Zip Code"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="
|
||||
addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? ''
|
||||
!addressForeign
|
||||
? (addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? '')
|
||||
: zipCode
|
||||
"
|
||||
@update:model-value="(v) => (zipCode = v.toString())"
|
||||
:rules="
|
||||
!addressForeign
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@ export { default as SideMenu } from './SideMenu.vue';
|
|||
export { default as StatCardComponent } from './StatCardComponent.vue';
|
||||
export { default as TooltipComponent } from './TooltipComponent.vue';
|
||||
export { default as TreeComponent } from './TreeComponent.vue';
|
||||
export { default as PaginationPageSize } from './PaginationPageSize.vue';
|
||||
|
|
|
|||
190
src/components/shared/AdvanceSearch.vue
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<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">
|
||||
<slot name="prepend"></slot>
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ defineProps<{
|
|||
history?: boolean;
|
||||
prefixId?: string;
|
||||
separateEnter?: boolean;
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -76,8 +78,10 @@ defineEmits<{
|
|||
/>
|
||||
|
||||
<KebabAction
|
||||
v-if="!hideAction"
|
||||
:id-name="prefixId"
|
||||
:status="disabled ? 'INACTIVE' : 'ACTIVE'"
|
||||
:hide-delete="hideDelete"
|
||||
@view="
|
||||
separateEnter
|
||||
? $emit('viewCard', 'INFO')
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ let defaultFilter: (
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
prefix?: string;
|
||||
id?: string;
|
||||
label?: string;
|
||||
option: T[];
|
||||
|
|
@ -28,6 +29,7 @@ const props = withDefaults(
|
|||
disable?: boolean;
|
||||
multiple?: boolean;
|
||||
hideInput?: boolean;
|
||||
hideDropdownIcon?: boolean;
|
||||
|
||||
rules?: ((value: string) => string | true)[];
|
||||
}>(),
|
||||
|
|
@ -70,6 +72,7 @@ watch(
|
|||
</script>
|
||||
<template>
|
||||
<q-select
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
outlined
|
||||
:clearable
|
||||
|
|
@ -82,7 +85,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 +106,11 @@ watch(
|
|||
}
|
||||
"
|
||||
:rules
|
||||
@clear="
|
||||
() => {
|
||||
multiple ? (model = []) : (model = '');
|
||||
}
|
||||
"
|
||||
>
|
||||
<template v-if="$slots.prepend" v-slot:prepend>
|
||||
<slot name="prepend"></slot>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,11 @@ onMounted(async () => {
|
|||
:option="selectOptions"
|
||||
:hide-selected="false"
|
||||
:fill-input="false"
|
||||
:rules="[(v: string) => !!v || $t('form.error.required')]"
|
||||
:rules="[
|
||||
(v: string) => {
|
||||
return !!v?.length || $t('form.error.required');
|
||||
},
|
||||
]"
|
||||
@filter="filter"
|
||||
>
|
||||
<template #before-options v-if="creatable">
|
||||
|
|
|
|||
|
|
@ -75,9 +75,9 @@ function setDefaultValue() {
|
|||
</script>
|
||||
<template>
|
||||
<SelectInput
|
||||
for="select-hq-id"
|
||||
v-model="value"
|
||||
incremental
|
||||
id="select-hq-id"
|
||||
:label
|
||||
:placeholder
|
||||
:readonly
|
||||
|
|
|
|||
213
src/components/shared/select/SelectBusinessType.vue
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
import { createSelect, SelectProps } from './select';
|
||||
import SelectInput from '../SelectInput.vue';
|
||||
|
||||
import { BusinessType } from 'src/stores/business-type/types';
|
||||
|
||||
import useStore from 'src/stores/business-type';
|
||||
|
||||
type SelectOption = BusinessType;
|
||||
|
||||
const value = defineModel<string | null | undefined>('value', {
|
||||
required: true,
|
||||
});
|
||||
const valueOption = defineModel<SelectOption>('valueOption', {
|
||||
required: false,
|
||||
});
|
||||
|
||||
const selectOptions = ref<SelectOption[]>([]);
|
||||
|
||||
const { fetchList: getList, fetchById: getById } = useStore();
|
||||
|
||||
defineEmits<{
|
||||
(e: 'create'): void;
|
||||
}>();
|
||||
|
||||
type ExclusiveProps = {
|
||||
lang?: string;
|
||||
codeOnly?: boolean;
|
||||
selectFirstValue?: boolean;
|
||||
branchVirtual?: boolean;
|
||||
checkRole?: string[];
|
||||
};
|
||||
|
||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||
|
||||
const { getOptions, setFirstValue, getSelectedOption, filter } =
|
||||
createSelect<SelectOption>(
|
||||
{
|
||||
value,
|
||||
valueOption,
|
||||
selectOptions,
|
||||
getList: async (query) => {
|
||||
const ret = await getList({
|
||||
query,
|
||||
...props.params,
|
||||
pageSize: 99999,
|
||||
});
|
||||
if (ret) return ret.result;
|
||||
},
|
||||
getByValue: async (id) => {
|
||||
const ret = await getById(id);
|
||||
if (ret) return ret;
|
||||
},
|
||||
},
|
||||
{ valueField: 'id' },
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await getOptions();
|
||||
|
||||
if (props.autoSelectOnSingle && selectOptions.value.length === 1) {
|
||||
setFirstValue();
|
||||
}
|
||||
|
||||
if (props.selectFirstValue) {
|
||||
setDefaultValue();
|
||||
} else await getSelectedOption();
|
||||
});
|
||||
|
||||
function setDefaultValue() {
|
||||
setFirstValue();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<SelectInput
|
||||
v-model="value"
|
||||
incremental
|
||||
option-value="id"
|
||||
:label="label || $t('menu.manage.businessType')"
|
||||
:placeholder
|
||||
:readonly
|
||||
:disable="disabled"
|
||||
:option="selectOptions"
|
||||
:hide-selected="false"
|
||||
:fill-input="false"
|
||||
:rules="[
|
||||
(v: string) => !props.required || !!v || $t('form.error.required'),
|
||||
]"
|
||||
@filter="filter"
|
||||
>
|
||||
<template #selected-item="{ opt }">
|
||||
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
|
||||
</template>
|
||||
|
||||
<template #no-option v-if="creatable">
|
||||
<q-item
|
||||
:disable="creatableDisabled"
|
||||
clickable
|
||||
v-close-popup
|
||||
@click.stop="$emit('create')"
|
||||
>
|
||||
<q-item-section>
|
||||
<span class="row items-center">
|
||||
<q-icon
|
||||
name="mdi-plus-circle-outline"
|
||||
class="q-mr-sm"
|
||||
style="color: hsl(var(--positive-bg))"
|
||||
/>
|
||||
<b>
|
||||
{{ $t('general.add', { text: $t('businessType.title') }) }}
|
||||
</b>
|
||||
<span
|
||||
v-if="creatableDisabled && creatableDisabledText"
|
||||
class="app-text-muted q-pl-xs"
|
||||
style="font-size: 80%"
|
||||
>
|
||||
{{ creatableDisabledText }}
|
||||
</span>
|
||||
</span>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template #before-options v-if="creatable">
|
||||
<q-item
|
||||
:disable="creatableDisabled"
|
||||
clickable
|
||||
v-close-popup
|
||||
@click.stop="$emit('create')"
|
||||
for="select-biz-type-add-new"
|
||||
id="select-biz-type-add-new"
|
||||
>
|
||||
<q-item-section>
|
||||
<span class="row items-center">
|
||||
<q-icon
|
||||
name="mdi-plus-circle-outline"
|
||||
class="q-mr-sm"
|
||||
style="color: hsl(var(--positive-bg))"
|
||||
/>
|
||||
<b>
|
||||
{{ $t('general.add', { text: $t('businessType.title') }) }}
|
||||
</b>
|
||||
<span
|
||||
v-if="creatableDisabled && creatableDisabledText"
|
||||
class="app-text-muted q-pl-xs"
|
||||
style="font-size: 80%"
|
||||
>
|
||||
{{ creatableDisabledText }}
|
||||
</span>
|
||||
</span>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template #before-options v-if="creatable">
|
||||
<q-item
|
||||
:disable="creatableDisabled"
|
||||
clickable
|
||||
v-close-popup
|
||||
@click.stop="$emit('create')"
|
||||
for="select-business-type-add-new"
|
||||
id="select-business-type-add-new"
|
||||
>
|
||||
<q-item-section>
|
||||
<span class="row items-center">
|
||||
<q-icon
|
||||
name="mdi-plus-circle-outline"
|
||||
class="q-mr-sm"
|
||||
style="color: hsl(var(--positive-bg))"
|
||||
/>
|
||||
<b>
|
||||
{{ $t('general.add', { text: $t('menu.manage.businessType') }) }}
|
||||
</b>
|
||||
<span
|
||||
v-if="creatableDisabled && creatableDisabledText"
|
||||
class="app-text-muted q-pl-xs"
|
||||
style="font-size: 80%"
|
||||
>
|
||||
{{ creatableDisabledText }}
|
||||
</span>
|
||||
</span>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template #option="{ opt, scope }">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<span class="row items-center">
|
||||
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
|
||||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template #append v-if="clearable">
|
||||
<q-icon
|
||||
v-if="!readonly && value"
|
||||
name="mdi-close-circle"
|
||||
@click.stop="value = ''"
|
||||
class="cursor-pointer clear-btn"
|
||||
/>
|
||||
</template>
|
||||
</SelectInput>
|
||||
</template>
|
||||
|
|
@ -30,6 +30,7 @@ defineEmits<{
|
|||
type ExclusiveProps = {
|
||||
simple?: boolean;
|
||||
simpleBranchNo?: boolean;
|
||||
selectFirstValue?: boolean;
|
||||
};
|
||||
|
||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||
|
|
@ -64,8 +65,14 @@ onMounted(async () => {
|
|||
setFirstValue();
|
||||
}
|
||||
|
||||
await getSelectedOption();
|
||||
if (props.selectFirstValue) {
|
||||
setDefaultValue();
|
||||
} else await getSelectedOption();
|
||||
});
|
||||
|
||||
function setDefaultValue() {
|
||||
setFirstValue();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<SelectInput
|
||||
|
|
@ -158,11 +165,9 @@ onMounted(async () => {
|
|||
</template>
|
||||
|
||||
<template #option="{ opt, scope }">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item v-bind="scope.itemProps" class="q-mx-sm bodrder">
|
||||
<SelectCustomerItem :data="opt" :simple :simple-branch-no />
|
||||
</q-item>
|
||||
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template #append v-if="clearable">
|
||||
|
|
@ -175,3 +180,11 @@ onMounted(async () => {
|
|||
</template>
|
||||
</SelectInput>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bodrder {
|
||||
border-bottom: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const { getQuotationList: getList, getQuotation: getById } = useStore();
|
|||
|
||||
defineEmits<{
|
||||
(e: 'create'): void;
|
||||
(e: 'selected', value: SelectOption): void;
|
||||
}>();
|
||||
|
||||
type ExclusiveProps = {
|
||||
|
|
@ -117,6 +118,14 @@ function setDefaultValue() {
|
|||
(v: string) => !props.required || !!v || $t('form.error.required'),
|
||||
]"
|
||||
@filter="filter"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
$emit(
|
||||
'selected',
|
||||
selectOptions.find((opt) => opt.id === v),
|
||||
);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #append v-if="clearable">
|
||||
<q-icon
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ defineEmits<{
|
|||
|
||||
type ExclusiveProps = {
|
||||
selectFirstValue?: boolean;
|
||||
prefix?: string;
|
||||
};
|
||||
|
||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||
|
|
@ -71,6 +72,7 @@ function setDefaultValue() {
|
|||
<SelectInput
|
||||
v-model="value"
|
||||
incremental
|
||||
:id="`${prefix || 'nome'}-select-user`"
|
||||
:label
|
||||
:placeholder
|
||||
:readonly
|
||||
|
|
@ -92,7 +94,9 @@ function setDefaultValue() {
|
|||
:hide-selected="false"
|
||||
:fill-input="false"
|
||||
:rules="
|
||||
required ? [(v: string) => !!v || $t('form.error.required')] : undefined
|
||||
required && !readonly
|
||||
? [(v: string) => !!v || $t('form.error.required')]
|
||||
: undefined
|
||||
"
|
||||
@filter="filter"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,13 @@ export const createSelect = <T extends Record<string, any>>(
|
|||
let previousSearch = '';
|
||||
|
||||
watch(value, (v) => {
|
||||
if (!v || (cache && cache.find((opt) => opt[valueField] === v))) return;
|
||||
if (!v) return;
|
||||
|
||||
if (cache && cache.find((opt) => opt[valueField] === v)) {
|
||||
valueOption.value = cache.find((opt) => opt[valueField] === v);
|
||||
return;
|
||||
}
|
||||
|
||||
getSelectedOption();
|
||||
});
|
||||
|
||||
|
|
@ -63,15 +69,26 @@ export const createSelect = <T extends Record<string, any>>(
|
|||
const currentValue = value.value;
|
||||
|
||||
if (!currentValue) return;
|
||||
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
|
||||
|
||||
if (valueOption.value && valueOption.value[valueField] === currentValue) {
|
||||
return selectOptions.value.unshift(valueOption.value);
|
||||
selectOptions.value.unshift(valueOption.value);
|
||||
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
|
||||
return (
|
||||
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = await getByValue(currentValue);
|
||||
|
||||
if (ret) {
|
||||
selectOptions.value.unshift(ret);
|
||||
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
|
||||
return (
|
||||
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
|
||||
);
|
||||
});
|
||||
valueOption.value = ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,10 @@ function selectedIndex(item: any) {
|
|||
>
|
||||
<!-- NOTE: custom column will starts with # -->
|
||||
<template v-if="!col.name.startsWith('#')">
|
||||
<span>
|
||||
<span v-if="col.name === 'serviceDetail'">
|
||||
{{ props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') || '-' }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{
|
||||
typeof col.field === 'string'
|
||||
? props.row[col.field as keyof (Product | Service)]
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -143,6 +145,7 @@ function selectedIndex(item: Employee) {
|
|||
<template v-if="col.name === '#check'">
|
||||
<q-checkbox
|
||||
id="select-worker-all"
|
||||
for="select-worker-all"
|
||||
v-model="props.selected"
|
||||
@update:model-value="(v) => handleUpdate()"
|
||||
size="sm"
|
||||
|
|
@ -198,6 +201,7 @@ function selectedIndex(item: Employee) {
|
|||
v-model="props.selected"
|
||||
size="sm"
|
||||
:id="`select-worker-${props.row.firstName}`"
|
||||
:for="`select-worker-${props.row.firstName}`"
|
||||
/>
|
||||
</template>
|
||||
</q-td>
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.citizenId')"
|
||||
for="input-citizen-id"
|
||||
v-model="citizenId"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
|
|
@ -221,6 +222,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.religion')"
|
||||
for="input-religion"
|
||||
v-model="religion"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-select
|
||||
outlined
|
||||
|
|
@ -305,6 +307,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.firstName')"
|
||||
for="input-first-name"
|
||||
v-model="firstName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
@ -316,6 +319,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.lastName')"
|
||||
for="input-last-name"
|
||||
v-model="lastName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ type Props = {
|
|||
autoSave?: boolean;
|
||||
data?: Data;
|
||||
hideBtn?: boolean;
|
||||
disabledSubmit?: boolean;
|
||||
};
|
||||
|
||||
type HandleProps = {
|
||||
|
|
@ -109,6 +110,7 @@ async function change(e: Event) {
|
|||
hide-delete
|
||||
hide-btn
|
||||
edit
|
||||
:disabledSubmit
|
||||
:title
|
||||
:is-edit
|
||||
:readonly
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const currentIndexDropdownList = ref(0);
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
treeFile: { label: string; file: { label: string }[] }[];
|
||||
treeFile?: { label: string; file: { label: string }[] }[];
|
||||
readonly?: boolean;
|
||||
dropdownList?: { label: string; value: string }[];
|
||||
hideAction?: boolean;
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ onMounted(() => {
|
|||
@click="$emit('click')"
|
||||
>
|
||||
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
|
||||
<article class="col column q-pl-md">
|
||||
<span class="ellipsis full-width">
|
||||
<div class="col column q-pl-md">
|
||||
<div class="ellipsis full-width" style="max-width: 65vw !important">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="text-caption app-text-muted-2">
|
||||
{{
|
||||
uploading.loaded
|
||||
|
|
@ -79,7 +80,7 @@ onMounted(() => {
|
|||
/>
|
||||
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
|
||||
</span>
|
||||
</article>
|
||||
</div>
|
||||
<q-btn
|
||||
v-if="closeable"
|
||||
icon="mdi-close"
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ const props = withDefaults(
|
|||
readonly?: boolean;
|
||||
showTitle?: boolean;
|
||||
ocr?: (
|
||||
group: any,
|
||||
group: string,
|
||||
file: File,
|
||||
) => void | Promise<{
|
||||
) => Promise<{
|
||||
status: boolean;
|
||||
group: string;
|
||||
meta: { name: string; value: string }[];
|
||||
|
|
@ -123,7 +123,7 @@ async function change(e: Event) {
|
|||
...obj.value,
|
||||
{
|
||||
_meta: structuredClone(toRaw(selectedMenu.value)._meta || {}),
|
||||
group: selectedMenu.value?.value,
|
||||
group: selectedMenu.value?.group,
|
||||
file: renamedFile,
|
||||
},
|
||||
];
|
||||
|
|
@ -168,8 +168,8 @@ async function change(e: Event) {
|
|||
type: map['doc_type'],
|
||||
number: map['doc_number'],
|
||||
gender: map['sex'],
|
||||
firstName: map['first_name'],
|
||||
lastName: map['last_name'],
|
||||
firstName: map['last_name'],
|
||||
lastName: map['first_name'],
|
||||
issueDate: map['issue_date'],
|
||||
expireDate: map['expire_date'],
|
||||
issuePlace: map['nationality'],
|
||||
|
|
@ -327,7 +327,7 @@ defineEmits<{
|
|||
:rows="
|
||||
obj
|
||||
.filter((v) => {
|
||||
if (!autoSave && v.group !== selectedMenu?.value) {
|
||||
if (!autoSave && v.group !== selectedMenu?.group) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -198,3 +198,10 @@ i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-i
|
|||
.q-focus-helper {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
opacity: 0.6;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
src/env.d.ts
vendored
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable */
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: string;
|
||||
|
|
|
|||
112
src/i18n/eng.ts
|
|
@ -4,7 +4,7 @@ export default {
|
|||
save: 'Save',
|
||||
open: 'Open',
|
||||
close: 'Close',
|
||||
edit: 'Edit',
|
||||
edit: 'Edit{text}',
|
||||
cancel: 'Cancel',
|
||||
back: 'Back',
|
||||
undo: 'Undo',
|
||||
|
|
@ -31,6 +31,7 @@ export default {
|
|||
displayField: 'Display Fields',
|
||||
order: 'Order',
|
||||
name: '{msg} Name',
|
||||
nameEN: 'Name (English)',
|
||||
fullName: 'Full Name',
|
||||
detail: '{msg} Detail',
|
||||
remark: '{msg} Remark',
|
||||
|
|
@ -60,7 +61,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 +152,16 @@ 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',
|
||||
price: 'Price {price} Baht',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -193,12 +204,14 @@ export default {
|
|||
title: 'Manage',
|
||||
branch: 'Branch',
|
||||
personnel: 'Personnel',
|
||||
group: 'Group',
|
||||
productService: 'Product and Service',
|
||||
workflow: 'Workflow',
|
||||
property: 'Property',
|
||||
customer: 'Customer',
|
||||
mainData: 'Main Data',
|
||||
agencies: 'Agencies',
|
||||
businessType: 'Business Type',
|
||||
},
|
||||
|
||||
sales: {
|
||||
|
|
@ -245,7 +258,8 @@ export default {
|
|||
|
||||
manual: {
|
||||
title: 'Manual',
|
||||
usage: 'การใช้งาน',
|
||||
usage: 'Usage',
|
||||
troubleshooting: 'Troubleshooting',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -327,7 +341,7 @@ export default {
|
|||
requireLength: 'Please enter {msg} character',
|
||||
branchNameField: "Only letters, numbers, or the characters . , - ' &.",
|
||||
branchNameENField:
|
||||
"Only English letters, numbers, or the characters . , - ' &.",
|
||||
"Only English letters, numbers, or the characters . , - ' &. ( )",
|
||||
passportFormat: 'Please enter the passport number in the correct format.',
|
||||
},
|
||||
warning: {
|
||||
|
|
@ -374,7 +388,7 @@ export default {
|
|||
branchLabel: 'Branch',
|
||||
branchHQLabel: 'Headoffice',
|
||||
taxNo: 'Legal Person',
|
||||
contactName: 'Contact Name',
|
||||
contactName: 'Contact Person',
|
||||
},
|
||||
page: {
|
||||
captionManage: 'Manage',
|
||||
|
|
@ -395,8 +409,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',
|
||||
|
|
@ -446,10 +460,10 @@ export default {
|
|||
regisNo: 'Registration Number',
|
||||
startDate: 'Start Date',
|
||||
retireDate: 'Retire Date',
|
||||
responsibleArea: 'Responsibel Area',
|
||||
responsibleArea: 'Responsible Area',
|
||||
discount: 'Discount Condition',
|
||||
sourceNationality: 'Source Nationality',
|
||||
importNationality: 'import Nationality',
|
||||
importNationality: 'Import Nationality',
|
||||
trainingPlace: 'Training Place',
|
||||
checkpoint: 'Checkpoint',
|
||||
checkpointEN: 'Checkpoint (EN)',
|
||||
|
|
@ -457,6 +471,13 @@ 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',
|
||||
addressForeign: 'Use foreign address',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -470,10 +491,9 @@ export default {
|
|||
powerOfAttorney: 'Power of Attorney',
|
||||
others: 'Others',
|
||||
},
|
||||
|
||||
employer: 'Employer',
|
||||
employerLegalEntity: 'Legal Entity',
|
||||
employerNaturalPerson: 'Natrual Person',
|
||||
employerNaturalPerson: 'Natural Person',
|
||||
employerType: 'Employer Type',
|
||||
employee: 'Employee',
|
||||
form: {
|
||||
|
|
@ -483,24 +503,22 @@ export default {
|
|||
},
|
||||
|
||||
prefix: {
|
||||
mr: 'Mr.',
|
||||
mrs: 'Mrs.',
|
||||
miss: 'Miss.',
|
||||
mr: 'MR.',
|
||||
mrs: 'MRS.',
|
||||
miss: 'MISS.',
|
||||
},
|
||||
|
||||
taxpayyerNo: 'Taxpayer Identification Number',
|
||||
citizenId: 'Citizen ID',
|
||||
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 +526,6 @@ export default {
|
|||
registerDate: 'Registered On',
|
||||
registerCompanyName: 'Registered Name',
|
||||
authorizedCapital: 'Authorized Capital',
|
||||
|
||||
workplace: 'Workplace',
|
||||
workplaceEN: 'Workplace (EN)',
|
||||
address: 'Address',
|
||||
|
|
@ -516,7 +533,6 @@ export default {
|
|||
branchCode: 'Branch Code',
|
||||
customerCode: 'Employer Code',
|
||||
legalPersonCode: 'Legal Entity Code',
|
||||
|
||||
codeAbbrev: 'Company Abbreviation',
|
||||
codeNumber: 'Company Number',
|
||||
registeredBranch: 'Registered Branch',
|
||||
|
|
@ -554,7 +570,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',
|
||||
|
|
@ -608,7 +624,7 @@ export default {
|
|||
placeOfBirth: 'Place of Birth',
|
||||
countryOfbirth: 'Country of Birth',
|
||||
issueCountry: 'Issue Country',
|
||||
entryCount: 'Entry Count',
|
||||
entryCount: 'Number of Days in the Country',
|
||||
employerSelect: {
|
||||
branchName: 'Branch Name',
|
||||
customerName: 'Employer Name',
|
||||
|
|
@ -758,10 +774,13 @@ export default {
|
|||
},
|
||||
|
||||
quotation: {
|
||||
ownOnly: 'View Own Quotation Only',
|
||||
quotationDate: 'Quotation Date',
|
||||
seller: 'Seller',
|
||||
paymentChannels: 'Payment Channels',
|
||||
channelsThat: 'Channels That',
|
||||
refNo: 'Reference Number',
|
||||
bankAccount: 'Bank Account',
|
||||
bankAccountNumber: 'Bank Account Number',
|
||||
bankAccountName: 'Bank Account Name',
|
||||
inTheNameOf: 'In The Name Of',
|
||||
|
|
@ -792,7 +811,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 +893,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 +929,10 @@ export default {
|
|||
code: 'Agencies Code',
|
||||
group: 'Agencies Group',
|
||||
name: 'Agencies Name',
|
||||
contactName: 'Contact Person',
|
||||
contactTel: 'Contact Number',
|
||||
bankInfo: 'Bank Information',
|
||||
attachment: 'Attachment',
|
||||
},
|
||||
|
||||
requestList: {
|
||||
|
|
@ -931,8 +954,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 +1020,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 +1079,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 +1100,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?',
|
||||
|
|
@ -1101,7 +1127,7 @@ export default {
|
|||
oneOrMoreBranchMissing:
|
||||
'One or more branch cannot be delete and is missing.',
|
||||
cantMakeHQAndBranchSameTime:
|
||||
'Cannot make this as headquaters and branch at the same time.',
|
||||
'Cannot make this as headquarters and branch at the same time.',
|
||||
unknowHowToVerify: 'Unknown how to verify identity.',
|
||||
noPermission:
|
||||
'You do not have permission to access or perform with this resource.',
|
||||
|
|
@ -1183,13 +1209,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.',
|
||||
|
|
@ -1207,6 +1234,9 @@ export default {
|
|||
taskListNotPending: 'One or more task is not pending.',
|
||||
reqNotMet: 'Not Match',
|
||||
systemError: 'A system error occurred.',
|
||||
taskOrderInvalid: 'Please select the product and the organization.',
|
||||
|
||||
flowAccountProductIdNotFound: 'Product not found in flow account',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -1476,4 +1506,26 @@ 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',
|
||||
},
|
||||
|
||||
businessType: {
|
||||
title: 'Business Type',
|
||||
caption: 'Manage Business Type',
|
||||
name: 'Business Type Name',
|
||||
nameEn: 'Business Type Name (English)',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export default {
|
|||
save: 'บันทึก',
|
||||
open: 'เปิด',
|
||||
close: 'ปิด',
|
||||
edit: 'แก้ไข',
|
||||
edit: 'แก้ไข{text}',
|
||||
cancel: 'ยกเลิก',
|
||||
back: 'ย้อนกลับ',
|
||||
undo: 'ย้อนกลับ',
|
||||
|
|
@ -31,6 +31,7 @@ export default {
|
|||
displayField: 'ฟิลด์แสดงผล',
|
||||
order: 'ลำดับ',
|
||||
name: 'ชื่อ{msg}',
|
||||
nameEN: 'ชื่อ (ภาษาอังกฤษ)',
|
||||
fullName: 'ชื่อ-สกุล',
|
||||
detail: 'รายละเอียด{msg}',
|
||||
remark: 'หมายเหตุ{msg}',
|
||||
|
|
@ -151,6 +152,16 @@ export default {
|
|||
dueDate: 'วันครบกำหนด',
|
||||
year: 'ปี',
|
||||
tableOfContent: 'สารบัญ',
|
||||
draw: 'วาด',
|
||||
newUpload: 'อัปโหลดใหม่',
|
||||
nativeLanguage: '{msg} ภาษาต้นทาง',
|
||||
copy: 'คัดลอก',
|
||||
paste: 'วาง',
|
||||
period: 'ช่วงเวลา',
|
||||
documentStatus: 'สถานะเอกสาร',
|
||||
advanceSearch: 'ค้นหาขั้นสูง',
|
||||
totalPeople: '{meg} คน',
|
||||
price: 'ราคา {price} บาท',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -193,12 +204,14 @@ export default {
|
|||
title: 'จัดการ',
|
||||
branch: 'สาขา',
|
||||
personnel: 'บุคลากร',
|
||||
group: 'กลุ่ม',
|
||||
productService: 'สินค้าและบริการ',
|
||||
workflow: 'ขั้นตอนการทำงาน',
|
||||
property: 'คุณสมบัติ',
|
||||
customer: 'ลูกค้า',
|
||||
mainData: 'ข้อมูลหลัก',
|
||||
agencies: 'หน่วยงาน',
|
||||
businessType: 'ประเภทกิจการ',
|
||||
},
|
||||
|
||||
sales: {
|
||||
|
|
@ -246,6 +259,7 @@ export default {
|
|||
manual: {
|
||||
title: 'คู่มือ',
|
||||
usage: 'การใช้งาน',
|
||||
troubleshooting: 'การแก้ปัญหา',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -324,7 +338,7 @@ export default {
|
|||
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
|
||||
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
|
||||
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
|
||||
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
|
||||
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
|
||||
branchNameENField:
|
||||
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
|
||||
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
|
||||
|
|
@ -453,6 +467,13 @@ export default {
|
|||
citizenId: 'เลขที่บัตรประชาชน',
|
||||
citizenIssue: 'วันที่ออกบัตร',
|
||||
citizenExpire: 'วันที่หมดอายุ',
|
||||
agencyStatus: 'สถานะเอเจนซี่',
|
||||
normal: 'ปกติ',
|
||||
canceled: 'ยกเลิก',
|
||||
blacklist: 'แบล็คลิสต์',
|
||||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
|
||||
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -479,15 +500,16 @@ export default {
|
|||
},
|
||||
|
||||
prefix: {
|
||||
mr: 'Mr.',
|
||||
mrs: 'Mrs.',
|
||||
miss: 'Miss.',
|
||||
mr: 'นาย',
|
||||
mrs: 'นาง',
|
||||
miss: 'นางสาว',
|
||||
},
|
||||
|
||||
citizenId: 'บัตรประจำตัวประชาชน',
|
||||
religion: 'ศาสนา',
|
||||
issueDate: 'วันที่ออกหนังสือ',
|
||||
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
|
||||
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
|
||||
|
||||
ownerName: 'ชื่อนายจ้าง',
|
||||
firstName: 'ชื่อ ',
|
||||
|
|
@ -571,7 +593,7 @@ export default {
|
|||
family: 'ข้อมูลครอบครัว',
|
||||
},
|
||||
workerStatus: 'สถานะคนงาน',
|
||||
previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
|
||||
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า',
|
||||
employerBranch: 'สาขานายจ้าง',
|
||||
employeeCode: 'รหัสลูกจ้าง',
|
||||
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
|
||||
|
|
@ -604,7 +626,7 @@ export default {
|
|||
placeOfBirth: 'สถานที่เกิด',
|
||||
countryOfbirth: 'ประเทศที่เกิด',
|
||||
issueCountry: 'ประเทศที่ออก',
|
||||
entryCount: 'จำนวนที่เข้าประเทศ',
|
||||
entryCount: 'จำนวนวันที่เข้าประเทศ',
|
||||
employerSelect: {
|
||||
branchName: 'ชื่อสาขา',
|
||||
customerName: 'ชื่อนายจ้าง',
|
||||
|
|
@ -632,7 +654,7 @@ export default {
|
|||
permitIssuedAt: 'สถานที่ออกใบอนุญาต',
|
||||
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
|
||||
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
|
||||
identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
|
||||
identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)',
|
||||
},
|
||||
formFamily: {
|
||||
citizenId:
|
||||
|
|
@ -750,10 +772,13 @@ export default {
|
|||
},
|
||||
|
||||
quotation: {
|
||||
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
|
||||
quotationDate: 'วันที่ใบเสนอราคา',
|
||||
seller: 'ผู้ขาย',
|
||||
paymentChannels: 'ช่องทางชำระเงิน',
|
||||
channelsThat: 'ช่องทางที่',
|
||||
refNo: 'เลขที่อ้างอิง',
|
||||
bankAccount: 'บัญชีธนาคาร',
|
||||
bankAccountNumber: 'เลขบัญชีธนาคาร',
|
||||
bankAccountName: 'ชื่อบัญชี',
|
||||
inTheNameOf: 'ในนาม',
|
||||
|
|
@ -901,6 +926,10 @@ export default {
|
|||
code: 'รหัสหน่วยงาน',
|
||||
group: 'กลุ่มหน่วยงาน',
|
||||
name: 'ชื่อหน่วยงาน',
|
||||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
contactTel: 'เบอร์โทรผู้ติดต่อ',
|
||||
bankInfo: 'ข้อมูลธนาคาร',
|
||||
attachment: 'เอกสารเพิ่มเติม',
|
||||
},
|
||||
|
||||
requestList: {
|
||||
|
|
@ -922,6 +951,7 @@ export default {
|
|||
nonLocalEmployee: 'พนักงานนอกพื้นที่',
|
||||
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
|
||||
salesRepresentative: 'พนักงานขาย',
|
||||
dataOffice: 'สำนักงานเขตจัดหางาน',
|
||||
ref: 'อ้างอิง',
|
||||
action: {
|
||||
title: 'จัดการ',
|
||||
|
|
@ -1040,6 +1070,9 @@ export default {
|
|||
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
|
||||
},
|
||||
message: {
|
||||
copy: 'คัดลอก',
|
||||
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
|
||||
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
|
||||
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
|
||||
beingUse: '"{msg}" มีการใช้งานอยู่',
|
||||
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
|
||||
|
|
@ -1161,13 +1194,14 @@ export default {
|
|||
'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น',
|
||||
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
|
||||
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
|
||||
samePropertyNameExists: 'คุณสมบัตินี้มีอยู่ในระบบอยู่แล้ว',
|
||||
|
||||
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
|
||||
codeMisMatch: 'รหัสไม่ตรงกัน',
|
||||
crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้',
|
||||
errorOccure:
|
||||
errorOccurred:
|
||||
'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง',
|
||||
invalideData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
|
||||
invalidData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
|
||||
authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง',
|
||||
installmentsValidateFailed:
|
||||
'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ',
|
||||
|
|
@ -1186,6 +1220,8 @@ export default {
|
|||
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
|
||||
reqNotMet: 'ไม่ตรงกัน',
|
||||
systemError: 'ระบบเกิดข้อผิดพลาด',
|
||||
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
|
||||
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -1457,4 +1493,26 @@ export default {
|
|||
type: 'ประเภท',
|
||||
},
|
||||
},
|
||||
|
||||
dateRange: {
|
||||
today: 'วันนี้',
|
||||
yesterday: 'เมื่อวานนี้',
|
||||
thisWeek: 'สัปดาห์นี้',
|
||||
lastWeek: 'สัปดาห์ที่แล้ว',
|
||||
thisMonth: 'เดือนนี้',
|
||||
lastMonth: 'เดือนที่แล้ว',
|
||||
thisYear: 'ปีนี้',
|
||||
lastYear: 'ปีที่แล้ว',
|
||||
last7Days: '7 วันที่ผ่านมา',
|
||||
last30Days: '30 วันที่ผ่านมา',
|
||||
last90Days: '90 วันที่ผ่านมา',
|
||||
customDateRange: 'กำหนดช่วงวันที่เอง',
|
||||
},
|
||||
|
||||
businessType: {
|
||||
title: 'ประเภทกิจการ',
|
||||
caption: 'จัดการประเภทกิจการ',
|
||||
name: 'ชื่อประเภทกิจการ',
|
||||
nameEn: 'ชื่อประเภทกิจการ (ภาษาอังกฤษ)',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import useMyBranch from 'stores/my-branch';
|
|||
import { getUserId, getRole } from 'src/services/keycloak';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { canAccess } from 'src/stores/utils';
|
||||
|
||||
type Menu = {
|
||||
label: string;
|
||||
|
|
@ -70,39 +71,45 @@ function initMenu() {
|
|||
{
|
||||
label: 'branch',
|
||||
route: '/branch-management',
|
||||
hidden: !(
|
||||
role.value.includes('admin') ||
|
||||
role.value.includes('branch_manager') ||
|
||||
role.value.includes('head_of_admin') ||
|
||||
role.value.includes('system') ||
|
||||
role.value.includes('owner') ||
|
||||
role.value.includes('head_of_account')
|
||||
),
|
||||
hidden: !canAccess('branch'),
|
||||
},
|
||||
{
|
||||
label: 'personnel',
|
||||
route: '/personnel-management',
|
||||
hidden: !(
|
||||
role.value.includes('admin') ||
|
||||
role.value.includes('head_of_admin') ||
|
||||
role.value.includes('system') ||
|
||||
role.value.includes('owner') ||
|
||||
role.value.includes('branch_manager')
|
||||
),
|
||||
hidden: !canAccess('personnel'),
|
||||
},
|
||||
{
|
||||
label: 'group',
|
||||
route: '/group-management',
|
||||
hidden: !canAccess('personnel'),
|
||||
},
|
||||
{
|
||||
label: 'workflow',
|
||||
route: '/workflow',
|
||||
hidden: !canAccess('workflow'),
|
||||
},
|
||||
{ label: 'workflow', route: '/workflow' },
|
||||
{
|
||||
label: 'property',
|
||||
route: '/property',
|
||||
hidden: !(
|
||||
role.value.includes('admin') ||
|
||||
role.value.includes('head_of_admin') ||
|
||||
role.value.includes('system')
|
||||
),
|
||||
hidden: !canAccess('workflow'),
|
||||
},
|
||||
{
|
||||
label: 'businessType',
|
||||
route: '/business-type',
|
||||
},
|
||||
{
|
||||
label: 'productService',
|
||||
route: '/product-service',
|
||||
},
|
||||
{
|
||||
label: 'customer',
|
||||
route: '/customer-management',
|
||||
hidden: !canAccess('customer'),
|
||||
},
|
||||
{
|
||||
label: 'agencies',
|
||||
route: '/agencies-management',
|
||||
},
|
||||
{ label: 'productService', route: '/product-service' },
|
||||
{ label: 'customer', route: '/customer-management' },
|
||||
{ label: 'agencies', route: '/agencies-management' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -153,7 +160,11 @@ function initMenu() {
|
|||
icon: 'mdi-monitor-dashboard',
|
||||
children: [
|
||||
{ label: 'report', route: '/report' },
|
||||
{ label: 'dashboard', route: '/dash-board' },
|
||||
{
|
||||
label: 'dashboard',
|
||||
route: '/dash-board',
|
||||
hidden: !canAccess('dashBoard'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
@ -163,7 +174,11 @@ function initMenu() {
|
|||
children: [
|
||||
{
|
||||
label: 'usage',
|
||||
route: `/manual`,
|
||||
route: '/manual',
|
||||
},
|
||||
{
|
||||
label: 'troubleshooting',
|
||||
route: '/troubleshooting',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -250,7 +265,13 @@ onMounted(async () => {
|
|||
</header>
|
||||
|
||||
<div id="drawer-menu" class="q-pl-md q-mr-xs q-gutter-y-sm">
|
||||
<template v-for="(menu, i) in menuData" :key="i">
|
||||
<template
|
||||
v-for="(menu, i) in menuData.filter(
|
||||
(v) =>
|
||||
!(v.children?.length === 0 || v.children?.every((i) => i.hidden)),
|
||||
)"
|
||||
:key="i"
|
||||
>
|
||||
<q-expansion-item
|
||||
v-if="!menu.hidden"
|
||||
:id="menu.label"
|
||||
|
|
@ -444,8 +465,9 @@ onMounted(async () => {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<!-- v-if="!mini" -->
|
||||
<q-btn
|
||||
v-if="!mini"
|
||||
v-if="false"
|
||||
dense
|
||||
flat
|
||||
rounded
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { ref, onMounted, computed, reactive } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { getUserId, getUsername, logout, getRole } from 'src/services/keycloak';
|
||||
import { getUserId, getUsername, getName, logout, getRole } from 'src/services/keycloak';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import moment from 'moment';
|
||||
|
|
@ -39,7 +39,7 @@ const configStore = useConfigStore();
|
|||
const { data: notificationData } = storeToRefs(notificationStore);
|
||||
|
||||
const { visible } = storeToRefs(loaderStore);
|
||||
const { t } = useI18n({ useScope: 'global' });
|
||||
const { t, locale } = useI18n({ useScope: 'global' });
|
||||
const userStore = useUserStore();
|
||||
|
||||
const canvasModal = ref(false);
|
||||
|
|
@ -50,11 +50,16 @@ 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 userName = ref({ th: '', en: '' });
|
||||
const canvasRef = ref();
|
||||
|
||||
const displayName = computed(() => {
|
||||
if (!userName.value.th && !userName.value.en) return getName() || 'Guest';
|
||||
return locale.value === 'eng' ? userName.value.en : userName.value.th;
|
||||
});
|
||||
|
||||
const language: {
|
||||
value: Lang;
|
||||
label: string;
|
||||
|
|
@ -124,6 +129,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();
|
||||
|
|
@ -151,9 +167,14 @@ onMounted(async () => {
|
|||
if (user === 'admin') return;
|
||||
if (uid) {
|
||||
const res = await userStore.fetchById(uid);
|
||||
if (res && res.gender) {
|
||||
userGender.value = res.gender;
|
||||
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
|
||||
if (res) {
|
||||
if (res.gender) {
|
||||
userGender.value = res.gender;
|
||||
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
|
||||
}
|
||||
// เก็บชื่อทั้งสองภาษา
|
||||
userName.value.th = `${res.firstName || ''} ${res.lastName || ''}`.trim();
|
||||
userName.value.en = `${res.firstNameEN || ''} ${res.lastNameEN || ''}`.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -367,12 +388,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 +419,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
|
||||
|
|
@ -467,6 +495,7 @@ onMounted(async () => {
|
|||
<!-- User -->
|
||||
<ProfileMenu
|
||||
id="btn-profile-menu"
|
||||
:user-name="displayName"
|
||||
@logout="doLogout"
|
||||
@edit-personal-info="console.log('edit')"
|
||||
@signature="
|
||||
|
|
@ -495,13 +524,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();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const filterRole = ref<string[]>();
|
|||
defineProps<{
|
||||
userImage?: string;
|
||||
gender?: string;
|
||||
userName?: string;
|
||||
}>();
|
||||
|
||||
const inputFile = document.createElement('input');
|
||||
|
|
@ -31,7 +32,7 @@ const options = [
|
|||
label: 'menu.profile.signature',
|
||||
value: 'signature',
|
||||
color: 'grey',
|
||||
disabled: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
icon: 'mdi-brightness-6',
|
||||
|
|
@ -147,9 +148,9 @@ onMounted(async () => {
|
|||
class="text-weight-bold ellipsis"
|
||||
style="max-width: 9vw"
|
||||
>
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
<q-tooltip>
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
</q-tooltip>
|
||||
</span>
|
||||
<span
|
||||
|
|
@ -234,12 +235,12 @@ onMounted(async () => {
|
|||
style="margin-top: 58px"
|
||||
>
|
||||
<span v-if="isLoggedIn()">
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
</span>
|
||||
<span v-else>{{ 'Guest' }}</span>
|
||||
<q-tooltip>
|
||||
<span v-if="isLoggedIn()">
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
</span>
|
||||
<span v-else>{{ 'Guest' }}</span>
|
||||
</q-tooltip>
|
||||
|
|
|
|||
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,44 @@ 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, useRouter } from 'vue-router';
|
||||
|
||||
// NOTE: Variable
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
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 : [];
|
||||
if (
|
||||
res.length &&
|
||||
res.length === 1 &&
|
||||
res[0].page &&
|
||||
res[0].page.length === 1
|
||||
) {
|
||||
router.replace(
|
||||
`/troubleshooting/${res[0].category}/${res[0].page[0].name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -34,7 +56,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 +80,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, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,8 +116,8 @@ async function scrollTo(id: string) {
|
|||
|
||||
<template>
|
||||
<main
|
||||
class="full-height q-gutter-sm"
|
||||
:class="{ 'row reverse': $q.screen.gt.xs, column: $q.screen.xs }"
|
||||
class="full-height q-gutter-sm no-wrap"
|
||||
:class="{ column: !toc && $q.screen.lt.md, 'row reverse': $q.screen.gt.sm }"
|
||||
>
|
||||
<section
|
||||
v-if="toc"
|
||||
|
|
@ -156,7 +168,7 @@ async function scrollTo(id: string) {
|
|||
</q-list>
|
||||
</section>
|
||||
|
||||
<section v-if="!toc && $q.screen.xs">
|
||||
<section v-if="!toc && $q.screen.lt.md">
|
||||
<q-btn
|
||||
dense
|
||||
class="full-width text-capitalize"
|
||||
|
|
@ -169,7 +181,7 @@ async function scrollTo(id: string) {
|
|||
</section>
|
||||
|
||||
<section
|
||||
v-if="content || (!toc && $q.screen.xs)"
|
||||
v-if="$q.screen.gt.xs || (!toc && $q.screen.xs)"
|
||||
ref="wrapper"
|
||||
class="markdown col scroll full-height rounded"
|
||||
>
|
||||
|
|
@ -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/`,
|
||||
),
|
||||
)
|
||||
"
|
||||
|
|
@ -209,6 +223,38 @@ async function scrollTo(id: string) {
|
|||
padding-block: 1rem !important;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
counter-set: h1 0;
|
||||
counter-reset: h1;
|
||||
}
|
||||
|
||||
.markdown :deep(h1) {
|
||||
counter-reset: h2;
|
||||
}
|
||||
|
||||
.markdown :deep(h2) {
|
||||
counter-reset: h3;
|
||||
}
|
||||
|
||||
.markdown :deep(h3) {
|
||||
counter-reset: h4;
|
||||
}
|
||||
|
||||
.markdown :deep(h2:before) {
|
||||
counter-increment: h2;
|
||||
content: counter(h2) '. ';
|
||||
}
|
||||
|
||||
.markdown :deep(h3:before) {
|
||||
counter-increment: h3;
|
||||
content: counter(h2) '.' counter(h3) ' ';
|
||||
}
|
||||
|
||||
.markdown :deep(h4:before) {
|
||||
counter-increment: h4;
|
||||
content: counter(h2) '.' counter(h3) '.' counter(h4) ' ';
|
||||
}
|
||||
|
||||
.markdown :deep(blockquote) {
|
||||
background-color: var(--surface-2);
|
||||
border-radius: 8px;
|
||||
|
|
@ -282,7 +328,31 @@ async function scrollTo(id: string) {
|
|||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.markdown :deep(h4) {
|
||||
text-align: left;
|
||||
margin-block: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
|
|
|||
|
|
@ -94,21 +94,22 @@ onMounted(async () => {
|
|||
<template>
|
||||
<main class="column full-height no-wrap">
|
||||
<div class="surface-1 col bordered rounded column">
|
||||
<div class="q-px-lg q-py-xs row items-center">
|
||||
<div class="q-py-xs row items-center" style="padding-inline: 38px">
|
||||
<q-checkbox
|
||||
size="xs"
|
||||
:model-value="selectedNoti.length === noti.length"
|
||||
class="q-px-sm"
|
||||
:model-value="noti.length > 0 && selectedNoti.length === noti.length"
|
||||
:disable="noti.length === 0"
|
||||
@click="toggleSelection('', true)"
|
||||
/>
|
||||
<q-separator vertical inset spaced="md" />
|
||||
<q-btn
|
||||
v-if="selectedNoti.length === 0"
|
||||
icon="mdi-refresh"
|
||||
rounded
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
class="app-text-muted-2 q-ml-sm q-mt-xs"
|
||||
size="sm"
|
||||
class="app-text-muted-2 q-mt-xs"
|
||||
@click="async () => await fetchNoti()"
|
||||
>
|
||||
<q-tooltip>Refresh</q-tooltip>
|
||||
|
|
@ -119,8 +120,8 @@ onMounted(async () => {
|
|||
rounded
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
class="app-text-muted-2 q-ml-sm"
|
||||
size="sm"
|
||||
class="app-text-muted-2"
|
||||
@click="async () => await deleteNoti()"
|
||||
>
|
||||
<q-tooltip>{{ $t('general.delete') }}</q-tooltip>
|
||||
|
|
@ -131,7 +132,7 @@ onMounted(async () => {
|
|||
rounded
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
size="sm"
|
||||
class="app-text-muted-2 q-mx-sm"
|
||||
@click="async () => await markAsRead()"
|
||||
>
|
||||
|
|
@ -177,7 +178,13 @@ onMounted(async () => {
|
|||
<q-badge
|
||||
rounded
|
||||
class="q-ml-md"
|
||||
style="background: hsl(var(--info-bg))"
|
||||
:color="
|
||||
(tab.value === 'all'
|
||||
? noti.length
|
||||
: noti.filter((v) => !v.read).length) > 0
|
||||
? 'info'
|
||||
: 'grey'
|
||||
"
|
||||
>
|
||||
{{
|
||||
tab.value === 'all'
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ 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';
|
||||
import { isRoleInclude } from 'stores/utils';
|
||||
import { isRoleInclude, canAccess } from 'stores/utils';
|
||||
import {
|
||||
BranchWithChildren,
|
||||
BranchCreate,
|
||||
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
@ -781,6 +791,8 @@ async function onSubmit(submitSelectedItem?: boolean) {
|
|||
);
|
||||
|
||||
if (!res) return;
|
||||
|
||||
formType.value = 'view';
|
||||
formData.value.codeHeadOffice = formData.value.code = res.code;
|
||||
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
|
||||
|
||||
|
|
@ -855,7 +867,9 @@ async function onSubmit(submitSelectedItem?: boolean) {
|
|||
actionText: t('dialog.action.ok'),
|
||||
persistent: true,
|
||||
title: t('form.warning.title'),
|
||||
cancel: () => {},
|
||||
cancel: () => {
|
||||
formType.value = 'create';
|
||||
},
|
||||
action: async () => {
|
||||
await createBranch();
|
||||
},
|
||||
|
|
@ -1036,7 +1050,7 @@ watch(currentHq, () => {
|
|||
{{ $t('branch.allBranch') }}
|
||||
</div>
|
||||
<q-btn
|
||||
v-if="isRoleInclude(['head_of_admin', 'admin', 'system'])"
|
||||
v-if="isRoleInclude(['system'])"
|
||||
round
|
||||
flat
|
||||
size="md"
|
||||
|
|
@ -1060,6 +1074,7 @@ watch(currentHq, () => {
|
|||
<div class="col full-width scroll">
|
||||
<div class="q-pa-md">
|
||||
<TreeComponent
|
||||
:hide-create="!canAccess('branch', 'create')"
|
||||
v-model:nodes="treeData"
|
||||
v-model:expanded-tree="expandedTree"
|
||||
node-key="id"
|
||||
|
|
@ -1170,26 +1185,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
|
||||
|
|
@ -1524,8 +1562,18 @@ watch(currentHq, () => {
|
|||
</q-td>
|
||||
<q-td>
|
||||
<KebabAction
|
||||
v-if="
|
||||
!currentHq.id
|
||||
? canAccess('branch', 'create')
|
||||
: true
|
||||
"
|
||||
:status="props.row.status"
|
||||
:idName="props.row.name"
|
||||
:hide-delete="
|
||||
!currentHq.id
|
||||
? !isRoleInclude(['system'])
|
||||
: !canAccess('branch', 'create')
|
||||
"
|
||||
@view="
|
||||
if (props.row.isHeadOffice) {
|
||||
triggerEdit(
|
||||
|
|
@ -1665,8 +1713,18 @@ watch(currentHq, () => {
|
|||
>
|
||||
<template v-slot:action>
|
||||
<KebabAction
|
||||
v-if="
|
||||
!currentHq.id
|
||||
? canAccess('branch', 'create')
|
||||
: true
|
||||
"
|
||||
:status="props.row.status"
|
||||
:idName="props.row.name"
|
||||
:hide-delete="
|
||||
!currentHq.id
|
||||
? !isRoleInclude(['system'])
|
||||
: !canAccess('branch', 'create')
|
||||
"
|
||||
@view="
|
||||
if (props.row.isHeadOffice) {
|
||||
triggerEdit(
|
||||
|
|
@ -2214,13 +2272,22 @@ watch(currentHq, () => {
|
|||
@click="drawerEdit()"
|
||||
type="button"
|
||||
/>
|
||||
<DeleteButton
|
||||
v-if="formType !== 'edit'"
|
||||
id="btn-info-basic-delete"
|
||||
icon-only
|
||||
@click="triggerDelete(currentEdit.id)"
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<template
|
||||
v-if="
|
||||
formType !== 'edit' && formTypeBranch === 'headOffice'
|
||||
? isRoleInclude(['system'])
|
||||
: canAccess('branch', 'create')
|
||||
"
|
||||
>
|
||||
<DeleteButton
|
||||
v-if="formType !== 'edit'"
|
||||
id="btn-info-basic-delete"
|
||||
icon-only
|
||||
@click="triggerDelete(currentEdit.id)"
|
||||
type="button"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
67
src/pages/02_group-management/MainPage.vue
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { getInstance } from 'src/services/keycloak';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { initLang } from 'src/utils/ui';
|
||||
|
||||
const $q = useQuasar();
|
||||
const { locale } = useI18n();
|
||||
const navigatorStore = useNavigator();
|
||||
const EDM_SERVICE = import.meta.env.VITE_EDM_MICRO_FRONTEND_URL;
|
||||
const kc = getInstance();
|
||||
const at = ref(kc.token);
|
||||
const rt = ref(kc.refreshToken);
|
||||
const iframe = ref<InstanceType<typeof HTMLIFrameElement>>();
|
||||
|
||||
function sendMessage() {
|
||||
iframe?.value?.contentWindow?.postMessage(
|
||||
{
|
||||
i18n: locale.value,
|
||||
darkMode: $q.dark.isActive,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initLang();
|
||||
currentLocale.value = locale.value;
|
||||
navigatorStore.current.title = 'menu.dms';
|
||||
navigatorStore.current.path = [
|
||||
{
|
||||
text: '',
|
||||
i18n: true,
|
||||
handler: () => {},
|
||||
},
|
||||
];
|
||||
sendMessage();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => kc.token,
|
||||
() => {
|
||||
at.value = kc.token;
|
||||
rt.value = kc.refreshToken;
|
||||
},
|
||||
);
|
||||
|
||||
watch([locale, $q.dark], () => {
|
||||
sendMessage();
|
||||
});
|
||||
|
||||
const currentLocale = ref(locale.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe
|
||||
ref="iframe"
|
||||
:src="`${EDM_SERVICE}/user?at=${at}&rt=${rt}&lang=${currentLocale}`"
|
||||
frameborder="0"
|
||||
class="full-width full-height rounded"
|
||||
allowtransparency="true"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -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,18 @@ const defaultFormData = {
|
|||
citizenExpire: null,
|
||||
citizenIssue: null,
|
||||
citizenId: '',
|
||||
contactName: '',
|
||||
contactTel: '',
|
||||
remark: '',
|
||||
agencyStatus: '',
|
||||
addressForeign: false,
|
||||
provinceText: null,
|
||||
districtText: null,
|
||||
subDistrictText: null,
|
||||
provinceTextEN: null,
|
||||
districtTextEN: null,
|
||||
subDistrictTextEN: null,
|
||||
zipCodeText: null,
|
||||
};
|
||||
|
||||
const formData = ref<UserCreate>({
|
||||
|
|
@ -172,7 +186,7 @@ const formData = ref<UserCreate>({
|
|||
streetEN: '',
|
||||
street: '',
|
||||
trainingPlace: null,
|
||||
importNationality: null,
|
||||
importNationality: [],
|
||||
sourceNationality: null,
|
||||
licenseExpireDate: null,
|
||||
licenseIssueDate: null,
|
||||
|
|
@ -199,6 +213,18 @@ const formData = ref<UserCreate>({
|
|||
citizenExpire: null,
|
||||
citizenIssue: null,
|
||||
citizenId: '',
|
||||
contactName: '',
|
||||
contactTel: '',
|
||||
remark: '',
|
||||
agencyStatus: '',
|
||||
addressForeign: false,
|
||||
provinceText: null,
|
||||
districtText: null,
|
||||
subDistrictText: null,
|
||||
provinceTextEN: null,
|
||||
districtTextEN: null,
|
||||
subDistrictTextEN: null,
|
||||
zipCodeText: null,
|
||||
});
|
||||
|
||||
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
||||
|
|
@ -327,7 +353,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 +362,8 @@ function onClose(excludeDialog?: boolean) {
|
|||
mapUserType(currentTab.value);
|
||||
imageList.value = { selectedImage: '', list: [] };
|
||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
||||
userFileList.value = [];
|
||||
userFile.value = [];
|
||||
flowStore.rotate();
|
||||
}
|
||||
|
||||
|
|
@ -356,12 +384,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 +445,37 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
: '';
|
||||
const formDataEdit = {
|
||||
...formData.value,
|
||||
checkpointEN: formData.value.checkpoint,
|
||||
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE',
|
||||
provinceId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.provinceId,
|
||||
districtId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.districtId,
|
||||
subDistrictId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.subDistrictId,
|
||||
|
||||
provinceText: formData.value.addressForeign
|
||||
? formData.value.provinceId
|
||||
: null,
|
||||
districtText: formData.value.addressForeign
|
||||
? formData.value.districtId
|
||||
: null,
|
||||
subDistrictText: formData.value.addressForeign
|
||||
? formData.value.subDistrictId
|
||||
: null,
|
||||
zipCodeText: formData.value.addressForeign
|
||||
? formData.value.zipCode
|
||||
: null,
|
||||
} 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 +498,39 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
: hqId.value
|
||||
? hqId.value
|
||||
: '';
|
||||
formData.value.checkpointEN = formData.value.checkpoint;
|
||||
const result = await userStore.create(
|
||||
formData.value,
|
||||
{
|
||||
...formData.value,
|
||||
provinceId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.provinceId,
|
||||
districtId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.districtId,
|
||||
subDistrictId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.subDistrictId,
|
||||
|
||||
provinceText: formData.value.addressForeign
|
||||
? formData.value.provinceId
|
||||
: null,
|
||||
districtText: formData.value.addressForeign
|
||||
? formData.value.districtId
|
||||
: null,
|
||||
subDistrictText: formData.value.addressForeign
|
||||
? formData.value.subDistrictId
|
||||
: null,
|
||||
zipCodeText: formData.value.addressForeign
|
||||
? formData.value.zipCode
|
||||
: null,
|
||||
},
|
||||
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) {
|
||||
|
|
@ -551,12 +622,20 @@ async function assignFormData(idEdit: string) {
|
|||
currentUser.value = foundUser;
|
||||
formData.value = {
|
||||
branchId: foundUser.branch[0]?.id,
|
||||
provinceId: foundUser.provinceId,
|
||||
districtId: foundUser.districtId,
|
||||
subDistrictId: foundUser.subDistrictId,
|
||||
provinceId: foundUser.addressForeign
|
||||
? foundUser.provinceText
|
||||
: foundUser.provinceId,
|
||||
districtId: foundUser.addressForeign
|
||||
? foundUser.districtText
|
||||
: foundUser.districtId,
|
||||
subDistrictId: foundUser.addressForeign
|
||||
? foundUser.subDistrictText
|
||||
: foundUser.subDistrictId,
|
||||
telephoneNo: foundUser.telephoneNo,
|
||||
email: foundUser.email,
|
||||
zipCode: foundUser.zipCode,
|
||||
zipCode: foundUser.addressForeign
|
||||
? foundUser.zipCodeText
|
||||
: foundUser.zipCode,
|
||||
gender: foundUser.gender,
|
||||
addressEN: foundUser.addressEN,
|
||||
address: foundUser.address,
|
||||
|
|
@ -567,7 +646,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 +669,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 +687,12 @@ 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 || '',
|
||||
addressForeign: foundUser.addressForeign || false,
|
||||
provinceTextEN: foundUser.provinceTextEN,
|
||||
districtTextEN: foundUser.districtTextEN,
|
||||
subDistrictTextEN: foundUser.subDistrictTextEN,
|
||||
};
|
||||
|
||||
formData.value.status === 'ACTIVE' || 'CREATED'
|
||||
|
|
@ -661,6 +751,8 @@ async function fetchUserList(mobileFetch?: boolean) {
|
|||
: statusFilter.value === 'statusACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
startDate: searchDate.value[0],
|
||||
endDate: searchDate.value[1],
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -727,7 +819,17 @@ watch(
|
|||
|
||||
watch(
|
||||
() => formData.value.userType,
|
||||
async () => {
|
||||
async (type) => {
|
||||
if (type !== 'AGENCY') {
|
||||
formData.value.addressForeign = false;
|
||||
formData.value.provinceId = null;
|
||||
formData.value.districtId = null;
|
||||
formData.value.subDistrictId = null;
|
||||
formData.value.provinceTextEN = null;
|
||||
formData.value.districtTextEN = null;
|
||||
formData.value.subDistrictTextEN = null;
|
||||
formData.value.zipCodeText = null;
|
||||
}
|
||||
if (!infoDrawerEdit.value) return;
|
||||
formData.value.registrationNo = null;
|
||||
formData.value.startDate = null;
|
||||
|
|
@ -735,11 +837,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 +852,7 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
watch([inputSearch, statusFilter, pageSize], async () => {
|
||||
watch([inputSearch, statusFilter, pageSize, searchDate], async () => {
|
||||
if (userData.value) userData.value.result = [];
|
||||
currentPage.value = 1;
|
||||
|
||||
|
|
@ -872,26 +974,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
|
||||
|
|
@ -1241,7 +1362,7 @@ watch(
|
|||
{{
|
||||
locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName}`.trim()
|
||||
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
|
||||
}}
|
||||
<q-tooltip
|
||||
anchor="bottom left"
|
||||
|
|
@ -1251,7 +1372,7 @@ watch(
|
|||
{{
|
||||
locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName}`.trim()
|
||||
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
|
||||
}}
|
||||
</q-tooltip>
|
||||
|
||||
|
|
@ -1517,9 +1638,12 @@ watch(
|
|||
hide-action
|
||||
:is-edit="infoDrawerEdit"
|
||||
:title="
|
||||
locale === 'eng'
|
||||
(currentUser.namePrefix
|
||||
? $t('customer.form.prefix.' + currentUser.namePrefix) + ' '
|
||||
: '') +
|
||||
(locale === 'eng'
|
||||
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
|
||||
: `${currentUser.firstName} ${currentUser.lastName}`
|
||||
: `${currentUser.firstName || currentUser.firstNameEN} ${currentUser.lastName || currentUser.lastNameEN}`)
|
||||
"
|
||||
v-model:drawerOpen="infoDrawer"
|
||||
:submit="() => onSubmit()"
|
||||
|
|
@ -1551,7 +1675,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 || formData.firstNameEN,
|
||||
lastName: formData.lastName || formData.lastNameEN,
|
||||
firstNameEN: formData.firstNameEN,
|
||||
lastNameEN: formData.lastNameEN,
|
||||
},
|
||||
{ locale },
|
||||
)
|
||||
"
|
||||
:caption="userCode"
|
||||
:img="
|
||||
`${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat(
|
||||
|
|
@ -1736,12 +1871,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"
|
||||
/>
|
||||
|
||||
|
|
@ -1759,10 +1897,15 @@ watch(
|
|||
v-model:district-id="formData.districtId"
|
||||
v-model:sub-district-id="formData.subDistrictId"
|
||||
v-model:zip-code="formData.zipCode"
|
||||
v-model:address-foreign="formData.addressForeign"
|
||||
v-model:province-text-en="formData.provinceTextEN"
|
||||
v-model:district-text-en="formData.districtTextEN"
|
||||
v-model:sub-district-text-en="formData.subDistrictTextEN"
|
||||
:readonly="!infoDrawerEdit"
|
||||
prefix-id="drawer-info-personnel"
|
||||
:title="'personnel.form.addressInformation'"
|
||||
dense
|
||||
:use-foreign-address="formData.userType === 'AGENCY'"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<FormByType
|
||||
|
|
@ -1781,10 +1924,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 +1972,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 +2009,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 +2052,7 @@ watch(
|
|||
? [
|
||||
{
|
||||
name: $t('personnel.form.workInformation'),
|
||||
anchor: 'dialog-info-work',
|
||||
anchor: 'dialog-form-work',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
|
|
@ -1914,6 +2068,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 +2094,7 @@ watch(
|
|||
id="dialog-form-personal"
|
||||
prefix-id="form-dialog-personnel"
|
||||
dense
|
||||
:agency="formData.userType === 'AGENCY'"
|
||||
outlined
|
||||
separator
|
||||
:title="'personnel.form.personalInformation'"
|
||||
|
|
@ -1957,6 +2113,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
|
||||
|
|
@ -1973,8 +2131,13 @@ watch(
|
|||
v-model:district-id="formData.districtId"
|
||||
v-model:sub-district-id="formData.subDistrictId"
|
||||
v-model:zip-code="formData.zipCode"
|
||||
v-model:address-foreign="formData.addressForeign"
|
||||
v-model:province-text-en="formData.provinceTextEN"
|
||||
v-model:district-text-en="formData.districtTextEN"
|
||||
v-model:sub-district-text-en="formData.subDistrictTextEN"
|
||||
prefix-id="drawer-info-personnel"
|
||||
dense
|
||||
:use-foreign-address="formData.userType === 'AGENCY'"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<FormByType
|
||||
|
|
@ -1992,8 +2155,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>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { baseUrl } from 'src/stores/utils';
|
|||
import useCustomerStore from 'stores/customer';
|
||||
import useFlowStore from 'stores/flow';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { dialog } from 'stores/utils';
|
||||
import { dialog, canAccess } from 'stores/utils';
|
||||
|
||||
import { Status } from 'stores/types';
|
||||
import { Employee } from 'stores/employee/types';
|
||||
|
|
@ -18,6 +18,8 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
|
|||
import { columnsEmployee } from './constant';
|
||||
import { useCustomerBranchForm, useEmployeeForm } from './form';
|
||||
|
||||
import DialogEmployee from 'src/components/03_customer-management/DialogEmployee.vue';
|
||||
import DrawerEmployee from 'src/components/03_customer-management/DrawerEmployee.vue';
|
||||
import EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue';
|
||||
import FloatingActionButton from 'components/FloatingActionButton.vue';
|
||||
import SideMenu from 'components/SideMenu.vue';
|
||||
|
|
@ -89,6 +91,11 @@ const prop = withDefaults(
|
|||
currentCitizenId?: string;
|
||||
gender: string;
|
||||
selectedImage: string;
|
||||
fetchImageList: (
|
||||
id: string,
|
||||
selectedName: string,
|
||||
type: 'customer' | 'employee',
|
||||
) => Promise<void>;
|
||||
}>(),
|
||||
{
|
||||
color: 'green',
|
||||
|
|
@ -96,7 +103,6 @@ const prop = withDefaults(
|
|||
);
|
||||
const currentBranchEmployee = ref<string>('');
|
||||
const listEmployee = ref<Employee[]>([]);
|
||||
|
||||
const customerId = defineModel<string>('customerId', { required: true });
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -106,16 +112,6 @@ defineEmits<{
|
|||
(e: 'dialog'): void;
|
||||
}>();
|
||||
|
||||
onMounted(async () => {
|
||||
customerBranchFormState.value.currentCustomerId = route.params
|
||||
.customerId as string;
|
||||
await fetchList();
|
||||
|
||||
branch.value?.forEach((v) => {
|
||||
currentBtnOpen.value.push(false);
|
||||
});
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'branchName',
|
||||
|
|
@ -257,10 +253,6 @@ async function fetchEmployee(opts: { branchId: string; pageSize?: number }) {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchList();
|
||||
});
|
||||
|
||||
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
|
||||
await fetchList();
|
||||
});
|
||||
|
|
@ -280,12 +272,22 @@ watch(
|
|||
}
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
customerBranchFormState.value.currentCustomerId = route.params
|
||||
.customerId as string;
|
||||
await fetchList();
|
||||
|
||||
branch.value?.forEach((v) => {
|
||||
currentBtnOpen.value.push(false);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
style="z-index: 999"
|
||||
v-if="$route.name !== 'CustomerManagement'"
|
||||
v-if="$route.name !== 'CustomerManagement' && canAccess('customer', 'edit')"
|
||||
@click="openEmployerBranchForm('create')"
|
||||
hide-icon
|
||||
></FloatingActionButton>
|
||||
|
|
@ -473,7 +475,6 @@ watch(
|
|||
<q-tr
|
||||
:class="{
|
||||
'app-text-muted': props.row.status === 'INACTIVE',
|
||||
'cursor-pointer': props.row._count?.branch !== 0,
|
||||
}"
|
||||
:props="props"
|
||||
@click="$emit('viewDetail', props.row, props.rowIndex)"
|
||||
|
|
@ -547,7 +548,13 @@ watch(
|
|||
v-if="branchFieldSelected.includes('businessTypePure')"
|
||||
class="text-left"
|
||||
>
|
||||
{{ useOptionStore().mapOption(props.row.businessType) || '-' }}
|
||||
{{
|
||||
props.row.businessType
|
||||
? props.row.businessType[
|
||||
$i18n.locale === 'eng' ? 'nameEN' : 'name'
|
||||
]
|
||||
: '-'
|
||||
}}
|
||||
</q-td>
|
||||
<q-td
|
||||
v-if="branchFieldSelected.includes('totalEmployee')"
|
||||
|
|
@ -566,8 +573,6 @@ watch(
|
|||
await fetchEmployee({
|
||||
branchId: currentBranchEmployee,
|
||||
pageSize: 999,
|
||||
passport: true,
|
||||
visa: true,
|
||||
});
|
||||
|
||||
currentBtnOpen.map((v, i) => {
|
||||
|
|
@ -615,7 +620,7 @@ watch(
|
|||
<div class="text-center">
|
||||
<TableEmpoloyee
|
||||
:prefix-id="props.row.registerName || props.row.firstName"
|
||||
add-button
|
||||
:add-button="canAccess('customer', 'edit')"
|
||||
in-table
|
||||
:list-employee="listEmployee"
|
||||
:columns-employee="columnsEmployee"
|
||||
|
|
@ -638,10 +643,15 @@ watch(
|
|||
"
|
||||
@history="(item) => {}"
|
||||
@view="
|
||||
(item) => {
|
||||
async (item) => {
|
||||
employeeFormState.drawerModal = true;
|
||||
//employeeFormState.isEmployeeEdit = true;
|
||||
employeeFormStore.assignFormDataEmployee(item.id);
|
||||
await fetchImageList(
|
||||
item.id,
|
||||
item.selectedImage || '',
|
||||
'employee',
|
||||
);
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
|
@ -668,9 +678,11 @@ watch(
|
|||
? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}`
|
||||
: `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`,
|
||||
telephone: props.row.telephoneNo,
|
||||
businessTypePure: useOptionStore().mapOption(
|
||||
props.row.businessType,
|
||||
),
|
||||
businessTypePure: props.row.businessType
|
||||
? props.row.businessType[
|
||||
$i18n.locale === 'eng' ? 'nameEN' : 'name'
|
||||
]
|
||||
: '-',
|
||||
totalEmployee: props.row._count?.employee,
|
||||
}"
|
||||
:visible-columns="branchFieldSelected"
|
||||
|
|
@ -759,7 +771,10 @@ watch(
|
|||
/>
|
||||
<DeleteButton
|
||||
icon-only
|
||||
v-if="customerBranchFormState.dialogType === 'info'"
|
||||
v-if="
|
||||
customerBranchFormState.dialogType === 'info' &&
|
||||
canAccess('customer', 'edit')
|
||||
"
|
||||
@click="
|
||||
() => {
|
||||
deleteBranchById(customerBranchFormData.id || '');
|
||||
|
|
@ -853,7 +868,6 @@ watch(
|
|||
v-model:last-name-en="customerBranchFormData.lastNameEN"
|
||||
v-model:gender="customerBranchFormData.gender"
|
||||
v-model:birth-date="customerBranchFormData.birthDate"
|
||||
v-model:customer-name="customerBranchFormData.customerName"
|
||||
v-model:legal-person-no="customerBranchFormData.legalPersonNo"
|
||||
v-model:branch-code="customerBranchFormData.code"
|
||||
v-model:register-name="customerBranchFormData.registerName"
|
||||
|
|
@ -883,15 +897,14 @@ watch(
|
|||
</div>
|
||||
<EmployerFormBusiness
|
||||
dense
|
||||
class="q-mb-xl"
|
||||
outlined
|
||||
prefix-id="employer-branch"
|
||||
:readonly="customerBranchFormState.dialogType === 'info'"
|
||||
v-model:bussiness-type="customerBranchFormData.businessType"
|
||||
v-model:business-type-id="customerBranchFormData.businessTypeId"
|
||||
v-model:job-position="customerBranchFormData.jobPosition"
|
||||
v-model:job-description="customerBranchFormData.jobDescription"
|
||||
v-model:pay-date="customerBranchFormData.payDate"
|
||||
v-model:pay-date-e-n="customerBranchFormData.payDateEN"
|
||||
v-model:pay-date-en="customerBranchFormData.payDateEN"
|
||||
v-model:wage-rate="customerBranchFormData.wageRate"
|
||||
v-model:wage-rate-text="customerBranchFormData.wageRateText"
|
||||
/>
|
||||
|
|
@ -982,7 +995,7 @@ watch(
|
|||
v-model:email="customerBranchFormData.email"
|
||||
v-model:contact-tel="customerBranchFormData.contactTel"
|
||||
v-model:office-tel="customerBranchFormData.officeTel"
|
||||
v-model:agent="customerBranchFormData.agent"
|
||||
v-model:agent-user-id="customerBranchFormData.agentUserId"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -998,6 +1011,18 @@ watch(
|
|||
/>
|
||||
</template>
|
||||
</DialogFormContainer>
|
||||
|
||||
<DialogEmployee
|
||||
:fetch-list-employee="fetchEmployee"
|
||||
:fetch-image-list="fetchImageList"
|
||||
current-tab="employer"
|
||||
/>
|
||||
|
||||
<DrawerEmployee
|
||||
:fetch-list-employee="fetchEmployee"
|
||||
:fetch-image-list="fetchImageList"
|
||||
current-tab="employer"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||