Compare commits

..

No commits in common. "develop" and "version-0.11.3" have entirely different histories.

227 changed files with 24052 additions and 21327 deletions

9
.eslintignore Normal file
View file

@ -0,0 +1,9 @@
/dist
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.cjs
/src-ssr
/quasar.config.*.temporary.compiled*
/tests

61
.eslintrc.cjs Normal file
View file

@ -0,0 +1,61 @@
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',
},
};

View file

@ -9,7 +9,7 @@ env:
REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }}
CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }}/${{ vars.CONTAINER_IMAGE_NAME }}:latest CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }}/${{ vars.CONTAINER_IMAGE_NAME }}:latest
jobs: jobs:
build-deploy: gitea-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
@ -51,7 +51,7 @@ jobs:
"description": "**Details:**\n- Image: `${{ env.CONTAINER_IMAGE_NAME }}`\n- Deployed by: `${{ github.actor }}`", "description": "**Details:**\n- Image: `${{ env.CONTAINER_IMAGE_NAME }}`\n- Deployed by: `${{ github.actor }}`",
"color": 3066993, "color": 3066993,
"footer": { "footer": {
"text": "Local Release Notification", "text": "Gitea Local Release Notification",
"icon_url": "https://example.com/success-icon.png" "icon_url": "https://example.com/success-icon.png"
}, },
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" "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 }}`", "description": "**Details:**\n- Image: `${{ env.CONTAINER_IMAGE_NAME }}`\n- Attempted by: `${{ github.actor }}`",
"color": 15158332, "color": 15158332,
"footer": { "footer": {
"text": "Local Release Notification", "text": "Gitea Local Release Notification",
"icon_url": "https://example.com/failure-icon.png" "icon_url": "https://example.com/failure-icon.png"
}, },
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"

12398
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -7,69 +7,62 @@
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "scripts": {
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0", "test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev", "dev": "quasar dev",
"build": "quasar build", "build": "quasar build",
"postinstall": "quasar prepare",
"changelog:generate": "git-cliff -o CHANGELOG.md" "changelog:generate": "git-cliff -o CHANGELOG.md"
}, },
"dependencies": { "dependencies": {
"@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0", "@quasar/extras": "^1.16.12",
"@quasar/extras": "^1.16.17", "@tato30/vue-pdf": "^1.11.0",
"@tato30/vue-pdf": "^1.11.3",
"@vuepic/vue-datepicker": "^8.8.1", "@vuepic/vue-datepicker": "^8.8.1",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.8.4", "axios": "^1.7.4",
"cropperjs": "^1.6.2", "cropperjs": "^1.6.2",
"dayjs": "^1.11.13", "keycloak-js": "^25.0.4",
"highlight.js": "^11.11.1", "mime": "^4.0.4",
"keycloak-js": "^25.0.6",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-highlightjs": "^4.2.0",
"markdown-it-html5-embed": "^1.0.0",
"markdown-it-html5-media": "^0.7.1",
"markdown-it-image-figures": "^2.1.1",
"markdown-it-video": "^0.6.3",
"mime": "^4.0.6",
"moment": "^2.30.1", "moment": "^2.30.1",
"number-to-words": "^1.2.4", "number-to-words": "^1.2.4",
"open-props": "^1.7.14", "open-props": "^1.7.5",
"pinia": "^2.3.1", "pinia": "^2.2.2",
"quasar": "^2.18.1", "quasar": "^2.16.9",
"signature_pad": "^5.0.7", "signature_pad": "^5.0.2",
"socket.io-client": "^4.7.5",
"tesseract.js": "^5.1.1", "tesseract.js": "^5.1.1",
"thai-baht-text": "^2.0.5", "thai-baht-text": "^2.0.5",
"udsv": "^0.6.0", "udsv": "^0.6.0",
"uuid": "^10.0.0", "uuid": "^10.0.0",
"vue": "^3.5.13", "vue": "^3.4.38",
"vue3-apexcharts": "^1.7.0",
"vue-dragscroll": "^4.0.6", "vue-dragscroll": "^4.0.6",
"vue-i18n": "^11.1.2", "vue-i18n": "^9.14.0",
"vue-pdf": "^4.3.0", "vue-pdf": "^4.3.0",
"vue-router": "^4.5.0", "vue-router": "^4.4.3"
"vue-tsc": "^2.2.8",
"vue3-apexcharts": "^1.8.0"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^9.6.0", "@faker-js/faker": "^9.3.0",
"@iconify/vue": "^4.3.0", "@iconify/vue": "^4.1.2",
"@intlify/unplugin-vue-i18n": "^6.0.5", "@intlify/unplugin-vue-i18n": "^4.0.0",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.46.1",
"@quasar/app-vite": "^2.2.0", "@quasar/app-vite": "2.0.0-beta.19",
"@types/markdown-it": "^14.1.2", "@types/node": "^20.16.1",
"@types/markdown-it-highlightjs": "^3.3.4",
"@types/node": "^20.17.28",
"@types/number-to-words": "^1.2.3", "@types/number-to-words": "^1.2.3",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.21", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"autoprefixer": "^10.4.20",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"prettier": "^3.5.3", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.27.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vue-component-type-helpers": "^2.2.8" "vue-component-type-helpers": "^2.1.10"
}, },
"engines": { "engines": {
"node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18", "node": "^24 || ^22 || ^20 || ^18",
"npm": ">= 6.13.4", "npm": ">= 6.13.4",
"yarn": ">= 1.21.1" "yarn": ">= 1.21.1"
} }

3894
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
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',
],
}),
],
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

View file

@ -1,28 +1,5 @@
{ {
"eng": { "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": [ "workerStatus": [
{ {
"label": "Normal", "label": "Normal",
@ -177,21 +154,20 @@
{ "label": "VS2", "value": "VS2" }, { "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" }, { "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" }, { "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" }, { "label": "WP44", "value": "WP44" }
{ "label": "CUST", "value": "CUST" }
], ],
"prefix": [ "prefix": [
{ {
"label": "MR", "label": "Mr",
"value": "mr" "value": "mr"
}, },
{ {
"label": "MRS", "label": "Mrs",
"value": "mrs" "value": "mrs"
}, },
{ {
"label": "MISS", "label": "Miss",
"value": "miss" "value": "miss"
} }
], ],
@ -207,44 +183,29 @@
} }
], ],
"border": [ "training": [
{ {
"label": "Mae Sot, Tak Province", "label": "Myanmar Labor Training Center - Mae Sot, Tak Province",
"value": "trainingTak" "value": "trainingTak"
}, },
{ {
"label": "Koh Song, Ranong province", "label": "Myanmar Labor Training Center - Kawthoung, Ranong Province",
"value": "trainingRanong" "value": "trainingRanong"
}, },
{ {
"label": "Nong Khai, Nong Khai Province", "label": "Laos Labor Training Center - Nong Khai, Nong Khai Province",
"value": "trainingNongKhai" "value": "trainingNongKhai"
}, },
{ {
"label": "Aranyaprathet, Sa Kaeo Province", "label": "Cambodian Labor Training Center - Aranyaprathet, Sa Kaeo Province",
"value": "trainingSaKaeo" "value": "trainingSaKaeo"
}, },
{ {
"label": "Ban Laem, Chanthaburi Province", "label": "Cambodian Labor Training Center - Ban Laem, Chanthaburi Province",
"value": "trainingChanthaburi" "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": [ "nationality": [
{ {
"label": "Thai", "label": "Thai",
@ -1089,29 +1050,6 @@
}, },
"tha": { "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": [ "workerStatus": [
{ {
"label": "ปกติ", "label": "ปกติ",
@ -1266,8 +1204,7 @@
{ "label": "VS2", "value": "VS2" }, { "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" }, { "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" }, { "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" }, { "label": "WP44", "value": "WP44" }
{ "label": "CUST", "value": "CUST" }
], ],
"prefix": [ "prefix": [
@ -1296,44 +1233,29 @@
} }
], ],
"border": [ "training": [
{ {
"label": "แม่สอด จ.ตาก", "label": "สถานที่อบรมแรงงานเมียนมา-แม่สอด จ.ตาก",
"value": "trainingTak" "value": "trainingTak"
}, },
{ {
"label": "เกาะสอง จ.ระนอง", "label": "สถานที่อบรมแรงงานเมียนมา-เกาะสอง จ.ระนอง",
"value": "trainingRanong" "value": "trainingRanong"
}, },
{ {
"label": "หนองคาย จ.หนองคาย", "label": "สถานที่อบรมแรงงานลาว-หนองคาย จ.หนองคาย",
"value": "trainingNongKhai" "value": "trainingNongKhai"
}, },
{ {
"label": "อรัญประเทศ จ.สระแก้ว", "label": "สถานที่อบรมแรงงานกัมพูชา-อรัญประเทศ จ.สระแก้ว",
"value": "trainingSaKaeo" "value": "trainingSaKaeo"
}, },
{ {
"label": "บ้านแหลม จ.จันทบุรี", "label": "สถานที่อบรมแรงงานกัมพูชา-บ้านแหลม จ.จันทบุรี",
"value": "trainingChanthaburi" "value": "trainingChanthaburi"
} }
], ],
"training": [
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดตาก",
"value": "trainingTak"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดหนองคาย",
"value": "trainingNongKhai"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดสระแก้ว",
"value": "trainingSaKaeo"
}
],
"nationality": [ "nationality": [
{ {
"label": "ไทย", "label": "ไทย",

View file

@ -1,22 +1,26 @@
/* eslint-env node */
// Configuration for your app // Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
import { defineConfig } from '#q-app/wrappers'; import { configure } from 'quasar/wrappers';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
export default defineConfig((ctx) => { export default configure((ctx) => {
return { return {
eslint: {
fix: true,
warnings: true,
errors: true,
},
boot: ['i18n', 'axios', 'components'], boot: ['i18n', 'axios', 'components'],
css: ['app.scss'], css: ['app.scss'],
extras: ['mdi-v7'], extras: ['mdi-v7'],
build: { build: {
target: { target: {
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'], browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node20', node: 'node20',
}, },
typescript: {
vueShim: true,
},
vueRouterMode: 'history', vueRouterMode: 'history',
vitePlugins: [ vitePlugins: [
[ [
@ -31,7 +35,7 @@ export default defineConfig((ctx) => {
devServer: { devServer: {
host: '0.0.0.0', host: '0.0.0.0',
open: false, open: false,
port: 5174, port: 5173,
}, },
framework: { framework: {
config: {}, config: {},

167
reports.json Normal file
View file

@ -0,0 +1,167 @@
{
"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
}
}

View file

@ -1,11 +1,11 @@
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { defineBoot } from '#q-app/wrappers'; import { boot } from 'quasar/wrappers';
import { getToken } from 'src/services/keycloak'; import { getToken } from 'src/services/keycloak';
import { dialog } from 'stores/utils'; import { dialog } from 'stores/utils';
import useLoader from 'stores/loader'; import useLoader from 'stores/loader';
import useFlowStore from 'src/stores/flow'; import useFlowStore from 'src/stores/flow';
declare module 'vue' { declare module '@vue/runtime-core' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
$axios: AxiosInstance; $axios: AxiosInstance;
$api: AxiosInstance; $api: AxiosInstance;
@ -24,10 +24,10 @@ function parseError(
status: number, status: number,
body?: { status: number; message: string; code: string }, body?: { status: number; message: string; code: string },
) { ) {
if (status === 422) return 'invalidData'; if (status === 422) return 'invalideData';
if (body && body.code) return body.code; if (body && body.code) return body.code;
return 'errorOccurred'; return 'errorOccure';
} }
api.interceptors.request.use(async (config) => { api.interceptors.request.use(async (config) => {
@ -64,7 +64,7 @@ api.interceptors.response.use(
}, },
); );
export default defineBoot(({ app }) => { export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api // for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios; app.config.globalProperties.$axios = axios;

View file

@ -1,4 +1,4 @@
import { defineBoot } from '#q-app/wrappers'; import { boot } from 'quasar/wrappers';
import VueDatePicker from '@vuepic/vue-datepicker'; import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css'; import '@vuepic/vue-datepicker/dist/main.css';
import GlobalDialog from 'components/GlobalDialog.vue'; import GlobalDialog from 'components/GlobalDialog.vue';
@ -6,7 +6,7 @@ import GlobalLoading from 'components/GlobalLoading.vue';
import VueDragscroll from 'vue-dragscroll'; import VueDragscroll from 'vue-dragscroll';
import VueApexCharts from 'vue3-apexcharts'; import VueApexCharts from 'vue3-apexcharts';
export default defineBoot(({ app }) => { export default boot(({ app }) => {
app.component('global-dialog', GlobalDialog); app.component('global-dialog', GlobalDialog);
app.component('global-loading', GlobalLoading); app.component('global-loading', GlobalLoading);
app.component('VueDatePicker', VueDatePicker); app.component('VueDatePicker', VueDatePicker);

View file

@ -1,8 +1,7 @@
import { defineBoot } from '#q-app/wrappers'; import { boot } from 'quasar/wrappers';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import messages from 'src/i18n'; import messages from 'src/i18n';
import { Lang } from 'src/utils/ui';
export type MessageLanguages = keyof typeof messages; export type MessageLanguages = keyof typeof messages;
// Type-define 'eng' as the master schema for the resource // Type-define 'eng' as the master schema for the resource
@ -22,17 +21,16 @@ declare module 'vue-i18n' {
} }
/* eslint-enable @typescript-eslint/no-empty-interface */ /* eslint-enable @typescript-eslint/no-empty-interface */
export const i18n = createI18n< export const i18n = createI18n({
{ message: MessageSchema },
MessageLanguages,
false
>({
locale: 'tha', locale: 'tha',
legacy: false, legacy: false,
messages, messages: {
'en-US': {},
...messages,
},
}); });
export default defineBoot(({ app }) => { export default boot(({ app }) => {
// Set i18n instance on app // Set i18n instance on app
app.use(i18n); app.use(i18n);
}); });

View file

@ -89,7 +89,15 @@ defineProps<{
</div> </div>
</div> </div>
</div> </div>
<q-separator /> <div
style="
display: block;
width: 100%;
height: 1px;
background: hsla(0 0% 0% / 0.1);
margin-bottom: var(--size-2);
"
/>
<slot name="data"></slot> <slot name="data"></slot>
<template v-if="!$slots.data"> <template v-if="!$slots.data">
<div <div

View file

@ -36,7 +36,6 @@ defineProps<{
outlined?: boolean; outlined?: boolean;
readonly?: boolean; readonly?: boolean;
view?: boolean; view?: boolean;
single?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -122,7 +121,7 @@ watch(
/> />
{{ $t(`${title}`) }} {{ $t(`${title}`) }}
<AddButton <AddButton
v-if="!readonly && !single" v-if="!readonly"
id="btn-add-bank" id="btn-add-bank"
icon-only icon-only
class="q-ml-sm" class="q-ml-sm"
@ -142,10 +141,7 @@ watch(
style="padding-block: 0.01px" style="padding-block: 0.01px"
spaced="lg" spaced="lg"
/> />
<span <span class="col-12 app-text-muted-2 flex justify-between items-center">
v-if="!single"
class="col-12 app-text-muted-2 flex justify-between items-center"
>
{{ `${$t('branch.form.bankAccountNo')} ${i + 1}` }} {{ `${$t('branch.form.bankAccountNo')} ${i + 1}` }}
<div class="row items-center"> <div class="row items-center">
<div style="height: 30.8px" /> <div style="height: 30.8px" />
@ -176,8 +172,7 @@ watch(
</span> </span>
<div <div
v-if="!single" class="bordered q-mr-sm rounded col text-center overflow-hidden"
class="bordered q-mr-sm rounded col-4 text-center overflow-hidden"
:class="{ 'pointer-none': readonly, 'q-my-sm': $q.screen.lt.md }" :class="{ 'pointer-none': readonly, 'q-my-sm': $q.screen.lt.md }"
> >
<ImageHover <ImageHover

View file

@ -159,6 +159,42 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
]" ]"
for="input-name-en" 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>
<div class="col-12 row q-col-gutter-sm"> <div class="col-12 row q-col-gutter-sm">

View file

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import useUserStore from 'stores/user'; import useUserStore from 'stores/user';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import { UserAttachmentDelete, AgencyStatus } from 'stores/user/types'; import { UserAttachmentDelete } from 'stores/user/types';
import { dialog } from 'stores/utils'; import { dialog, selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { QSelect } from 'quasar';
import DatePicker from '../shared/DatePicker.vue'; import DatePicker from '../shared/DatePicker.vue';
import SelectInput from 'src/components/shared/SelectInput.vue';
import SelectOffice from 'components/shared/select-muliple/SelectOffice.vue'; import SelectOffice from 'components/shared/select-muliple/SelectOffice.vue';
@ -29,16 +29,15 @@ const discountCondition = defineModel<string | null | undefined>(
const sourceNationality = defineModel<string | null | undefined>( const sourceNationality = defineModel<string | null | undefined>(
'sourceNationality', 'sourceNationality',
); );
const importNationality = defineModel<string[] | null | undefined>( const importNationality = defineModel<string | null | undefined>(
'importNationality', 'importNationality',
); );
const trainingPlace = defineModel<string | null | undefined>('trainingPlace'); const trainingPlace = defineModel<string | null | undefined>('trainingPlace');
const checkpoint = defineModel<string | null | undefined>('checkpoint'); const checkpoint = defineModel<string | null | undefined>('checkPoint');
const userFile = defineModel<File[]>('userFile'); const checkpointEN = defineModel<string | null | undefined>('checkPointEn');
const userFileList = const agencyFile = defineModel<File[]>('agencyFile');
defineModel<{ name: string; url: string }[]>('userFileList'); const agencyFileList =
const remark = defineModel<string | null | undefined>('remark'); defineModel<{ name: string; url: string }[]>('agencyFileList');
const agencyStatus = defineModel<string | null | undefined>('agencyStatus');
const attachmentRef = ref(); const attachmentRef = ref();
@ -70,12 +69,66 @@ function deleteFile(name: string) {
userStore.deleteAttachment(userId.value, payload); userStore.deleteAttachment(userId.value, payload);
const result = await userStore.fetchAttachment(userId.value); const result = await userStore.fetchAttachment(userId.value);
if (result) { if (result) {
userFileList.value = result; agencyFileList.value = result;
} }
}, },
cancel: () => {}, 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> </script>
<template> <template>
<div class="row col-12"> <div class="row col-12">
@ -133,12 +186,11 @@ function deleteFile(name: string) {
/> />
<SelectOffice <SelectOffice
v-if="userType === 'MESSENGER'"
for="input-responsible-area" for="input-responsible-area"
v-model:value="responsibleArea" v-model:value="responsibleArea"
v-if="userType === 'MESSENGER'"
:readonly="readonly" :readonly="readonly"
:label="$t('personnel.form.responsibleArea')" :label="$t('personnel.form.responsibleArea')"
class="col"
/> />
</div> </div>
<div <div
@ -166,171 +218,207 @@ function deleteFile(name: string) {
class="row col-12 q-col-gutter-sm" class="row col-12 q-col-gutter-sm"
style="margin-left: 0px; padding-left: 0px" style="margin-left: 0px; padding-left: 0px"
> >
<SelectInput <q-select
:model-value="readonly ? sourceNationality || '-' : sourceNationality" 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"
id="input-source-nationality" id="input-source-nationality"
for="input-source-nationality" for="input-source-nationality"
:option="optionStore.globalOption.nationality" :dense="dense"
class="col-md-3 col-6" :readonly="readonly"
:readonly :hide-dropdown-icon="readonly"
clearable
:label="$t('personnel.form.sourceNationality')" :label="$t('personnel.form.sourceNationality')"
:options="nationalityOptions"
@filter="nationalityFilter"
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (sourceNationality = v) : '') (v) => (typeof v === 'string' ? (sourceNationality = v) : '')
" "
/> @clear="sourceNationality = ''"
>
<SelectInput <template v-slot:no-option>
v-model="importNationality" <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"
id="input-import-nationality" id="input-import-nationality"
for="input-import-nationality" for="input-import-nationality"
:option="optionStore.globalOption.nationality" :dense="dense"
class="col-md-3 col-6" :readonly="readonly"
:readonly :hide-dropdown-icon="readonly"
multiple
:hideSelected="false"
clearable
fillInput
:label="$t('personnel.form.importNationality')" :label="$t('personnel.form.importNationality')"
/> :options="nationalityOptions"
@filter="nationalityFilter"
<SelectInput :model-value="readonly ? importNationality || '-' : importNationality"
: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=" @update:model-value="
(v) => (typeof v === 'string' ? (checkpoint = v) : '') (v) => (typeof v === 'string' ? (importNationality = v) : '')
" "
/> @clear="importNationality = ''"
>
<SelectInput <template v-slot:no-option>
:model-value="readonly ? trainingPlace || '-' : trainingPlace" <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-label="label"
option-value="label"
class="col-md-6 col-12"
id="select-trainig-place" id="select-trainig-place"
for="select-trainig-place" for="select-trainig-place"
:option="optionStore.globalOption.training" :dense="dense"
class="col-md-8 col-12" :readonly="readonly"
:readonly :hide-dropdown-icon="readonly"
:label="$t('personnel.form.trainingPlace')" :label="$t('personnel.form.trainingPlace')"
clearable :options="trainingPlaceOptions"
@filter="trainingPlaceFilter"
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (trainingPlace = v) : '') (v) => (typeof v === 'string' ? (trainingPlace = v) : '')
" "
/> @clear="trainingPlace = ''"
>
<SelectInput <template v-slot:no-option>
:model-value="readonly ? agencyStatus || '-' : agencyStatus" <q-item>
id="select-checkpoint-en" <q-item-section class="text-grey">
for="select-checkpoint-en" {{ $t('general.noData') }}
:option="[ </q-item-section>
{ label: $t('personnel.form.normal'), value: AgencyStatus.Normal }, </q-item>
{ </template>
label: $t('personnel.form.canceled'), </q-select>
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 <q-input
for="input-discount-condition" for="input-checkpoint"
:dense="dense" :dense="dense"
outlined outlined
:readonly :readonly="readonly"
:label="$t('general.remark')" :label="$t('personnel.form.checkpoint')"
class="col-12" class="col-6"
type="textarea" :model-value="readonly ? checkpoint || '-' : checkpoint"
:model-value="readonly ? remark || '-' : remark"
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (remark = v) : '') (v) => (typeof v === 'string' ? (checkpoint = v) : '')
" "
@clear="remark = ''" @clear="checkpoint = ''"
/> />
</div> <q-input
for="input-checkpoint-en"
<q-file :dense="dense"
v-if="userType && !readonly" outlined
ref="attachmentRef" :readonly="readonly"
for="input-attachment" :label="$t('personnel.form.checkpointEN')"
:dense="dense" class="col-6"
outlined :model-value="readonly ? checkpointEN || '-' : checkpointEN"
:readonly="readonly" @update:model-value="
multiple (v) => (typeof v === 'string' ? (checkpointEN = v) : '')
append "
:label="$t('personnel.form.attachment')" @clear="checkpointEN = ''"
class="col" />
v-model="userFile" <q-file
> ref="attachmentRef"
<template v-slot:prepend> for="input-attachment"
<Icon :dense="dense"
icon="material-symbols:attach-file" outlined
width="20px" :readonly="readonly"
style="color: var(--brand-1)" multiple
/> append
</template> :label="$t('personnel.form.attachment')"
<template v-slot:file="file"> class="col-12"
<div class="row full-width items-center"> v-model="agencyFile"
<span class="col ellipsis"> >
{{ file.file.name }} <template v-slot:prepend>
</span> <Icon
<q-btn icon="material-symbols:attach-file"
dense width="20px"
rounded style="color: var(--brand-1)"
flat
padding="2 2"
class="app-text-muted"
icon="mdi-close-circle"
@click.stop="attachmentRef.removeAtIndex(file.index)"
/> />
</div> </template>
</template> <template v-slot:file="file">
</q-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"> <div v-if="agencyFileList && agencyFileList?.length > 0" class="col-12">
<q-list bordered separator class="rounded" style="padding: 0"> <q-list bordered separator class="rounded" style="padding: 0">
<q-item <q-item
id="attachment-file" id="attachment-file"
for="attachment-file" for="attachment-file"
v-for="item in userFileList" v-for="item in agencyFileList"
clickable clickable
:key="item.url" :key="item.url"
class="items-center row" class="items-center row"
@click="() => openNewTab(item.url)" @click="() => openNewTab(item.url)"
> >
<q-item-section> <q-item-section>
<div class="row items-center justify-between"> <div class="row items-center justify-between">
<div class="col"> <div class="col">
{{ item.name }} {{ 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)"
/>
</div> </div>
<q-btn </q-item-section>
id="delete-file" </q-item>
v-if="!readonly && userId" </q-list>
rounded </div>
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> </div>
</div> </div>

View file

@ -1,8 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { QSelect } from 'quasar';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import { selectFilterOptionRefMod } from 'stores/utils';
import { calculateAge, disabledAfterToday } from 'src/utils/datetime'; import { calculateAge, disabledAfterToday } from 'src/utils/datetime';
import { watch } from 'vue'; import { ref, onMounted, watch } from 'vue';
import SelectInput from '../shared/SelectInput.vue'; import { capitalize } from 'vue';
import DatePicker from '../shared/DatePicker.vue'; import DatePicker from '../shared/DatePicker.vue';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
@ -21,8 +23,6 @@ const midNameEN = defineModel<string | null>('midNameEn');
const citizenId = defineModel<string>('citizenId'); const citizenId = defineModel<string>('citizenId');
const citizenIssue = defineModel<Date | null>('citizenIssue'); const citizenIssue = defineModel<Date | null>('citizenIssue');
const citizenExpire = defineModel<Date | null>('citizenExpire'); const citizenExpire = defineModel<Date | null>('citizenExpire');
const contactName = defineModel<string>('contactName');
const contactTel = defineModel<string>('contactTel');
const props = defineProps<{ const props = defineProps<{
dense?: boolean; dense?: boolean;
@ -30,19 +30,73 @@ const props = defineProps<{
readonly?: boolean; readonly?: boolean;
separator?: boolean; separator?: boolean;
employee?: boolean; employee?: boolean;
agency?: boolean;
title?: string; title?: string;
prefixId: string; prefixId: string;
hideNameEn?: boolean; hideNameEn?: boolean;
}>(); }>();
function matchPreFixName() { 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() {
if (gender.value === 'male') prefixName.value = 'mr'; if (gender.value === 'male') prefixName.value = 'mr';
if (gender.value === 'female' && prefixName.value === 'mr') { if (gender.value === 'female') prefixName.value = 'mrs';
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( watch(
() => prefixName.value, () => prefixName.value,
(v) => { (v) => {
@ -56,7 +110,7 @@ watch(
() => gender.value, () => gender.value,
() => { () => {
if (props.readonly) return; if (props.readonly) return;
matchPreFixName(); matPreFixName();
}, },
); );
</script> </script>
@ -96,19 +150,40 @@ watch(
for="input-citizen-id" for="input-citizen-id"
/> />
<div class="col-12 row" style="display: flex; gap: var(--size-2)"> <div class="col-12 row" style="display: flex; gap: var(--size-2)">
<SelectInput <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"
hide-dropdown-icon hide-dropdown-icon
:readonly autocomplete="off"
:option="optionStore.globalOption?.prefix"
:id="`${prefixId}-select-prefix-name`"
:for="`${prefixId}-select-prefix-name`"
:rules="
agency ? [] : [(val: string) => !!val || $t('form.error.required')]
"
:label="$t('personnel.form.prefixName')"
class="col-md-1 col-6" class="col-md-1 col-6"
v-model="prefixName" :dense="dense"
/> :readonly="readonly"
:options="prefixNameOptions"
: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) : '')
"
@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>
<q-input <q-input
:for="`${prefixId}-input-first-name`" :for="`${prefixId}-input-first-name`"
@ -119,11 +194,7 @@ watch(
class="col" class="col"
:label="$t('personnel.form.firstName')" :label="$t('personnel.form.firstName')"
v-model="firstName" v-model="firstName"
:rules=" :rules="[(val: string) => !!val || $t('form.error.required')]"
employee || agency
? []
: [(val: string) => !!val || $t('form.error.required')]
"
/> />
<q-input <q-input
@ -158,16 +229,24 @@ watch(
class="col-12 row" class="col-12 row"
style="display: flex; gap: var(--size-2)" style="display: flex; gap: var(--size-2)"
> >
<SelectInput <q-input
hide-dropdown-icon :for="`${prefixId}-input-first-name`"
:readonly :dense="dense"
:option="optionStore.rawOption?.eng.prefix" outlined
:id="`${prefixId}-select-prefix-name-en`" hide-bottom-space
:for="`${prefixId}-select-prefix-name-en`" :readonly="readonly"
:rules="[(val: string) => !!val || $t('form.error.required')]" :disable="!readonly"
label="Prefix"
class="col-md-1 col-6" class="col-md-1 col-6"
v-model="prefixName" label="Title"
:model-value="
readonly
? capitalize(prefixName || '') || '-'
: capitalize(prefixName || '')
"
@update:model-value="
(v) => (typeof v === 'string' ? (prefixName = v) : '')
"
@clear="prefixName = ''"
/> />
<q-input <q-input
@ -208,16 +287,10 @@ watch(
class="col" class="col"
label="Surname" label="Surname"
v-model="lastNameEN" v-model="lastNameEN"
:rules=" :rules="[
employee (val: string) =>
? [] !val || /^[A-Za-z\s]+$/.test(val) || $t('form.error.letterOnly'),
: [ ]"
(val: string) =>
!val ||
/^[A-Za-z\s]+$/.test(val) ||
$t('form.error.letterOnly'),
]
"
/> />
</div> </div>
@ -253,13 +326,16 @@ watch(
hide-bottom-space hide-bottom-space
:readonly="readonly" :readonly="readonly"
:label="$t('form.email')" :label="$t('form.email')"
:rules="[ :rules="
(val) => (val && val.length > 0) || $t('form.error.required'), readonly
(v: string) => ? undefined
!v || : [
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) || (v: string) =>
$t('form.error.invalid'), !v ||
]" /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
$t('form.error.invalid'),
]
"
class="col-md-3 col-6" class="col-md-3 col-6"
:model-value="readonly ? email || '-' : email" :model-value="readonly ? email || '-' : email"
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')" @update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
@ -275,16 +351,39 @@ watch(
</template> </template>
</q-input> </q-input>
<SelectInput <q-select
v-if="!employee" v-if="!employee"
:readonly outlined
:option="optionStore.globalOption?.gender" use-input
:id="`${prefixId}-select-gender`" 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"
:for="`${prefixId}-select-gender`" :for="`${prefixId}-select-gender`"
:label="$t('form.gender')" :label="$t('form.gender')"
class="col-md-2 col-6" @filter="genderFilter"
v-model="gender" :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>
<DatePicker <DatePicker
v-model="birthDate" v-model="birthDate"
@ -357,67 +456,72 @@ watch(
" "
/> />
<SelectInput <q-select
v-if="employee" v-if="employee"
:readonly outlined
:option="optionStore.globalOption?.gender" clearable
:id="`${prefixId}-select-gender`" 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"
:for="`${prefixId}-select-gender`" :for="`${prefixId}-select-gender`"
:label="$t('form.gender')" :label="$t('form.gender')"
class="col-md-2 col-6" :rules="[(val: string) => !!val || $t('form.error.required')]"
v-model="gender" @filter="genderFilter"
/> >
<SelectInput <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" v-if="employee"
:readonly outlined
:option="optionStore.globalOption.nationality" clearable
:id="`${prefixId}-select-nationality`" 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"
:for="`${prefixId}-select-nationality`" :for="`${prefixId}-select-nationality`"
:label="$t('general.nationality')" :label="$t('general.nationality')"
class="col-md-2 col-6" :rules="[(val: string) => !!val || $t('form.error.required')]"
v-model="nationality" @filter="nationalityFilter"
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 #prepend> <template v-slot:no-option>
<q-icon <q-item>
size="xs" <q-item-section class="text-grey">
name="mdi-phone-outline" {{ $t('general.noData') }}
class="cursor-pointer" </q-item-section>
color="primary" </q-item>
/>
</template> </template>
</q-input> </q-select>
</div> </div>
</div> </div>
</template> </template>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -268,7 +268,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
<div class="col-md col-6"> <div class="col-md col-6">
<DatePicker <DatePicker
:label="$t('customerEmployee.formHealthCheck.coverageStartDate')"
v-model="checkup.coverageStartDate" v-model="checkup.coverageStartDate"
:id="`${prefixId}-input-coverage-start-date`" :id="`${prefixId}-input-coverage-start-date`"
:readonly="readonly || checkup.statusSave" :readonly="readonly || checkup.statusSave"

View file

@ -106,7 +106,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave" :readonly="readonly || employeeOther.statusSave"
hide-bottom-space hide-bottom-space
class="col-md-3 col-6" class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.firstName') })" :label="$t('form.firstName')"
:model-value="employeeOther.fatherFirstName" :model-value="employeeOther.fatherFirstName"
@update:model-value=" @update:model-value="
(v) => (v) =>
@ -122,7 +122,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave" :readonly="readonly || employeeOther.statusSave"
hide-bottom-space hide-bottom-space
class="col-md-3 col-6" class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.lastName') })" :label="$t('form.lastName')"
:model-value="employeeOther.fatherLastName" :model-value="employeeOther.fatherLastName"
@update:model-value=" @update:model-value="
(v) => (v) =>
@ -177,7 +177,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave" :readonly="readonly || employeeOther.statusSave"
hide-bottom-space hide-bottom-space
class="col-md-3 col-6" class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.firstName') })" :label="$t('form.firstName')"
:model-value="employeeOther.motherFirstName" :model-value="employeeOther.motherFirstName"
@update:model-value=" @update:model-value="
(v) => (v) =>
@ -193,7 +193,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave" :readonly="readonly || employeeOther.statusSave"
hide-bottom-space hide-bottom-space
class="col-md-3 col-6" class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.lastName') })" :label="$t('form.lastName')"
:model-value="employeeOther.motherLastName" :model-value="employeeOther.motherLastName"
@update:model-value=" @update:model-value="
(v) => (v) =>

View file

@ -9,8 +9,6 @@ import useOptionStore from 'stores/options';
import DatePicker from '../shared/DatePicker.vue'; import DatePicker from '../shared/DatePicker.vue';
import { dateFormat } from 'src/utils/datetime';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
const { locale } = useI18n(); const { locale } = useI18n();
@ -20,8 +18,8 @@ const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry'); const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('issueDate'); const issueDate = defineModel<Date | null | string>('issueDate');
const type = defineModel<string>('type'); const type = defineModel<string>('type');
const expireDate = defineModel<Date | string>('expireDate'); const expireDate = defineModel<Date>('expireDate');
const birthDate = defineModel<Date | string>('birthDate'); const birthDate = defineModel<Date>('birthDate');
const workerStatus = defineModel<string>('workerStatus'); const workerStatus = defineModel<string>('workerStatus');
const nationality = defineModel<string>('nationality'); const nationality = defineModel<string>('nationality');
const gender = defineModel<string>('gender'); const gender = defineModel<string>('gender');
@ -34,8 +32,6 @@ const firstName = defineModel<string>('firstName');
const namePrefix = defineModel<string>('namePrefix'); const namePrefix = defineModel<string>('namePrefix');
const passportNumber = defineModel<string>('passportNumber'); 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 passportValidator = /[a-zA-Z]{1}[a-zA-Z0-9]{1}[0-9]{5,7}$/;
const genderOptions = ref<Record<string, unknown>[]>([]); const genderOptions = ref<Record<string, unknown>[]>([]);
@ -107,7 +103,7 @@ onMounted(() => {
'label', 'label',
); );
passportIssuingCountryFilter = selectFilterOptionRefMod( passportIssuingCountryFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality), ref(optionStore.globalOption.nationality),
passportIssuingCountryOptions, passportIssuingCountryOptions,
'label', 'label',
); );
@ -125,13 +121,13 @@ onMounted(() => {
); );
genderFilter = selectFilterOptionRefMod( genderFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.gender), ref(optionStore.globalOption?.gender),
genderOptions, genderOptions,
'label', 'label',
); );
nationalityFilter = selectFilterOptionRefMod( nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality), ref(optionStore.globalOption?.nationality),
nationalityOptions, nationalityOptions,
'label', 'label',
); );
@ -156,7 +152,7 @@ watch(
); );
passportIssuingCountryFilter = selectFilterOptionRefMod( passportIssuingCountryFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality), ref(optionStore.globalOption.nationality),
passportIssuingCountryOptions, passportIssuingCountryOptions,
'label', 'label',
); );
@ -168,43 +164,19 @@ watch(
); );
genderFilter = selectFilterOptionRefMod( genderFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.gender), ref(optionStore.globalOption?.gender),
genderOptions, genderOptions,
'label', 'label',
); );
nationalityFilter = selectFilterOptionRefMod( nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality), ref(optionStore.globalOption?.nationality),
nationalityOptions, nationalityOptions,
'label', '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( watch(
() => namePrefix.value, () => namePrefix.value,
(v) => { (v) => {
@ -249,14 +221,20 @@ watch(
</div> </div>
<div class="q-col-gutter-sm" :class="{ row: $q.screen.gt.sm }"> <div class="q-col-gutter-sm" :class="{ row: $q.screen.gt.sm }">
<div v-if="!ocr"> <div
<q-btn class="col row justify-center q-col-gutter-sml"
flat style="max-height: 50%"
color="primary" v-if="!ocr"
icon="mdi-upload-box-outline" >
@click="() => browse()" <q-avatar
:disable="readonly" style="border: 1px dashed; border-color: black"
></q-btn> square
size="100px"
font-size="50px"
color="grey-4"
text-color="grey"
icon="mdi-image-outline"
/>
</div> </div>
<div <div
class="row q-col-gutter-sm" class="row q-col-gutter-sm"
@ -280,7 +258,7 @@ watch(
:options="workerStatusOptions" :options="workerStatusOptions"
:hide-dropdown-icon="readonly" :hide-dropdown-icon="readonly"
:for="`${prefixId}-select-visa-type`" :for="`${prefixId}-select-visa-type`"
:label="$t('customerEmployee.form.workerStatus')" :label="$t('customerEmployee.form.workerType')"
@filter="workerStatusFilter" @filter="workerStatusFilter"
:model-value="readonly ? workerStatus || '-' : workerStatus" :model-value="readonly ? workerStatus || '-' : workerStatus"
@update:model-value=" @update:model-value="

View file

@ -28,22 +28,20 @@ const arrivalAt = defineModel<string>('arrivalAt');
const arrivalTMNo = defineModel<string>('arrivalTmNo'); const arrivalTMNo = defineModel<string>('arrivalTmNo');
const arrivalTM = defineModel<string>('arrivalTm'); const arrivalTM = defineModel<string>('arrivalTm');
const mrz = defineModel<string>('mrz'); const mrz = defineModel<string>('mrz');
const entryCount = defineModel<number | string>('entryCount'); const entryCount = defineModel<number>('entryCount');
const issuePlace = defineModel<string>('issuePlace'); const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry'); const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('visaIssueDate'); const issueDate = defineModel<Date | null | string>('visaIssueDate');
const type = defineModel<string>('type'); const type = defineModel<string>('visaType');
const expireDate = defineModel<Date | string>('expireDate'); const expireDate = defineModel<Date>('expireDate');
const remark = defineModel<string>('remark'); const remark = defineModel<string>('remark');
const workerType = defineModel<string>('workerType'); const workerType = defineModel<string>('workerType');
const number = defineModel<string>('number'); const number = defineModel<string>('visaNumber');
const reportDate = defineModel<Date | null | string>('reportDate');
// const calculatedVisaDate = computed(() => {
// const calculatedVisaDate = computed(() => { if (!issueDate.value) return undefined;
// if (!issueDate.value) return undefined; return calculate90DayNext(issueDate.value);
// return calculate90DayNext(issueDate.value); });
// });
defineProps<{ defineProps<{
title?: string; title?: string;
@ -80,12 +78,6 @@ onMounted(async () => {
await fetchProvince(); 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>[]>([]); const visaTypeOptions = ref<Record<string, unknown>[]>([]);
let visaTypeFilter: ( let visaTypeFilter: (
value: string, value: string,
@ -100,14 +92,8 @@ let workerTypeFilter: (
onMounted(() => { onMounted(() => {
visaTypeFilter = selectFilterOptionRefMod( visaTypeFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.visaType),
visaTypeOptions,
'label',
);
visaIssueCountryFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.nationality), ref(optionStore.globalOption?.nationality),
visaIssueCountryOptions, visaTypeOptions,
'label', 'label',
); );
@ -121,14 +107,8 @@ onMounted(() => {
watch( watch(
() => optionStore.globalOption, () => optionStore.globalOption,
() => { () => {
visaIssueCountryFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.nationality),
visaIssueCountryOptions,
'label',
);
visaTypeFilter = selectFilterOptionRefMod( visaTypeFilter = selectFilterOptionRefMod(
optionStore.globalOption.visaType, optionStore.globalOption.nationality,
visaTypeOptions, visaTypeOptions,
'label', 'label',
); );
@ -140,10 +120,6 @@ watch(
); );
}, },
); );
//
// watch([() => issueDate.value], () => {
// reportDate.value = calculate90DayNext(issueDate.value);
// });
</script> </script>
<template> <template>
@ -157,7 +133,7 @@ watch(
name="mdi-passport" name="mdi-passport"
style="background-color: var(--surface-3)" style="background-color: var(--surface-3)"
/> />
{{ $t(title) }} {{ title }}
</div> </div>
<div <div
@ -377,12 +353,10 @@ watch(
<DatePicker <DatePicker
:id="`${prefixId}-date-picker-visa-issuance`" :id="`${prefixId}-date-picker-visa-issuance`"
:readonly :readonly
:disabled="!readonly"
:label="$t('customerEmployee.form.visa90Day')" :label="$t('customerEmployee.form.visa90Day')"
v-model="reportDate" :model-value="calculatedVisaDate"
clearable clearable
:rules="[
(val) => (val && val.length > 0) || $t('form.error.required'),
]"
/> />
</div> </div>
</div> </div>
@ -448,11 +422,11 @@ watch(
class="col-md-4 col-6" class="col-md-4 col-6"
:dense="dense" :dense="dense"
:readonly="readonly" :readonly="readonly"
:options="visaIssueCountryOptions" :options="visaTypeOptions"
:hide-dropdown-icon="readonly" :hide-dropdown-icon="readonly"
:for="`${prefixId}-select-issue-country`" :for="`${prefixId}-select-issue-country`"
:label="$t('customerEmployee.form.issueCountry')" :label="$t('customerEmployee.form.issueCountry')"
@filter="visaIssueCountryFilter" @filter="visaTypeFilter"
:model-value="readonly ? issueCountry || '-' : issueCountry" :model-value="readonly ? issueCountry || '-' : issueCountry"
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (issueCountry = v) : '') (v) => (typeof v === 'string' ? (issueCountry = v) : '')

View file

@ -22,8 +22,6 @@ const prop = withDefaults(
inTable?: boolean; inTable?: boolean;
addButton?: boolean; addButton?: boolean;
prefixId?: string; prefixId?: string;
hideAction?: boolean;
hideDelete?: boolean;
}>(), }>(),
{ {
gridView: false, gridView: false,
@ -141,9 +139,8 @@ defineEmits<{
<q-avatar size="md"> <q-avatar size="md">
<q-img <q-img
:src=" :src="
props.row.selectedImage `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` `/images/employee-avatar-${props.row.gender}.png`
: `/images/employee-avatar-${props.row.gender}.png`
" "
class="text-center" class="text-center"
:ratio="1" :ratio="1"
@ -268,10 +265,9 @@ defineEmits<{
@click.stop="$emit('view', props.row)" @click.stop="$emit('view', props.row)"
/> />
<KebabAction <KebabAction
v-if="!inTable && !hideAction" v-if="!inTable"
:id-name="props.row.firstName" :id-name="props.row.firstName"
:status="props.row.status" :status="props.row.status"
:hide-delete="hideDelete"
@view="$emit('view', props.row)" @view="$emit('view', props.row)"
@edit="$emit('edit', props.row)" @edit="$emit('edit', props.row)"
@delete="$emit('delete', props.row)" @delete="$emit('delete', props.row)"
@ -284,11 +280,9 @@ defineEmits<{
<template v-slot:item="props"> <template v-slot:item="props">
<div class="col-12 col-md-3 col-sm-6"> <div class="col-12 col-md-3 col-sm-6">
<PersonCard <PersonCard
history
:hide-delete="hideDelete"
:hide-action="hideAction"
:id="`card-${props.row.firstNameEN}`" :id="`card-${props.row.firstNameEN}`"
:field-selected="fieldSelected" :field-selected="fieldSelected"
history
:prefix-id="props.row.firstNameEN ?? props.rowIndex" :prefix-id="props.row.firstNameEN ?? props.rowIndex"
:data="{ :data="{
code: props.row.code, code: props.row.code,
@ -296,9 +290,9 @@ defineEmits<{
$i18n.locale === 'eng' $i18n.locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim() ? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
: `${props.row.firstName} ${props.row.lastName} `.trim(), : `${props.row.firstName} ${props.row.lastName} `.trim(),
img: props.row.selectedImage img:
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
: `/images/employee-avatar-${props.row.gender}.png`, `/images/employee-avatar-${props.row.gender}.png`,
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`, fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
male: props.row.gender === 'male', male: props.row.gender === 'male',
female: props.row.gender === 'female', female: props.row.gender === 'female',

View file

@ -3,7 +3,6 @@ import { QSelect } from 'quasar';
import { CustomerBranch } from 'stores/customer/types'; import { CustomerBranch } from 'stores/customer/types';
import { selectFilterOptionRefMod } from 'stores/utils'; import { selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import SelectCustomer from 'components/shared/select/SelectCustomer.vue';
import { import {
EditButton, EditButton,
DeleteButton, DeleteButton,
@ -12,7 +11,6 @@ import {
} from 'components/button'; } from 'components/button';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import { formatAddress } from 'src/utils/address';
const { locale } = useI18n(); const { locale } = useI18n();
@ -23,13 +21,6 @@ const optionsBranch = defineModel<{ id: string; name: string }[]>(
); );
// employee // employee
const customerBranchId = defineModel<string>('customerBranchId');
const currentCustomerBranch = defineModel<CustomerBranch>(
'currentCustomerBranch',
);
const customerBranch = defineModel<{ const customerBranch = defineModel<{
id: string; id: string;
address: string; address: string;
@ -54,7 +45,6 @@ defineProps<{
employeeOwnerOption?: CustomerBranch[]; employeeOwnerOption?: CustomerBranch[];
prefixId: string; prefixId: string;
showBtnSave?: boolean; showBtnSave?: boolean;
disableCustomerSelect?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -117,18 +107,180 @@ defineEmits<{
</div> </div>
<div class="col-12 row" style="gap: var(--size-2)"> <div class="col-12 row" style="gap: var(--size-2)">
<SelectCustomer <q-select
id="form-select-customer-branch-id" :id="`${prefixId}-select-employer-branch`"
for="form-select-customer-branch-id" :for="`${prefixId}-select-employer-branch`"
v-model:value="customerBranchId" :use-input="!customerBranch"
v-model:value-option="currentCustomerBranch" autocomplete="off"
input-debounce="0"
:hide-dropdown-icon="readonly"
:dense="dense"
outlined
:readonly="readonly"
hide-bottom-space
class="col-12"
:label="$t('customer.form.branchCode')" :label="$t('customer.form.branchCode')"
class="col-12 field-two" v-model="customerBranch"
simple :option-value="
required (v) => ({
:readonly id: v.id,
:disabled="disableCustomerSelect && !readonly" 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>
<q-input <q-input
:for="`${prefixId}-input-code`" :for="`${prefixId}-input-code`"

View file

@ -6,14 +6,12 @@ import { useI18n } from 'vue-i18n';
import useUserStore from 'src/stores/user'; import useUserStore from 'src/stores/user';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { baseUrl } from 'stores/utils'; import { baseUrl } from 'stores/utils';
import { getRole } from 'src/services/keycloak'; import { getRole } from 'src/services/keycloak';
import { import {
WorkflowUserInTable, WorkflowUserInTable,
WorkflowTemplatePayload, WorkflowTemplatePayload,
WorkFlowPayloadStep, WorkFlowPayloadStep,
Group,
} from 'src/stores/workflow-template/types'; } from 'src/stores/workflow-template/types';
import { User } from 'src/stores/user/types'; import { User } from 'src/stores/user/types';
@ -22,18 +20,15 @@ import ToggleButton from 'src/components/button/ToggleButton.vue';
import NoData from '../NoData.vue'; import NoData from '../NoData.vue';
import SelectBranch from '../shared/select/SelectBranch.vue'; import SelectBranch from '../shared/select/SelectBranch.vue';
import AddButton from '../button/AddButton.vue'; import AddButton from '../button/AddButton.vue';
import { QField } from 'quasar';
defineProps<{ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
hideAction?: boolean;
}>(); }>();
const { t } = useI18n(); const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const optionStore = useOptionStore(); const optionStore = useOptionStore();
const workflowStore = useWorkflowTemplate();
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', { const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
default: [], default: [],
@ -48,7 +43,7 @@ const flowData = defineModel<WorkflowTemplatePayload>('flowData', {
}, },
}); });
let objectOptions = [ const objectOptions = [
...(optionStore.globalOption?.agenciesType || []), ...(optionStore.globalOption?.agenciesType || []),
{ label: t('flow.customer'), value: 'customer' }, { label: t('flow.customer'), value: 'customer' },
{ label: t('flow.officer'), value: 'officer' }, { label: t('flow.officer'), value: 'officer' },
@ -56,9 +51,7 @@ let objectOptions = [
const options = ref(objectOptions); const options = ref(objectOptions);
const role = ref<string[]>([]); const role = ref<string[]>([]);
const userList = ref<User[]>([]); const userList = ref<User[]>([]);
const groupList = ref<Group[]>([]);
const responsiblePersonSearch = ref(''); const responsiblePersonSearch = ref('');
const responsibleMenu = ref(false);
async function getUserList(opts?: { query: string }) { async function getUserList(opts?: { query: string }) {
const resUser = await userStore.fetchList({ const resUser = await userStore.fetchList({
@ -67,10 +60,10 @@ async function getUserList(opts?: { query: string }) {
if (resUser) userList.value = resUser.result; if (resUser) userList.value = resUser.result;
} }
async function getGroupList() { // async function getUserById(responsiblePersonId: string) {
const resGroup = await workflowStore.getGroupList(); // const resUser = await userStore.fetchById(responsiblePersonId);
if (resGroup) groupList.value = resGroup; // if (resUser) userInTable.value.push(resUser);
} // }
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) { function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
const currStep = flowData.value.step[stepIndex]; const currStep = flowData.value.step[stepIndex];
@ -85,7 +78,6 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
userInTable.value[stepIndex] = { userInTable.value[stepIndex] = {
name: flowData.value.step[stepIndex].name, name: flowData.value.step[stepIndex].name,
responsiblePerson: [], responsiblePerson: [],
responsibleGroup: [],
}; };
} }
@ -109,33 +101,6 @@ 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( function selectItem(
val: Record<string, unknown>, val: Record<string, unknown>,
responsibleInstitution?: string[], responsibleInstitution?: string[],
@ -177,7 +142,6 @@ watch(
onMounted(async () => { onMounted(async () => {
role.value = getRole() || []; role.value = getRole() || [];
await getUserList(); await getUserList();
await getGroupList();
await userStore.fetchHqOption(); await userStore.fetchHqOption();
}); });
</script> </script>
@ -202,7 +166,6 @@ onMounted(async () => {
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }" :class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
> >
<ToggleButton <ToggleButton
:disable="hideAction"
class="q-mr-sm" class="q-mr-sm"
two-way two-way
:model-value="flowData.status !== 'INACTIVE'" :model-value="flowData.status !== 'INACTIVE'"
@ -492,140 +455,99 @@ onMounted(async () => {
:key="i" :key="i"
class="surface-2 bordered rounded q-px-xs" class="surface-2 bordered rounded q-px-xs"
> >
{{ {{ optionStore.mapOption(att.fieldName ?? '') }}
optionStore.mapOption(
att.fieldName ?? '',
'propertiesField',
)
}}
</span> </span>
</section> </section>
</div> </div>
</div> </div>
<!-- RESPONSIBLE-PERSON --> <!-- RESPONSIBLE-PERSON -->
<q-field <q-select
v-if="step.responsiblePersonId" v-if="step.responsiblePersonId"
behavior="menu"
:for="`select-responsible-person-${index}-${onDrawer ? 'drawer' : 'dialog'}`" :for="`select-responsible-person-${index}-${onDrawer ? 'drawer' : 'dialog'}`"
:bg-color="readonly ? 'transparent' : ''" :bg-color="readonly ? 'transparent' : ''"
:readonly :readonly
outlined outlined
:stack-label="
userInTable[index]?.responsiblePerson.length > 0 ||
userInTable[index]?.responsibleGroup.length > 0
"
:label="$t('flow.responsiblePerson')"
dense dense
v-model="step.responsiblePersonId"
multiple
:options="[1, 2, 3]"
hide-bottom-space
option-label="label"
option-value="value"
emit-value
:label="$t('flow.responsiblePerson')"
class="col-md-6 col-12" class="col-md-6 col-12"
:class="{ 'cursor-pointer': !readonly }" :hide-dropdown-icon="readonly"
> >
<template #control> <template v-slot:selected-item="scope">
<q-item <div class="column full-width">
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}`"
>
<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 <div
class="column q-pl-md" class="row items-center no-wrap"
style="color: var(--foreground)" v-for="person in userInTable[
index
]?.responsiblePerson.filter(
(p) => p.id === scope.opt,
)"
:key="person.id"
> >
<span> <q-avatar class="q-ml-sm" size="md">
{{ <q-img
`${optionStore.mapOption(person.namePrefix || '')} ${ class="text-center"
$i18n.locale === 'eng' :ratio="1"
? person.firstNameEN :src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
: person.firstName >
} ${ <template #error>
$i18n.locale === 'eng' <div
? person.lastNameEN class="no-padding full-width full-height flex items-center justify-center"
: person.lastName :style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
}` >
}} <q-img
</span> v-if="person.gender"
<span class="text-caption app-text-muted"> :src="
{{ person.code }} person.gender === 'male'
</span> ? '/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> </div>
</q-item>
<div
v-if="step.responsibleGroup.length > 0"
class="full-width app-text-muted text-weight-medium"
style="font-size: 10px"
>
{{ $t('general.group') }}
</div> </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>
<template v-if="!readonly" #append>
<q-icon <template v-slot:option></template>
name="mdi-menu-down" <q-menu v-if="!readonly" :offset="[0, 4]">
: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-list>
<q-item> <q-item>
<q-input <q-input
@ -654,7 +576,6 @@ onMounted(async () => {
{{ $t('general.noData') }} {{ $t('general.noData') }}
</q-item> </q-item>
<q-item <q-item
v-else
v-for="(person, i) in userList" v-for="(person, i) in userList"
dense dense
:key="i" :key="i"
@ -729,7 +650,6 @@ onMounted(async () => {
{{ $t('personnel.MESSENGER') }} {{ $t('personnel.MESSENGER') }}
</span> </span>
<q-item <q-item
dense
clickable clickable
@click="step.messengerByArea = !step.messengerByArea" @click="step.messengerByArea = !step.messengerByArea"
class="column" class="column"
@ -745,49 +665,9 @@ onMounted(async () => {
</div> </div>
</div> </div>
</q-item> </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-list>
</q-menu> </q-menu>
</q-field> </q-select>
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION --> <!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
<q-select <q-select
@ -902,8 +782,8 @@ onMounted(async () => {
} }
:deep( :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; justify-content: start !important;
padding-right: 8px !important; padding-right: 8px !important;
padding-top: 16px; padding-top: 16px;
@ -915,26 +795,19 @@ onMounted(async () => {
} }
:deep( :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); color: var(--brand-1);
} }
:deep( :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-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-focus-helper
) { ) {
visibility: hidden; visibility: hidden;
} }
:deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) { :deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) {
visibility: hidden; visibility: hidden;
} }
.transition-rotate {
transition: transform 0.3s ease;
}
.rotated {
transform: rotate(180deg);
}
</style> </style>

View file

@ -167,28 +167,26 @@ withDefaults(
<div class="col-12 full-width"> <div class="col-12 full-width">
<q-table <q-table
:rows-per-page-options="[0]" :rows-per-page-options="[0]"
:rows=" :rows="[
[ {
priceDisplay.price && { label: $t('productService.product.salePrice'),
label: $t('productService.product.salePrice'), pricePerUnit: price,
pricePerUnit: price, calcVat,
calcVat, vatIncluded,
vatIncluded, },
}, {
priceDisplay.agentPrice && { label: $t('productService.product.agentPrice'),
label: $t('productService.product.agentPrice'), calcVat: agentPriceCalcVat,
calcVat: agentPriceCalcVat, vatIncluded: agentPriceVatIncluded,
vatIncluded: agentPriceVatIncluded, pricePerUnit: agentPrice,
pricePerUnit: agentPrice, },
}, {
priceDisplay.serviceCharge && { label: $t('productService.product.processingPrice'),
label: $t('productService.product.processingPrice'), calcVat: serviceChargeCalcVat,
calcVat: serviceChargeCalcVat, vatIncluded: serviceChargeVatIncluded,
vatIncluded: serviceChargeVatIncluded, pricePerUnit: serviceCharge,
pricePerUnit: serviceCharge, },
}, ]"
].filter(Boolean)
"
:columns :columns
hide-bottom hide-bottom
bordered bordered
@ -246,19 +244,16 @@ withDefaults(
<template v-if="col.name === '#calcVat'"> <template v-if="col.name === '#calcVat'">
<q-checkbox <q-checkbox
v-if="priceDisplay?.price && props.rowIndex === 0" v-if="priceDisplay?.price && props.rowIndex === 0"
:disable="readonly"
v-model="calcVat" v-model="calcVat"
size="xs" size="xs"
/> />
<q-checkbox <q-checkbox
v-if="priceDisplay?.agentPrice && props.rowIndex === 1" v-if="priceDisplay?.agentPrice && props.rowIndex === 1"
:disable="readonly"
v-model="agentPriceCalcVat" v-model="agentPriceCalcVat"
size="xs" size="xs"
/> />
<q-checkbox <q-checkbox
v-if="priceDisplay?.serviceCharge && props.rowIndex === 2" v-if="priceDisplay?.serviceCharge && props.rowIndex === 2"
:disable="readonly"
v-model="serviceChargeCalcVat" v-model="serviceChargeCalcVat"
size="xs" size="xs"
/> />
@ -276,8 +271,6 @@ withDefaults(
flat flat
outlined outlined
dense dense
:readonly
:hide-dropdown-icon="readonly"
v-model="vatIncluded" v-model="vatIncluded"
></q-select> ></q-select>
<q-select <q-select
@ -292,8 +285,6 @@ withDefaults(
flat flat
outlined outlined
dense dense
:readonly
:hide-dropdown-icon="readonly"
v-model="agentPriceVatIncluded" v-model="agentPriceVatIncluded"
></q-select> ></q-select>
<q-select <q-select
@ -308,8 +299,6 @@ withDefaults(
flat flat
outlined outlined
dense dense
:readonly
:hide-dropdown-icon="readonly"
v-model="serviceChargeVatIncluded" v-model="serviceChargeVatIncluded"
></q-select> ></q-select>
</template> </template>

View file

@ -98,8 +98,7 @@ watch(
(c, o) => { (c, o) => {
const list = c.map((v: { name: string }) => v.name); const list = c.map((v: { name: string }) => v.name);
const oldList = o.map((v: { name: string }) => v.name); const oldList = o.map((v: { name: string }) => v.name);
const index = workName.value ? oldList.indexOf(workName.value) : -1; const index = oldList.indexOf(workName.value || '');
if (index === -1) return;
if ( if (
list[index] !== oldList[index] && list[index] !== oldList[index] &&
@ -585,12 +584,7 @@ watch(
:key="i" :key="i"
class="surface-2 bordered rounded q-px-xs" class="surface-2 bordered rounded q-px-xs"
> >
{{ {{ optionStore.mapOption(att.fieldName ?? '') }}
optionStore.mapOption(
att.fieldName ?? '',
'propertiesField',
)
}}
</span> </span>
</div> </div>
<div v-else class="app-text-muted-2"> <div v-else class="app-text-muted-2">
@ -705,8 +699,8 @@ watch(
} }
:deep( :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; justify-content: start !important;
padding-right: 8px !important; padding-right: 8px !important;
padding-top: 16px; padding-top: 16px;
@ -736,8 +730,8 @@ watch(
} }
:deep( :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); color: var(--brand-1);
} }

View file

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref, watch } from 'vue'; import { nextTick, onMounted, ref, watch } from 'vue';
import { DeleteButton, EditButton, SaveButton, UndoButton } from '../button'; import { DeleteButton, EditButton, SaveButton, UndoButton } from '../button';
import { scrollToElement } from 'stores/utils';
// import { useI18n } from 'vue-i18n'; // import { useI18n } from 'vue-i18n';
// import { storeToRefs } from 'pinia'; // import { storeToRefs } from 'pinia';
@ -137,8 +138,11 @@ watch(
@click=" @click="
() => { () => {
assignClone(); assignClone();
cloneList = cloneList.filter((item) => item.name !== ''); if (nameList[nameList.length - 1].name === '') {
nameList = nameList.filter((item) => item.name !== ''); $emit('delete', cloneList[cloneList.length - 1].id, true);
cloneList = cloneList.filter((item) => item.name !== '');
nameList = nameList.filter((item) => item.name !== '');
}
} }
" "
/> />

View file

@ -1,228 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { getRole } from 'src/services/keycloak';
import SelectBranch from '../shared/select/SelectBranch.vue';
import SelectInput from '../shared/SelectInput.vue';
import ToggleButton from '../button/ToggleButton.vue';
import { Status } from 'src/stores/types';
const name = defineModel<string>('name');
const nameEN = defineModel<string>('nameEn');
const type = defineModel<Record<string, any>>('type');
const registeredBranchId = defineModel<string>('registeredBranchId');
const status = defineModel<Status>('status');
const isAdmin = computed(() => {
const roles = getRole() || [];
return roles.includes('system');
});
const typeOption = [
{
label: 'Text',
value: 'string',
color: 'var(--pink-6-hsl)',
icon: 'mdi-alpha-t',
},
{
label: 'Number',
value: 'number',
color: 'var(--purple-11-hsl)',
icon: 'mdi-numeric',
},
{
label: 'Date',
value: 'date',
color: 'var(--green-9-hsl)',
icon: 'mdi-calendar-blank-outline',
},
{
label: 'Selection',
value: 'array',
color: 'var(--indigo-7-hsl)',
icon: 'mdi-code-array',
},
];
defineProps<{
readonly?: boolean;
onDrawer?: boolean;
inputOnly?: boolean;
disableToggle?: boolean;
}>();
defineEmits<{
(e: 'changeStatus'): void;
}>();
</script>
<template>
<div
:class="{ 'surface-1 rounded bordered': readonly }"
style="border: 1px solid transparent"
>
<div class="col-12 q-pb-sm text-weight-bold text-body1 row items-center">
<q-icon
size="xs"
class="q-pa-sm rounded q-mr-xs"
color="info"
name="mdi-map-marker-radius-outline"
style="background-color: var(--surface-3)"
/>
{{ $t('general.information', { msg: $t('property.title') }) }}
<span
class="row items-center text-weight-regular text-body2"
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
>
<ToggleButton
class="q-mr-sm"
two-way
:disable="disableToggle"
:model-value="status !== 'INACTIVE'"
@click="
() => {
onDrawer
? $emit('changeStatus')
: status !== 'INACTIVE'
? (status = 'INACTIVE')
: (status = 'CREATED');
}
"
/>
{{ $t('status.title') }}
</span>
</div>
<div class="q-col-gutter-sm row">
<SelectBranch
v-if="isAdmin"
v-model:value="registeredBranchId"
:label="$t('quotation.branchVirtual')"
class="col-12"
:class="{
'field-one': inputOnly && $q.screen.gt.sm,
'q-mb-sm': inputOnly && $q.screen.lt.md,
}"
simple
required
:readonly
/>
<q-input
:for="`input-name-${onDrawer ? 'drawer' : 'dialog'}`"
:bg-color="readonly ? 'transparent' : ''"
:readonly
class="col-md-6 col-12"
hide-bottom-space
dense
outlined
:label="$t('property.dialog.name')"
:model-value="!readonly ? name : name || '-'"
@update:model-value="(v) => (name = v?.toString() || '')"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input
:for="`input-nameEn-${onDrawer ? 'drawer' : 'dialog'}`"
:bg-color="readonly ? 'transparent' : ''"
:readonly
class="col-md-6 col-12"
hide-bottom-space
dense
outlined
:label="$t('property.dialog.nameEn')"
:model-value="!readonly ? nameEN : nameEN || '-'"
@update:model-value="(v) => (nameEN = v?.toString() || '')"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<SelectInput
hide-input
:fill-input="false"
:hide-selected="false"
:readonly
:for="`input-type-${onDrawer ? 'drawer' : 'dialog'}`"
:label="$t('property.dialog.type')"
class="col-md-6 col-12"
v-model="type.type"
:option="typeOption"
:rules="[(val: string) => !!val || $t('form.error.required')]"
>
<template v-slot:option="{ scope }">
<q-item
v-if="scope.opt"
v-bind="scope.itemProps"
class="row items-center col-12"
:id="`type-${scope.itemProps}`"
>
<q-avatar
size="sm"
class="q-mr-md"
:style="`background-color: hsla(${scope.opt.color}/0.2)`"
>
<q-icon
size="20px"
:name="scope.opt.icon"
:style="`color: hsl(${scope.opt.color})`"
/>
</q-avatar>
{{ scope.opt.label }}
</q-item>
</template>
<template v-slot:selected-item="{ scope }">
<div v-if="scope.opt" class="row items-center col-12">
<q-avatar
size="xs"
class="q-mr-sm"
:style="`background-color: hsla(${scope.opt.color}/0.2)`"
>
<q-icon
size="14px"
:name="scope.opt.icon"
:style="`color: hsl(${scope.opt.color})`"
/>
</q-avatar>
{{ scope.opt.label }}
</div>
</template>
</SelectInput>
</div>
</div>
</template>
<style scoped>
:deep(.responsible-search .q-field__control) {
height: 36px;
font-size: 12px;
}
:deep(
.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;
min-width: 0px;
}
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
color: hsl(var(--text-mute));
}
:deep(
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
) {
visibility: hidden;
}
:deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) {
visibility: hidden;
}
</style>

View file

@ -2,18 +2,11 @@
import SelectCustomer from '../shared/select/SelectCustomer.vue'; import SelectCustomer from '../shared/select/SelectCustomer.vue';
import SelectBranch from '../shared/select/SelectBranch.vue'; import SelectBranch from '../shared/select/SelectBranch.vue';
import { CustomerBranch } from 'src/stores/customer';
import { ref } from 'vue';
const branchId = defineModel<string>('branchId'); const branchId = defineModel<string>('branchId');
const customerBranchId = defineModel<string>('customerBranchId'); const customerBranchId = defineModel<string>('customerBranchId');
const agentPrice = defineModel<boolean>('agentPrice'); const agentPrice = defineModel<boolean>('agentPrice');
const special = defineModel<boolean>('special'); const special = defineModel<boolean>('special');
const customerBranchOption = defineModel<CustomerBranch>(
'customerBranchOption',
);
defineProps<{ defineProps<{
outlined?: boolean; outlined?: boolean;
readonly?: boolean; readonly?: boolean;
@ -74,12 +67,8 @@ defineEmits<{
required required
:readonly :readonly
/> />
<SelectCustomer <SelectCustomer
id="about-select-customer-branch-id"
for="about-select-customer-branch-id"
v-model:value="customerBranchId" v-model:value="customerBranchId"
v-model:value-option="customerBranchOption"
:label="$t('quotation.customer')" :label="$t('quotation.customer')"
:creatable-disabled-text="`(${$t('form.error.selectField', { :creatable-disabled-text="`(${$t('form.error.selectField', {
field: $t('quotation.branchVirtual'), field: $t('quotation.branchVirtual'),
@ -99,14 +88,14 @@ defineEmits<{
<style scoped> <style scoped>
:deep( :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; padding-right: 4px;
} }
:deep( :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; padding-left: 4px;
} }
</style> </style>

View file

@ -58,15 +58,16 @@ const currentBtnOpen = ref<{ title: string; opened: boolean[] }[]>([
function calcPrice(c: (typeof rows.value)[number]) { function calcPrice(c: (typeof rows.value)[number]) {
const originalPrice = c.pricePerUnit; const originalPrice = c.pricePerUnit;
const finalPricePerUnit = precisionRound( const finalPriceWithVat = precisionRound(
originalPrice + originalPrice + originalPrice * (config.value?.vat || 0.07),
(c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? originalPrice * (config.value?.vat || 0.07)
: 0),
); );
const price = finalPricePerUnit * c.amount - c.discount; const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
return precisionRound(price); 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);
} }
const discount4Show = ref<string[]>([]); const discount4Show = ref<string[]>([]);
@ -434,20 +435,8 @@ watch(
<q-td align="right"> <q-td align="right">
{{ {{
formatNumberDecimal( formatNumberDecimal(
props.row.product[ props.row.pricePerUnit * props.row.amount -
agentPrice ? 'agentPriceCalcVat' : 'calcVat' props.row.discount,
]
? 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, 2,
) )
}} }}
@ -459,12 +448,9 @@ watch(
agentPrice ? 'agentPriceCalcVat' : 'calcVat' agentPrice ? 'agentPriceCalcVat' : 'calcVat'
] ]
? precisionRound( ? precisionRound(
((props.row.pricePerUnit * (props.row.pricePerUnit * props.row.amount -
(1 + (config?.vat || 0.07)) * props.row.discount) *
props.row.amount - (config?.vat || 0.07),
props.row.discount) /
(1 + (config?.vat || 0.07))) *
0.07,
) )
: 0, : 0,
2, 2,

View file

@ -24,7 +24,6 @@ defineProps<{
hideAction?: boolean; hideAction?: boolean;
useCancel?: boolean; useCancel?: boolean;
disableCancel?: boolean; disableCancel?: boolean;
useRejectCancel?: boolean;
customData?: { customData?: {
label: string; label: string;
@ -42,7 +41,6 @@ defineEmits<{
(e: 'example'): void; (e: 'example'): void;
(e: 'preview'): void; (e: 'preview'): void;
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'rejectCancel'): void;
}>(); }>();
const rand = Math.random(); const rand = Math.random();
@ -64,16 +62,15 @@ const rand = Math.random();
solid solid
/> />
</div> </div>
<div class="q-mr-sm col row q-gutter-xs" style="font-size: 90%"> <div class="q-mr-sm" style="font-size: 90%">
<BadgeComponent <BadgeComponent
:title="status" :title="status"
:hsla-color="badgeColor || '--blue-6-hsl'" :hsla-color="badgeColor || '--blue-6-hsl'"
:border="urgent" :border="urgent"
/> />
<slot name="badge"></slot>
</div> </div>
<nav class="text-right no-wrap"> <nav class="col text-right no-wrap">
<q-btn <q-btn
v-if="!hidePreview" v-if="!hidePreview"
flat flat
@ -100,7 +97,6 @@ const rand = Math.random();
hide-toggle hide-toggle
:use-cancel :use-cancel
:disable-cancel :disable-cancel
:use-reject-cancel
:hide-delete="hideKebabDelete" :hide-delete="hideKebabDelete"
:hide-view="hideKebabView" :hide-view="hideKebabView"
:hide-edit="hideKebabEdit" :hide-edit="hideKebabEdit"
@ -110,7 +106,6 @@ const rand = Math.random();
@upload="$emit('upload')" @upload="$emit('upload')"
@delete="$emit('delete')" @delete="$emit('delete')"
@cancel="$emit('cancel')" @cancel="$emit('cancel')"
@reject-cancel="$emit('rejectCancel')"
/> />
</nav> </nav>
</header> </header>

View file

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { QTableProps } from 'quasar'; import { QTableProps } from 'quasar';
import { dateFormat, dateFormatJS } from 'src/utils/datetime'; import { dateFormat } from 'src/utils/datetime';
import { formatNumberDecimal } from 'stores/utils'; import { formatNumberDecimal } from 'stores/utils';
@ -19,8 +19,6 @@ const props = withDefaults(
page?: number; page?: number;
pageSize?: number; pageSize?: number;
hideBtnPreview?: boolean; hideBtnPreview?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
}>(), }>(),
{ {
row: () => [], row: () => [],
@ -86,11 +84,11 @@ defineEmits<{
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('createdAt')"> <q-td v-if="visibleColumns.includes('createdAt')">
{{ dateFormatJS({ date: props.row.createdAt }) }} {{ dateFormat(props.row.createdAt) }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('dueDate')"> <q-td v-if="visibleColumns.includes('dueDate')">
{{ dateFormatJS({ date: props.row.dueDate }) }} {{ dateFormat(props.row.dueDate) }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('contactName')"> <q-td v-if="visibleColumns.includes('contactName')">
@ -149,12 +147,12 @@ defineEmits<{
flat flat
@click.stop="$emit('view', props.row)" @click.stop="$emit('view', props.row)"
/> />
<KebabAction <KebabAction
v-if="!hideAction"
:idName="`btn-kebab-${props.row.workName}`" :idName="`btn-kebab-${props.row.workName}`"
status="'ACTIVE'" status="'ACTIVE'"
hide-toggle hide-toggle
:hide-delete hide-delete
:hide-edit="hideEdit" :hide-edit="hideEdit"
@view="$emit('view', props.row)" @view="$emit('view', props.row)"
@edit="$emit('edit', props.row)" @edit="$emit('edit', props.row)"

View file

@ -1,10 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SelectInput from '../shared/SelectInput.vue'; import SelectInput from '../shared/SelectInput.vue';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useInstitution } from 'src/stores/institution';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
@ -14,31 +10,12 @@ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
}>(); }>();
const emit = defineEmits<{
(e: 'deleteAttachment', name: string): void;
}>();
const attachmentRef = ref();
const group = defineModel('group', { default: '' }); const group = defineModel('group', { default: '' });
const name = defineModel('name', { default: '' }); const name = defineModel('name', { default: '' });
const nameEn = defineModel('nameEn', { 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 }; type Options = { label: string; value: string };
function openNewTab(url: string) {
window.open(url, '_blank');
}
function deleteAttachment(name: string) {
emit('deleteAttachment', name);
}
</script> </script>
<template> <template>
<div class="row col-12"> <div class="row col-12">
@ -58,7 +35,7 @@ function deleteAttachment(name: string) {
<div class="col-12 row q-col-gutter-sm"> <div class="col-12 row q-col-gutter-sm">
<SelectInput <SelectInput
class="col" :class="{ col: $q.screen.lt.md }"
:disable="!readonly && onDrawer" :disable="!readonly && onDrawer"
:readonly="readonly" :readonly="readonly"
for="input-agencies-code" for="input-agencies-code"
@ -85,15 +62,10 @@ function deleteAttachment(name: string) {
outlined outlined
:readonly="readonly" :readonly="readonly"
hide-bottom-space hide-bottom-space
class="col-md-4 col-12" class="col-md col-12"
:label="$t('agencies.name')" :label="$t('agencies.name')"
v-model="name" v-model="name"
:rules="[ :rules="[(val: string) => !!val || $t('form.error.required')]"
(val) => !!val || $t('form.error.required'),
(val) =>
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
$t('form.error.branchNameField'),
]"
/> />
<q-input <q-input
for="input-agencies-name-en" for="input-agencies-name-en"
@ -101,159 +73,10 @@ function deleteAttachment(name: string) {
outlined outlined
:readonly="readonly" :readonly="readonly"
hide-bottom-space hide-bottom-space
class="col-md-4 col-12" class="col-md col-12"
:label="'Agencies Name'" :label="'Agencies Name'"
v-model="nameEn" 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>
</div> </div>
</template> </template>

View file

@ -27,38 +27,26 @@ withDefaults(
class="app-text-muted q-pr-sm" class="app-text-muted q-pr-sm"
:width="iconSize || '2rem'" :width="iconSize || '2rem'"
/> />
<span :id="`dd-wrapper-${label}`" class="row col"> <span class="row col">
<span <span class="col-12 app-text-muted-2" style="font-size: 10px">
:id="`dd-label-${label}`"
class="col-12 app-text-muted-2"
style="font-size: 10px"
>
{{ label }} {{ label }}
</span> </span>
<span :id="`dd-value-wrapper-${label}`" class="col-12 ellipsis"> <span class="col-12 ellipsis">
<slot name="value"> <slot name="value">
<span <span
:class="{ 'link cursor-pointer': clickable }" :class="{ 'link cursor-pointer': clickable }"
v-if="typeof value === 'string'" v-if="typeof value === 'string'"
@click="clickable ? $emit('labelClick', value, null) : undefined" @click="$emit('labelClick', value, null)"
:id="`link-${value}`"
:for="`link-${value}`"
> >
{{ value }} {{ value }}
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip> <q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
</span> </span>
<span <span v-else :class="{ 'link cursor-pointer': clickable }">
:id="`dd-value-${label}`"
v-else
:class="{ 'link cursor-pointer': clickable }"
>
<span <span
v-for="(item, index) in value" v-for="(item, index) in value"
:key="index" :key="index"
@click="$emit('labelClick', item, index)" @click="$emit('labelClick', item, index)"
class="link cursor-pointer" class="link cursor-pointer"
:id="`link-${item}`"
:for="`link-${item}`"
> >
{{ item }} {{ item }}
<span v-if="index < value.length - 1">,&nbsp;</span> <span v-if="index < value.length - 1">,&nbsp;</span>

View file

@ -8,10 +8,6 @@ defineProps<{
const quotationId = defineModel<string>('quotationId', { const quotationId = defineModel<string>('quotationId', {
required: true, required: true,
}); });
const isDebitNote = defineModel<boolean>('isDebitNote', {
required: false,
default: false,
});
</script> </script>
<template> <template>
<div class="row col-12"> <div class="row col-12">
@ -41,7 +37,6 @@ const isDebitNote = defineModel<boolean>('isDebitNote', {
cancelIncludeDebitNote: true, cancelIncludeDebitNote: true,
hasCancel: true, hasCancel: true,
}" }"
@selected="(v) => (isDebitNote = v.isDebitNote)"
/> />
</section> </section>
</div> </div>

View file

@ -4,7 +4,7 @@ import { useQuasar } from 'quasar';
import SignaturePad from 'signature_pad'; import SignaturePad from 'signature_pad';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
defineExpose({ setCanvas, getCanvas, clearCanvas, clearUpload }); defineExpose({ clearCanvas, clearUpload });
const $q = useQuasar(); const $q = useQuasar();
const isDarkActive = computed(() => $q.dark.isActive); const isDarkActive = computed(() => $q.dark.isActive);
@ -18,7 +18,7 @@ const cropper = ref();
const tab = ref('draw'); const tab = ref('draw');
const uploadFile = ref<File | undefined>(undefined); const uploadFile = ref<File | undefined>(undefined);
const imgUrl = ref<string | null>(''); const profileUrl = ref<string | null>('');
const inputFile = (() => { const inputFile = (() => {
const element = document.createElement('input'); const element = document.createElement('input');
element.type = 'file'; element.type = 'file';
@ -26,7 +26,7 @@ const inputFile = (() => {
const reader = new FileReader(); const reader = new FileReader();
reader.addEventListener('load', () => { reader.addEventListener('load', () => {
if (typeof reader.result === 'string') imgUrl.value = reader.result; if (typeof reader.result === 'string') profileUrl.value = reader.result;
}); });
element.addEventListener('change', () => { element.addEventListener('change', () => {
@ -39,11 +39,12 @@ const inputFile = (() => {
return element; return element;
})(); })();
async function initializeSignaturePad() { async function initializeSignaturePad(canva?: HTMLCanvasElement) {
const canvas = canvasRef.value; if (canva) {
signaturePad.value = new SignaturePad(canva, {
if (canvas) { backgroundColor: isDarkActive.value
signaturePad.value = new SignaturePad(canvas, { ? 'rgb(21,25,29)'
: 'rgb(248,249,250)',
penColor: 'blue', penColor: 'blue',
}); });
} else { } else {
@ -76,34 +77,34 @@ function changeColor(color: string) {
currentColor.value = color; currentColor.value = color;
} }
function setCanvas() {
const data = signaturePad.value.toDataURL('image/png');
return data;
}
function getCanvas(signature: string) {
signaturePad.value.fromDataURL(signature);
}
function clearCanvas() { function clearCanvas() {
signaturePad.value.clear(); signaturePad.value.clear();
} }
function clearUpload() { function clearUpload() {
imgUrl.value = ''; profileUrl.value = '';
} }
watch(
() => tab.value,
async () => {
await initializeSignaturePad(canvasRef.value);
await initializeCropper(imageRef.value);
},
);
onMounted(async () => { onMounted(async () => {
await initializeSignaturePad(); await initializeSignaturePad(canvasRef.value);
await initializeCropper(imageRef.value);
}); });
</script> </script>
<template> <template>
<div class="surface-1 column full-width full-height"> <div class="surface-1 bordered rounded full-width">
<q-tabs <q-tabs
v-model="tab" v-model="tab"
dense dense
align="left" align="left"
class="text-grey surface-2" class="text-grey"
active-color="primary" active-color="primary"
indicator-color="primary" indicator-color="primary"
> >
@ -111,18 +112,18 @@ onMounted(async () => {
<div class="row"> <div class="row">
<q-tab <q-tab
name="draw" name="draw"
:label="$t('general.draw')" label="Draw"
style="border-top-left-radius: var(--radius-2)" style="border-top-left-radius: var(--radius-2)"
/> />
<q-tab name="upload" :label="$t('general.upload')" /> <q-tab name="upload" label="Upload" />
</div> </div>
<div class="q-pr-md"> <div class="q-pr-md">
<q-btn <q-btn
v-if="tab === 'upload'"
dense dense
flat flat
:label="$t('general.newUpload')" v-if="tab === 'upload'"
:label="$t('newUpload')"
color="info" color="info"
@click="inputFile.click()" @click="inputFile.click()"
/> />
@ -131,66 +132,89 @@ onMounted(async () => {
</q-tabs> </q-tabs>
<q-separator /> <q-separator />
<section v-show="tab === 'draw'" class="q-pa-md col"> <div v-show="tab === 'draw'" class="q-pa-md">
<div class="column relative-position"> <div class="column relative-position">
<article <div class="absolute-top-right q-ma-md q-gutter-x-md row items-center">
class="absolute-top-right q-ma-md q-gutter-x-md row items-center"
>
<span <span
v-for="color in ['black', 'red', 'blue']"
:key="color"
:class="{ active: currentColor === color }"
class="dot" class="dot"
:style="`background-color: ${color}`" :class="{ active: currentColor === 'black' }"
@click="changeColor(color)" style="background-color: black"
@click="changeColor('black')"
> >
<q-icon <q-icon
v-if="currentColor === color" v-if="currentColor === 'black'"
name="mdi-check" name="mdi-check"
color="white" color="white"
size="sm" size="sm"
/> />
</span> </span>
</article> <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>
<canvas <canvas
class="signature-canvas" class="signature-canvas"
ref="canvasRef" ref="canvasRef"
id="signature-pad" id="signature-pad"
width="766" width="700"
height="364" height="310"
></canvas> ></canvas>
</div> </div>
</section> </div>
<section v-show="tab === 'upload'" class="q-pa-md col"> <div v-show="tab === 'upload'" class="q-pa-md">
<div <div
class="bordered upload-border rounded column items-center justify-center full-height" class="bordered upload-border rounded column items-center justify-center"
style="height: 312px"
> >
<q-img <q-img
v-show="imgUrl" v-show="profileUrl"
ref="imageRef" ref="imageRef"
:src="imgUrl ?? ''" :src="profileUrl ?? ''"
style="object-fit: cover; width: 100%; height: 100%" style="object-fit: cover; width: 100%; height: 100%"
/> />
<div v-if="!imgUrl"> <div v-if="!profileUrl">
<q-icon <q-icon
name="mdi-cloud-upload" name="mdi-cloud-upload"
size="10rem" size="10rem"
style="color: hsla(var(--text-mute) / 0.2)" style="color: hsla(var(--text-mute) / 0.2)"
/> />
<div class="text-center"> <div>
<q-btn <q-btn
unelevated unelevated
color="info" color="info"
:label="$t('general.upload')" :label="$t('uploadFile')"
icon="mdi-plus" icon="mdi-plus"
@click="inputFile.click()" @click="inputFile.click()"
/> />
</div> </div>
</div> </div>
</div> </div>
</section> </div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View file

@ -42,15 +42,6 @@ defineProps<{
const modal = defineModel('modal', { default: false }); const modal = defineModel('modal', { default: false });
const currentTab = defineModel<string>('currentTab'); 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> </script>
<template> <template>
<q-dialog <q-dialog
@ -69,7 +60,6 @@ async function onValidationError(ref: any) {
@submit.prevent @submit.prevent
@validation-success="submit" @validation-success="submit"
class="column full-height" class="column full-height"
@validation-error="onValidationError"
> >
<!-- header --> <!-- header -->
<div <div

View file

@ -8,7 +8,7 @@ import {
UndoButton, UndoButton,
} from 'components/button'; } from 'components/button';
const props = withDefaults( withDefaults(
defineProps<{ defineProps<{
title: string; title: string;
category?: string; category?: string;
@ -42,24 +42,10 @@ const drawerOpen = defineModel<boolean>('drawerOpen', {
const myForm = ref(); const myForm = ref();
function reset() { function reset() {
if (props.beforeClose) {
drawerOpen.value = props.beforeClose
? props.beforeClose()
: !drawerOpen.value;
}
if (myForm.value) { if (myForm.value) {
myForm.value.resetValidation(); myForm.value.resetValidation();
} }
} }
async function onValidationError(ref: any) {
const el = ref.$el as Element;
el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest',
});
}
</script> </script>
<template> <template>
<q-drawer <q-drawer
@ -67,6 +53,7 @@ async function onValidationError(ref: any) {
@show="show" @show="show"
@before-hide="reset" @before-hide="reset"
@hide="close" @hide="close"
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize" :width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
v-model="drawerOpen" v-model="drawerOpen"
behavior="mobile" behavior="mobile"
@ -79,7 +66,6 @@ async function onValidationError(ref: any) {
greedy greedy
@submit.prevent @submit.prevent
@validation-success="submit" @validation-success="submit"
@validation-error="onValidationError"
> >
<div <div
class="column justify-between full-height" class="column justify-between full-height"

View file

@ -1,9 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@iconify/vue/dist/iconify.js';
defineProps<{ defineProps<{
hideIcon?: boolean; hideIcon?: boolean;
icon?: string;
}>(); }>();
</script> </script>
@ -13,13 +10,10 @@ defineProps<{
v-if="!hideIcon" v-if="!hideIcon"
id="btn-add" id="btn-add"
padding="sm" padding="sm"
:icon="icon ? undefined : 'mdi-plus'" icon="mdi-plus"
direction="up" direction="up"
class="color-btn" class="color-btn"
> >
<template #icon v-if="icon">
<Icon :icon width="24" />
</template>
<slot> <slot>
<q-fab-action <q-fab-action
padding="xs" padding="xs"
@ -35,12 +29,10 @@ defineProps<{
fab fab
id="btn-add" id="btn-add"
padding="sm" padding="sm"
:icon="icon ? undefined : 'mdi-plus'" icon="mdi-plus"
direction="up" direction="up"
class="color-btn" class="color-btn"
> />
<Icon v-if="icon" :icon width="24" />
</q-btn>
</q-page-sticky> </q-page-sticky>
</template> </template>

View file

@ -46,7 +46,6 @@ defineProps<{
color="grey" color="grey"
icon="mdi-close" icon="mdi-close"
v-close-popup v-close-popup
@click="cancel"
/> />
</div> </div>

View file

@ -1,12 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
const pageSize = defineModel<number>({ required: true }); const pageSize = defineModel<number>({ required: true });
withDefaults(
defineProps<{
fetchData?: (...args: unknown[]) => void;
}>(),
{},
);
</script> </script>
<template> <template>
@ -17,12 +10,7 @@ withDefaults(
:key="v" :key="v"
clickable clickable
v-close-popup v-close-popup
@click=" @click="pageSize = v"
() => {
pageSize = v;
fetchData();
}
"
> >
<q-item-section> <q-item-section>
<q-item-label>{{ v }}</q-item-label> <q-item-label>{{ v }}</q-item-label>

View file

@ -229,7 +229,6 @@ const smallBanner = ref(false);
<ToggleButton <ToggleButton
v-if="useToggle" v-if="useToggle"
:disable="readonly"
two-way two-way
:model-value="toggleStatus !== 'INACTIVE'" :model-value="toggleStatus !== 'INACTIVE'"
@click="$emit('update:toggleStatus', toggleStatus)" @click="$emit('update:toggleStatus', toggleStatus)"
@ -266,7 +265,6 @@ const smallBanner = ref(false);
class="app-text-muted full-width" class="app-text-muted full-width"
align="left" align="left"
v-if="typeof tabsList === 'object'" v-if="typeof tabsList === 'object'"
@update:model-value="(v) => $emit('update:currentTab', v)"
> >
<q-tab <q-tab
v-for="tab in tabsList" v-for="tab in tabsList"

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { BranchWithChildren } from 'stores/branch/types'; import { BranchWithChildren } from 'stores/branch/types';
import KebabAction from './shared/KebabAction.vue'; import KebabAction from './shared/KebabAction.vue';
import { isRoleInclude } from 'stores/utils';
const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', { const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', {
default: [], default: [],
@ -16,7 +17,6 @@ withDefaults(
labelKey?: string; labelKey?: string;
childrenKey: string; childrenKey: string;
action?: boolean; action?: boolean;
hideCreate?: boolean;
}>(), }>(),
{ {
color: 'transparent', color: 'transparent',
@ -96,9 +96,7 @@ defineEmits<{
expandedTree[expandedTree.length - 1] === node.id, expandedTree[expandedTree.length - 1] === node.id,
}" }"
> >
{{ {{ node.name }}
$i18n.locale === 'eng' ? node.nameEN || node.name : node.name
}}
</span> </span>
<span class="app-text-muted text-caption ellipsis"> <span class="app-text-muted text-caption ellipsis">
{{ node.code }} {{ node.code }}
@ -122,7 +120,11 @@ defineEmits<{
/> />
<q-btn <q-btn
v-if="node.isHeadOffice && typeTree === 'branch' && !hideCreate" v-if="
node.isHeadOffice &&
typeTree === 'branch' &&
isRoleInclude(['head_of_admin', 'admin', 'system'])
"
:id="`create-sub-branch-btn-${node.name}`" :id="`create-sub-branch-btn-${node.name}`"
@click.stop="$emit('create', node)" @click.stop="$emit('create', node)"
icon="mdi-file-plus-outline" icon="mdi-file-plus-outline"

View file

@ -1,10 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import MainButton from './MainButton.vue'; import MainButton from './MainButton.vue';
const emit = defineEmits<{ defineEmits<{
(e: 'click', v: MouseEvent): void; (e: 'click', v: MouseEvent): void;
(e: 'fileSelected', v: File[]): void;
}>(); }>();
defineProps<{ defineProps<{
iconOnly?: boolean; iconOnly?: boolean;
@ -12,29 +10,15 @@ defineProps<{
outlined?: boolean; outlined?: boolean;
disabled?: boolean; disabled?: boolean;
dark?: boolean; dark?: boolean;
importFile?: boolean;
label?: string; label?: string;
icon?: 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> </script>
<template> <template>
<MainButton <MainButton
@click="(e) => (importFile ? triggerFileInput() : $emit('click', e))" @click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }" v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-import'" :icon="icon || 'mdi-import'"
color="var(--info-bg)" color="var(--info-bg)"
@ -42,13 +26,4 @@ function handleFileChange(event: Event) {
> >
{{ label || $t('general.import') }} {{ label || $t('general.import') }}
</MainButton> </MainButton>
<input
ref="inputRef"
type="file"
@change="(e) => handleFileChange(e)"
hidden
accept=".xls, .xlsx , .csv"
multiple
/>
</template> </template>

View file

@ -5,7 +5,6 @@ defineEmits<{
(e: 'click', v: MouseEvent): void; (e: 'click', v: MouseEvent): void;
}>(); }>();
defineProps<{ defineProps<{
id?: string;
icon?: string; icon?: string;
color: string; color: string;
iconOnly?: boolean; iconOnly?: boolean;
@ -19,7 +18,6 @@ defineProps<{
<template> <template>
<button <button
:id="id"
@click="(e) => $emit('click', e)" @click="(e) => $emit('click', e)"
class="main-btn" class="main-btn"
:class="{ :class="{

View file

@ -1,32 +0,0 @@
<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>

View file

@ -10,7 +10,6 @@ defineProps<{
outlined?: boolean; outlined?: boolean;
disabled?: boolean; disabled?: boolean;
dark?: boolean; dark?: boolean;
color?: string;
label?: string; label?: string;
icon?: string; icon?: string;
@ -24,7 +23,7 @@ defineProps<{
@click="(e) => $emit('click', e)" @click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }" v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-content-save-outline'" :icon="icon || 'mdi-content-save-outline'"
:color="color || '207 96% 32%'" color="207 96% 32%"
:title="iconOnly ? $t('general.save') : undefined" :title="iconOnly ? $t('general.save') : undefined"
> >
{{ label || $t('general.save') }} {{ label || $t('general.save') }}

View file

@ -14,4 +14,3 @@ export { default as PrintButton } from './PrintButton.vue';
export { default as StateButton } from './StateButton.vue'; export { default as StateButton } from './StateButton.vue';
export { default as NextButton } from './NextButton.vue'; export { default as NextButton } from './NextButton.vue';
export { default as ImportButton } from './ImportButton.vue'; export { default as ImportButton } from './ImportButton.vue';
export { default as PasteButton } from './PasteButton.vue';

View file

@ -23,15 +23,6 @@ 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 }); const state = defineModel({ default: false });
</script> </script>
@ -50,7 +41,6 @@ const state = defineModel({ default: false });
}" }"
> >
<q-form <q-form
@validation-error="onValidationError"
@submit.prevent="(e) => $emit('submit', e)" @submit.prevent="(e) => $emit('submit', e)"
@reset="$emit('reset')" @reset="$emit('reset')"
greedy greedy

View file

@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { useWorkflowTemplate } from 'src/stores/workflow-template'; import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { Option } from 'stores/options/types';
import { import {
WorkFlowPayloadStep, WorkFlowPayloadStep,
WorkflowTemplate, WorkflowTemplate,
@ -14,12 +15,6 @@ import SelectFlow from '../shared/select/SelectFlow.vue';
import NoData from '../NoData.vue'; import NoData from '../NoData.vue';
import DialogForm from '../DialogForm.vue'; import DialogForm from '../DialogForm.vue';
interface Option {
value: string;
label: string;
type: string;
}
const { t } = useI18n(); const { t } = useI18n();
const { getWorkflowTemplate } = useWorkflowTemplate(); const { getWorkflowTemplate } = useWorkflowTemplate();
const optionStore = useOptionStore(); const optionStore = useOptionStore();
@ -95,9 +90,8 @@ function manageProperties(
) { ) {
if (property === 'all' && propertiesOption.value) { if (property === 'all' && propertiesOption.value) {
if ( if (
tempStep.value[stepIndex].attributes.properties.filter((p) => tempStep.value[stepIndex].attributes.properties.length ===
propertiesOption.value.some((opt: Option) => opt.value === p.fieldName), propertiesOption.value.length
).length === propertiesOption.value.length
) { ) {
tempStep.value[stepIndex].attributes.properties = []; tempStep.value[stepIndex].attributes.properties = [];
return; return;
@ -267,7 +261,7 @@ function confirmDelete(items: unknown[], index: number) {
}); });
} }
async function assignTemp() { function assignTemp() {
propertiesOption.value = optionStore.globalOption?.propertiesField; propertiesOption.value = optionStore.globalOption?.propertiesField;
tempStep.value = JSON.parse(JSON.stringify(dataStep.value)); tempStep.value = JSON.parse(JSON.stringify(dataStep.value));
@ -391,11 +385,8 @@ watch(
<div class="full-width flex items-center"> <div class="full-width flex items-center">
<q-icon <q-icon
v-if=" v-if="
tempStep[stepIndex].attributes.properties?.filter((p) => tempStep[stepIndex].attributes.properties?.length ===
propertiesOption.some( propertiesOption?.length
(opt: Option) => opt.value === p.fieldName,
),
).length === propertiesOption.length
" "
name="mdi-checkbox-marked" name="mdi-checkbox-marked"
size="xs" size="xs"

View file

@ -12,7 +12,7 @@ import { formatAddress } from 'src/utils/address';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
const props = defineProps<{ defineProps<{
title?: string; title?: string;
addressTitle?: string; addressTitle?: string;
addressTitleEN?: string; addressTitleEN?: string;
@ -30,7 +30,6 @@ const props = defineProps<{
useEmployment?: boolean; useEmployment?: boolean;
useWorkPlace?: boolean; useWorkPlace?: boolean;
useForeignAddress?: boolean;
}>(); }>();
const addressStore = useAddressStore(); const addressStore = useAddressStore();
@ -58,25 +57,6 @@ const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
const zipCode = defineModel<string | null | undefined>('zipCode'); const zipCode = defineModel<string | null | undefined>('zipCode');
const sameWithEmployer = defineModel<boolean>('sameWithEmployer'); 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 homeCode = defineModel<string | null | undefined>('homeCode');
const employmentOffice = defineModel<string | null | undefined>( const employmentOffice = defineModel<string | null | undefined>(
'employmentOffice', 'employmentOffice',
@ -84,7 +64,6 @@ const employmentOffice = defineModel<string | null | undefined>(
const employmentOfficeEN = defineModel<string | null | undefined>( const employmentOfficeEN = defineModel<string | null | undefined>(
'employmentOfficeEn', 'employmentOfficeEn',
); );
const addressForeign = defineModel<boolean>('addressForeign');
const addrOptions = reactive<{ const addrOptions = reactive<{
provinceOps: Province[]; provinceOps: Province[];
@ -99,18 +78,14 @@ const addrOptions = reactive<{
const area = ref<Office[]>([]); const area = ref<Office[]>([]);
const fullAddress = computed(() => { const fullAddress = computed(() => {
const province = addressForeign.value const province = provinceOptions.value.find((v) => v.id === provinceId.value);
? { id: '1', name: provinceId.value } const district = districtOptions.value.find((v) => v.id === districtId.value);
: provinceOptions.value.find((v) => v.id === provinceId.value); const sDistrict = subDistrictOptions.value.find(
const district = addressForeign.value (v) => v.id === subDistrictId.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?.name && district?.name && sDistrict?.name) { if (province && district && sDistrict) {
const fullAddressText = formatAddress({ const fullAddress = formatAddress({
address: address.value, address: address.value,
addressEN: addressEN.value, addressEN: addressEN.value,
moo: moo.value ? moo.value : '', moo: moo.value ? moo.value : '',
@ -122,26 +97,21 @@ const fullAddress = computed(() => {
province: province as unknown as Province, province: province as unknown as Province,
district: district as unknown as District, district: district as unknown as District,
subDistrict: sDistrict as unknown as SubDistrict, subDistrict: sDistrict as unknown as SubDistrict,
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
}); });
return fullAddressText; return fullAddress;
} }
return '-'; return '-';
}); });
const fullAddressEN = computed(() => { const fullAddressEN = computed(() => {
const province = addressForeign.value const province = provinceOptions.value.find((v) => v.id === provinceId.value);
? { nameEN: provinceTextEN.value } const district = districtOptions.value.find((v) => v.id === districtId.value);
: provinceOptions.value.find((v) => v.id === provinceId.value); const sDistrict = subDistrictOptions.value.find(
const district = addressForeign.value (v) => v.id === subDistrictId.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?.nameEN && district?.nameEN && sDistrict?.nameEN) { if (province && district && sDistrict) {
const fullAddressText = formatAddress({ const fullAddress = formatAddress({
address: address.value, address: address.value,
addressEN: addressEN.value, addressEN: addressEN.value,
moo: moo.value ? moo.value : '', moo: moo.value ? moo.value : '',
@ -154,9 +124,8 @@ const fullAddressEN = computed(() => {
district: district as unknown as District, district: district as unknown as District,
subDistrict: sDistrict as unknown as SubDistrict, subDistrict: sDistrict as unknown as SubDistrict,
en: true, en: true,
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
}); });
return fullAddressText; return fullAddress;
} }
return '-'; return '-';
}); });
@ -180,7 +149,7 @@ async function fetchProvince() {
} }
async function fetchDistrict() { async function fetchDistrict() {
if (!provinceId.value || addressForeign.value) return; if (!provinceId.value) return;
const result = await addressStore.fetchDistrictByProvinceId(provinceId.value); const result = await addressStore.fetchDistrictByProvinceId(provinceId.value);
if (result) addrOptions.districtOps = result; if (result) addrOptions.districtOps = result;
@ -199,7 +168,7 @@ async function fetchDistrict() {
} }
async function fetchSubDistrict() { async function fetchSubDistrict() {
if (!districtId.value || addressForeign.value) return; if (!districtId.value) return;
const result = await addressStore.fetchSubDistrictByProvinceId( const result = await addressStore.fetchSubDistrictByProvinceId(
districtId.value, districtId.value,
); );
@ -286,16 +255,6 @@ onMounted(async () => {
await fetchSubDistrict(); 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(provinceId, fetchDistrict);
watch(districtId, fetchSubDistrict); watch(districtId, fetchSubDistrict);
@ -354,15 +313,6 @@ watchEffect(async () => {
{{ $t('customerEmployee.form.addressCustom') }} {{ $t('customerEmployee.form.addressCustom') }}
</span> </span>
</div> </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>
<div class="col-12 row q-col-gutter-y-md"> <div class="col-12 row q-col-gutter-y-md">
@ -499,24 +449,7 @@ watchEffect(async () => {
(v) => (typeof v === 'string' ? (street = v) : '') (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 <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -560,24 +493,7 @@ watchEffect(async () => {
</template> </template>
</q-select> </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 <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -620,25 +536,7 @@ watchEffect(async () => {
</q-item> </q-item>
</template> </template>
</q-select> </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 <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -682,27 +580,17 @@ watchEffect(async () => {
</template> </template>
</q-select> </q-select>
<q-input <q-input
:key="Number(addressForeign)"
hide-bottom-space
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`" :for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
:dense="dense" :dense="dense"
outlined outlined
:disable="!addressForeign && !readonly && !sameWithEmployer" :disable="!readonly && !sameWithEmployer"
:readonly="!addressForeign || readonly" readonly
:label="$t('form.zipCode')" :label="$t('form.zipCode')"
class="col-md-3 col-6" class="col-md-3 col-6"
:model-value=" :model-value="
!addressForeign addrOptions.subDistrictOps
? (addrOptions.subDistrictOps ?.filter((x) => x.id === subDistrictId)
?.filter((x) => x.id === subDistrictId) .map((x) => x.zipCode)[0] ?? ''
.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 <q-input
@ -801,24 +689,7 @@ watchEffect(async () => {
(v) => (typeof v === 'string' ? (streetEN = v) : '') (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 <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -861,25 +732,7 @@ watchEffect(async () => {
</q-item> </q-item>
</template> </template>
</q-select> </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 <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -922,25 +775,7 @@ watchEffect(async () => {
</q-item> </q-item>
</template> </template>
</q-select> </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 <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -984,28 +819,19 @@ watchEffect(async () => {
</template> </template>
</q-select> </q-select>
<q-input <q-input
:key="Number(addressForeign)"
hide-bottom-space hide-bottom-space
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`" :for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
:dense="dense" :dense="dense"
outlined outlined
:readonly="!addressForeign || readonly" readonly
:disable="!addressForeign && !readonly && !sameWithEmployer" :disable="!readonly && !sameWithEmployer"
zip="zip-en" zip="zip-en"
label="Zip Code" label="Zip Code"
class="col-md-3 col-6" class="col-md-3 col-6"
:model-value=" :model-value="
!addressForeign addrOptions.subDistrictOps
? (addrOptions.subDistrictOps ?.filter((x) => x.id === subDistrictId)
?.filter((x) => x.id === subDistrictId) .map((x) => x.zipCode)[0] ?? ''
.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 <q-input

View file

@ -16,4 +16,3 @@ export { default as SideMenu } from './SideMenu.vue';
export { default as StatCardComponent } from './StatCardComponent.vue'; export { default as StatCardComponent } from './StatCardComponent.vue';
export { default as TooltipComponent } from './TooltipComponent.vue'; export { default as TooltipComponent } from './TooltipComponent.vue';
export { default as TreeComponent } from './TreeComponent.vue'; export { default as TreeComponent } from './TreeComponent.vue';
export { default as PaginationPageSize } from './PaginationPageSize.vue';

View file

@ -1,190 +0,0 @@
<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>

View file

@ -25,11 +25,7 @@ withDefaults(
alt="Image" alt="Image"
/> />
</div> </div>
<div <div v-if="data.length > 3" class="avatar remaining-count">
v-if="data.length > 3"
class="avatar remaining-count"
style="cursor: default"
>
<q-tooltip> <q-tooltip>
<div v-for="(person, i) in data.slice(3)" :key="i + 3"> <div v-for="(person, i) in data.slice(3)" :key="i + 3">
{{ person.name }} {{ person.name }}

View file

@ -15,8 +15,6 @@ const props = withDefaults(
useLink?: boolean; useLink?: boolean;
useUpload?: boolean; useUpload?: boolean;
useCancel?: boolean; useCancel?: boolean;
useRejectCancel?: boolean;
useCopy?: boolean;
disableCancel?: boolean; disableCancel?: boolean;
disableDelete?: boolean; disableDelete?: boolean;
}>(), }>(),
@ -32,9 +30,7 @@ defineEmits<{
(e: 'link'): void; (e: 'link'): void;
(e: 'upload'): void; (e: 'upload'): void;
(e: 'delete'): void; (e: 'delete'): void;
(e: 'copy'): void;
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'rejectCancel'): void;
(e: 'changeStatus'): void; (e: 'changeStatus'): void;
}>(); }>();
@ -174,27 +170,6 @@ watch(
</span> </span>
</q-item> </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 <q-item
v-if="useCancel" v-if="useCancel"
v-close-popup v-close-popup
@ -218,41 +193,12 @@ watch(
}" }"
/> />
<span class="col-9 q-px-md flex items-center"> <span class="col-9 q-px-md flex items-center">
<slot name="labelCancel"> <slot name="labelDelete">
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</slot> </slot>
</span> </span>
</q-item> </q-item>
<q-item
v-if="useRejectCancel"
v-close-popup
dense
class="row"
style="white-space: nowrap"
:clickable="!disableCancel"
:id="`btn-kebab-delete-${idName}`"
:class="{
'surface-3': disableCancel,
'app-text-muted': disableCancel,
}"
@click.stop="() => $emit('rejectCancel')"
>
<q-icon
size="xs"
name="mdi-close"
class="col-3"
:class="{
'app-text-negative': !disableCancel,
}"
/>
<span class="col-9 q-px-md flex items-center">
<slot name="labelRejectCancel">
{{ $t('requestList.status.work.RejectCancel') }}
</slot>
</span>
</q-item>
<q-item v-if="!hideToggle" dense> <q-item v-if="!hideToggle" dense>
<q-item-section class="q-py-sm"> <q-item-section class="q-py-sm">
<div class="q-pa-sm surface-2 rounded flex items-center"> <div class="q-pa-sm surface-2 rounded flex items-center">

View file

@ -23,8 +23,6 @@ defineProps<{
history?: boolean; history?: boolean;
prefixId?: string; prefixId?: string;
separateEnter?: boolean; separateEnter?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -78,10 +76,8 @@ defineEmits<{
/> />
<KebabAction <KebabAction
v-if="!hideAction"
:id-name="prefixId" :id-name="prefixId"
:status="disabled ? 'INACTIVE' : 'ACTIVE'" :status="disabled ? 'INACTIVE' : 'ACTIVE'"
:hide-delete="hideDelete"
@view=" @view="
separateEnter separateEnter
? $emit('viewCard', 'INFO') ? $emit('viewCard', 'INFO')

View file

@ -13,7 +13,6 @@ let defaultFilter: (
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
prefix?: string;
id?: string; id?: string;
label?: string; label?: string;
option: T[]; option: T[];
@ -29,7 +28,6 @@ const props = withDefaults(
disable?: boolean; disable?: boolean;
multiple?: boolean; multiple?: boolean;
hideInput?: boolean; hideInput?: boolean;
hideDropdownIcon?: boolean;
rules?: ((value: string) => string | true)[]; rules?: ((value: string) => string | true)[];
}>(), }>(),
@ -72,7 +70,6 @@ watch(
</script> </script>
<template> <template>
<q-select <q-select
:id="id"
:placeholder="placeholder" :placeholder="placeholder"
outlined outlined
:clearable :clearable
@ -85,7 +82,7 @@ watch(
:hide-selected :hide-selected
hide-bottom-space hide-bottom-space
:fill-input="fillInput && !!model" :fill-input="fillInput && !!model"
:hide-dropdown-icon="readonly || hideDropdownIcon" :hide-dropdown-icon="readonly"
input-debounce="500" input-debounce="500"
:option-value=" :option-value="
typeof props.optionValue === 'string' ? props.optionValue : 'value' typeof props.optionValue === 'string' ? props.optionValue : 'value'
@ -106,11 +103,6 @@ watch(
} }
" "
:rules :rules
@clear="
() => {
multiple ? (model = []) : (model = '');
}
"
> >
<template v-if="$slots.prepend" v-slot:prepend> <template v-if="$slots.prepend" v-slot:prepend>
<slot name="prepend"></slot> <slot name="prepend"></slot>

View file

@ -72,11 +72,7 @@ onMounted(async () => {
:option="selectOptions" :option="selectOptions"
:hide-selected="false" :hide-selected="false"
:fill-input="false" :fill-input="false"
:rules="[ :rules="[(v: string) => !!v || $t('form.error.required')]"
(v: string) => {
return !!v?.length || $t('form.error.required');
},
]"
@filter="filter" @filter="filter"
> >
<template #before-options v-if="creatable"> <template #before-options v-if="creatable">

View file

@ -75,9 +75,9 @@ function setDefaultValue() {
</script> </script>
<template> <template>
<SelectInput <SelectInput
for="select-hq-id"
v-model="value" v-model="value"
incremental incremental
id="select-hq-id"
:label :label
:placeholder :placeholder
:readonly :readonly

View file

@ -1,213 +0,0 @@
<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>

View file

@ -30,7 +30,6 @@ defineEmits<{
type ExclusiveProps = { type ExclusiveProps = {
simple?: boolean; simple?: boolean;
simpleBranchNo?: boolean; simpleBranchNo?: boolean;
selectFirstValue?: boolean;
}; };
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>(); const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -65,14 +64,8 @@ onMounted(async () => {
setFirstValue(); setFirstValue();
} }
if (props.selectFirstValue) { await getSelectedOption();
setDefaultValue();
} else await getSelectedOption();
}); });
function setDefaultValue() {
setFirstValue();
}
</script> </script>
<template> <template>
<SelectInput <SelectInput
@ -165,9 +158,11 @@ function setDefaultValue() {
</template> </template>
<template #option="{ opt, scope }"> <template #option="{ opt, scope }">
<q-item v-bind="scope.itemProps" class="q-mx-sm bodrder"> <q-item v-bind="scope.itemProps">
<SelectCustomerItem :data="opt" :simple :simple-branch-no /> <SelectCustomerItem :data="opt" :simple :simple-branch-no />
</q-item> </q-item>
<q-separator class="q-mx-sm" />
</template> </template>
<template #append v-if="clearable"> <template #append v-if="clearable">
@ -180,11 +175,3 @@ function setDefaultValue() {
</template> </template>
</SelectInput> </SelectInput>
</template> </template>
<style scoped>
.bodrder {
border-bottom: solid;
border-bottom-width: 1px;
border-color: var(--border-color);
}
</style>

View file

@ -43,7 +43,6 @@ const { getOptions, setFirstValue, getSelectedOption, filter } =
const ret = await getList({ const ret = await getList({
query: query === '' ? undefined : query, query: query === '' ? undefined : query,
...props.params, ...props.params,
activeOnly: true,
}); });
if (ret) return ret.result; if (ret) return ret.result;
}, },

View file

@ -25,7 +25,6 @@ const { getQuotationList: getList, getQuotation: getById } = useStore();
defineEmits<{ defineEmits<{
(e: 'create'): void; (e: 'create'): void;
(e: 'selected', value: SelectOption): void;
}>(); }>();
type ExclusiveProps = { type ExclusiveProps = {
@ -118,14 +117,6 @@ function setDefaultValue() {
(v: string) => !props.required || !!v || $t('form.error.required'), (v: string) => !props.required || !!v || $t('form.error.required'),
]" ]"
@filter="filter" @filter="filter"
@update:model-value="
(v) => {
$emit(
'selected',
selectOptions.find((opt) => opt.id === v),
);
}
"
> >
<template #append v-if="clearable"> <template #append v-if="clearable">
<q-icon <q-icon

View file

@ -26,7 +26,6 @@ defineEmits<{
type ExclusiveProps = { type ExclusiveProps = {
selectFirstValue?: boolean; selectFirstValue?: boolean;
prefix?: string;
}; };
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>(); const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -72,7 +71,6 @@ function setDefaultValue() {
<SelectInput <SelectInput
v-model="value" v-model="value"
incremental incremental
:id="`${prefix || 'nome'}-select-user`"
:label :label
:placeholder :placeholder
:readonly :readonly
@ -94,9 +92,7 @@ function setDefaultValue() {
:hide-selected="false" :hide-selected="false"
:fill-input="false" :fill-input="false"
:rules=" :rules="
required && !readonly required ? [(v: string) => !!v || $t('form.error.required')] : undefined
? [(v: string) => !!v || $t('form.error.required')]
: undefined
" "
@filter="filter" @filter="filter"
> >

View file

@ -35,13 +35,7 @@ export const createSelect = <T extends Record<string, any>>(
let previousSearch = ''; let previousSearch = '';
watch(value, (v) => { watch(value, (v) => {
if (!v) return; if (!v || (cache && cache.find((opt) => opt[valueField] === v))) return;
if (cache && cache.find((opt) => opt[valueField] === v)) {
valueOption.value = cache.find((opt) => opt[valueField] === v);
return;
}
getSelectedOption(); getSelectedOption();
}); });
@ -69,26 +63,15 @@ export const createSelect = <T extends Record<string, any>>(
const currentValue = value.value; const currentValue = value.value;
if (!currentValue) return; if (!currentValue) return;
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
if (valueOption.value && valueOption.value[valueField] === currentValue) { if (valueOption.value && valueOption.value[valueField] === currentValue) {
selectOptions.value.unshift(valueOption.value); return 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); const ret = await getByValue(currentValue);
if (ret) { if (ret) {
selectOptions.value.unshift(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; valueOption.value = ret;
} }
} }

View file

@ -251,10 +251,7 @@ function selectedIndex(item: any) {
> >
<!-- NOTE: custom column will starts with # --> <!-- NOTE: custom column will starts with # -->
<template v-if="!col.name.startsWith('#')"> <template v-if="!col.name.startsWith('#')">
<span v-if="col.name === 'serviceDetail'"> <span>
{{ props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') || '-' }}
</span>
<span v-else>
{{ {{
typeof col.field === 'string' typeof col.field === 'string'
? props.row[col.field as keyof (Product | Service)] ? props.row[col.field as keyof (Product | Service)]

View file

@ -54,9 +54,7 @@ const columns = [
field: (v: Employee) => field: (v: Employee) =>
locale.value === Lang.English locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}` ? `${v.firstNameEN} ${v.lastNameEN}`
: v.firstName : `${v.firstName} ${v.lastName}`,
? `${v.firstName} ${v.lastName}`
: `${v.firstNameEN} ${v.lastNameEN}`,
}, },
{ {
name: 'birthDate', name: 'birthDate',
@ -145,7 +143,6 @@ function selectedIndex(item: Employee) {
<template v-if="col.name === '#check'"> <template v-if="col.name === '#check'">
<q-checkbox <q-checkbox
id="select-worker-all" id="select-worker-all"
for="select-worker-all"
v-model="props.selected" v-model="props.selected"
@update:model-value="(v) => handleUpdate()" @update:model-value="(v) => handleUpdate()"
size="sm" size="sm"
@ -201,7 +198,6 @@ function selectedIndex(item: Employee) {
v-model="props.selected" v-model="props.selected"
size="sm" size="sm"
:id="`select-worker-${props.row.firstName}`" :id="`select-worker-${props.row.firstName}`"
:for="`select-worker-${props.row.firstName}`"
/> />
</template> </template>
</q-td> </q-td>

View file

@ -188,7 +188,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.citizenId')" :label="$t('customer.form.citizenId')"
for="input-citizen-id" for="input-citizen-id"
v-model="citizenId" v-model="citizenId"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/> />
<DatePicker <DatePicker
@ -222,7 +221,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.religion')" :label="$t('customer.form.religion')"
for="input-religion" for="input-religion"
v-model="religion" v-model="religion"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/> />
<q-select <q-select
outlined outlined
@ -307,7 +305,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.firstName')" :label="$t('customer.form.firstName')"
for="input-first-name" for="input-first-name"
v-model="firstName" v-model="firstName"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/> />
<q-input <q-input
@ -319,7 +316,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.lastName')" :label="$t('customer.form.lastName')"
for="input-last-name" for="input-last-name"
v-model="lastName" v-model="lastName"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/> />
<q-input <q-input

View file

@ -22,7 +22,6 @@ type Props = {
autoSave?: boolean; autoSave?: boolean;
data?: Data; data?: Data;
hideBtn?: boolean; hideBtn?: boolean;
disabledSubmit?: boolean;
}; };
type HandleProps = { type HandleProps = {
@ -110,7 +109,6 @@ async function change(e: Event) {
hide-delete hide-delete
hide-btn hide-btn
edit edit
:disabledSubmit
:title :title
:is-edit :is-edit
:readonly :readonly

View file

@ -31,7 +31,7 @@ const currentIndexDropdownList = ref(0);
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
treeFile?: { label: string; file: { label: string }[] }[]; treeFile: { label: string; file: { label: string }[] }[];
readonly?: boolean; readonly?: boolean;
dropdownList?: { label: string; value: string }[]; dropdownList?: { label: string; value: string }[];
hideAction?: boolean; hideAction?: boolean;

View file

@ -54,11 +54,10 @@ onMounted(() => {
@click="$emit('click')" @click="$emit('click')"
> >
<q-icon :name="icon" size="lg" :style="`color: ${color}`" /> <q-icon :name="icon" size="lg" :style="`color: ${color}`" />
<div class="col column q-pl-md"> <article class="col column q-pl-md">
<div class="ellipsis full-width" style="max-width: 65vw !important"> <span class="ellipsis full-width">
{{ name }} {{ name }}
</div> </span>
<span class="text-caption app-text-muted-2"> <span class="text-caption app-text-muted-2">
{{ {{
uploading.loaded uploading.loaded
@ -80,7 +79,7 @@ onMounted(() => {
/> />
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }} {{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
</span> </span>
</div> </article>
<q-btn <q-btn
v-if="closeable" v-if="closeable"
icon="mdi-close" icon="mdi-close"

View file

@ -45,9 +45,9 @@ const props = withDefaults(
readonly?: boolean; readonly?: boolean;
showTitle?: boolean; showTitle?: boolean;
ocr?: ( ocr?: (
group: string, group: any,
file: File, file: File,
) => Promise<{ ) => void | Promise<{
status: boolean; status: boolean;
group: string; group: string;
meta: { name: string; value: string }[]; meta: { name: string; value: string }[];
@ -123,7 +123,7 @@ async function change(e: Event) {
...obj.value, ...obj.value,
{ {
_meta: structuredClone(toRaw(selectedMenu.value)._meta || {}), _meta: structuredClone(toRaw(selectedMenu.value)._meta || {}),
group: selectedMenu.value?.group, group: selectedMenu.value?.value,
file: renamedFile, file: renamedFile,
}, },
]; ];
@ -168,8 +168,8 @@ async function change(e: Event) {
type: map['doc_type'], type: map['doc_type'],
number: map['doc_number'], number: map['doc_number'],
gender: map['sex'], gender: map['sex'],
firstName: map['last_name'], firstName: map['first_name'],
lastName: map['first_name'], lastName: map['last_name'],
issueDate: map['issue_date'], issueDate: map['issue_date'],
expireDate: map['expire_date'], expireDate: map['expire_date'],
issuePlace: map['nationality'], issuePlace: map['nationality'],
@ -327,7 +327,7 @@ defineEmits<{
:rows=" :rows="
obj obj
.filter((v) => { .filter((v) => {
if (!autoSave && v.group !== selectedMenu?.group) { if (!autoSave && v.group !== selectedMenu?.value) {
return false; return false;
} }
return true; return true;

View file

@ -198,10 +198,3 @@ i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-i
.q-focus-helper { .q-focus-helper {
visibility: hidden; visibility: hidden;
} }
.clear-btn {
opacity: 0.6;
&:hover {
opacity: 1;
}
}

2
src/env.d.ts vendored
View file

@ -1,3 +1,5 @@
/* eslint-disable */
declare namespace NodeJS { declare namespace NodeJS {
interface ProcessEnv { interface ProcessEnv {
NODE_ENV: string; NODE_ENV: string;

View file

@ -4,7 +4,7 @@ export default {
save: 'Save', save: 'Save',
open: 'Open', open: 'Open',
close: 'Close', close: 'Close',
edit: 'Edit{text}', edit: 'Edit',
cancel: 'Cancel', cancel: 'Cancel',
back: 'Back', back: 'Back',
undo: 'Undo', undo: 'Undo',
@ -31,7 +31,6 @@ export default {
displayField: 'Display Fields', displayField: 'Display Fields',
order: 'Order', order: 'Order',
name: '{msg} Name', name: '{msg} Name',
nameEN: 'Name (English)',
fullName: 'Full Name', fullName: 'Full Name',
detail: '{msg} Detail', detail: '{msg} Detail',
remark: '{msg} Remark', remark: '{msg} Remark',
@ -61,7 +60,7 @@ export default {
branchStatus: 'Branch Status', branchStatus: 'Branch Status',
success: 'Success', success: 'Success',
taxNo: 'Legal Person', taxNo: 'Legal Person',
contactName: 'Contact Person', contactName: 'Contact Name',
image: 'Image of ', image: 'Image of ',
apply: 'Apply', apply: 'Apply',
licenseNumber: 'License number', licenseNumber: 'License number',
@ -151,17 +150,6 @@ export default {
notIncluded: 'Not Included', notIncluded: 'Not Included',
dueDate: 'Due date', dueDate: 'Due date',
year: 'year', 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: { menu: {
@ -204,14 +192,11 @@ export default {
title: 'Manage', title: 'Manage',
branch: 'Branch', branch: 'Branch',
personnel: 'Personnel', personnel: 'Personnel',
group: 'Group',
productService: 'Product and Service', productService: 'Product and Service',
workflow: 'Workflow', workflow: 'Workflow',
property: 'Property',
customer: 'Customer', customer: 'Customer',
mainData: 'Main Data', mainData: 'Main Data',
agencies: 'Agencies', agencies: 'Agencies',
businessType: 'Business Type',
}, },
sales: { sales: {
@ -255,12 +240,6 @@ export default {
mode: 'Mode', mode: 'Mode',
addSignature: 'Add Signature', addSignature: 'Add Signature',
}, },
manual: {
title: 'Manual',
usage: 'Usage',
troubleshooting: 'Troubleshooting',
},
}, },
noti: { noti: {
@ -341,7 +320,7 @@ export default {
requireLength: 'Please enter {msg} character', requireLength: 'Please enter {msg} character',
branchNameField: "Only letters, numbers, or the characters . , - ' &.", branchNameField: "Only letters, numbers, or the characters . , - ' &.",
branchNameENField: 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.', passportFormat: 'Please enter the passport number in the correct format.',
}, },
warning: { warning: {
@ -388,7 +367,7 @@ export default {
branchLabel: 'Branch', branchLabel: 'Branch',
branchHQLabel: 'Headoffice', branchHQLabel: 'Headoffice',
taxNo: 'Legal Person', taxNo: 'Legal Person',
contactName: 'Contact Person', contactName: 'Contact Name',
}, },
page: { page: {
captionManage: 'Manage', captionManage: 'Manage',
@ -409,8 +388,8 @@ export default {
code: 'Headoffice Code', code: 'Headoffice Code',
codeBranch: 'Branch Code', codeBranch: 'Branch Code',
taxNo: 'Tax Identification Number', taxNo: 'Tax Identification Number',
contactName: 'Contact Person', contactName: 'Contact Name',
contactTelephone: 'Contact Number', contactTelephone: 'Contact Telephone',
branchName: 'Branch Name', branchName: 'Branch Name',
branchNameEN: 'Branch Name (EN)', branchNameEN: 'Branch Name (EN)',
servicePointName: 'Service Point Name', servicePointName: 'Service Point Name',
@ -460,10 +439,10 @@ export default {
regisNo: 'Registration Number', regisNo: 'Registration Number',
startDate: 'Start Date', startDate: 'Start Date',
retireDate: 'Retire Date', retireDate: 'Retire Date',
responsibleArea: 'Responsible Area', responsibleArea: 'Responsibel Area',
discount: 'Discount Condition', discount: 'Discount Condition',
sourceNationality: 'Source Nationality', sourceNationality: 'Source Nationality',
importNationality: 'Import Nationality', importNationality: 'import Nationality',
trainingPlace: 'Training Place', trainingPlace: 'Training Place',
checkpoint: 'Checkpoint', checkpoint: 'Checkpoint',
checkpointEN: 'Checkpoint (EN)', checkpointEN: 'Checkpoint (EN)',
@ -471,13 +450,6 @@ export default {
citizenId: 'Citizen ID', citizenId: 'Citizen ID',
citizenIssue: 'Citizen Issue', citizenIssue: 'Citizen Issue',
citizenExpire: 'Citizen Expire', citizenExpire: 'Citizen Expire',
agencyStatus: 'Agency Status',
normal: 'Normal',
canceled: 'Canceled',
blacklist: 'Black list',
contactName: 'Contact Person',
contactTel: 'Contact Number',
addressForeign: 'Use foreign address',
}, },
}, },
customer: { customer: {
@ -491,9 +463,10 @@ export default {
powerOfAttorney: 'Power of Attorney', powerOfAttorney: 'Power of Attorney',
others: 'Others', others: 'Others',
}, },
employer: 'Employer', employer: 'Employer',
employerLegalEntity: 'Legal Entity', employerLegalEntity: 'Legal Entity',
employerNaturalPerson: 'Natural Person', employerNaturalPerson: 'Natrual Person',
employerType: 'Employer Type', employerType: 'Employer Type',
employee: 'Employee', employee: 'Employee',
form: { form: {
@ -503,22 +476,24 @@ export default {
}, },
prefix: { prefix: {
mr: 'MR.', mr: 'Mr.',
mrs: 'MRS.', mrs: 'Mrs.',
miss: 'MISS.', miss: 'Miss.',
}, },
taxpayyerNo: 'Taxpayer Identification Number',
citizenId: 'Citizen ID', citizenId: 'Citizen ID',
religion: 'Religion', religion: 'Religion',
issueDate: 'Issue Date', issueDate: 'Issue Date',
passportExpiryDate: 'Passport Expiry Date', passportExpiryDate: 'Passport Expiry Date',
ownerName: 'Customer Name', ownerName: 'Customer Name',
firstName: 'First Name ', firstName: 'First Name ',
lastName: 'Last Name ', lastName: 'Last Name ',
firstNameEN: 'First Name in English', firstNameEN: 'First Name in English',
lastNameEN: 'Last Name in English', lastNameEN: 'Last Name in English',
cardNumber: 'ID Card Number', cardNumber: 'ID Card Number',
prefixName: 'Prefix', prefixName: 'Prefix',
legalPersonNo: 'Legal Entity Registration Number', legalPersonNo: 'Legal Entity Registration Number',
registerName: 'Company Name', registerName: 'Company Name',
@ -526,6 +501,7 @@ export default {
registerDate: 'Registered On', registerDate: 'Registered On',
registerCompanyName: 'Registered Name', registerCompanyName: 'Registered Name',
authorizedCapital: 'Authorized Capital', authorizedCapital: 'Authorized Capital',
workplace: 'Workplace', workplace: 'Workplace',
workplaceEN: 'Workplace (EN)', workplaceEN: 'Workplace (EN)',
address: 'Address', address: 'Address',
@ -533,6 +509,7 @@ export default {
branchCode: 'Branch Code', branchCode: 'Branch Code',
customerCode: 'Employer Code', customerCode: 'Employer Code',
legalPersonCode: 'Legal Entity Code', legalPersonCode: 'Legal Entity Code',
codeAbbrev: 'Company Abbreviation', codeAbbrev: 'Company Abbreviation',
codeNumber: 'Company Number', codeNumber: 'Company Number',
registeredBranch: 'Registered Branch', registeredBranch: 'Registered Branch',
@ -570,7 +547,7 @@ export default {
jobPosition: 'Job Position', jobPosition: 'Job Position',
address: 'Address', address: 'Address',
workPlace: 'Workplace', workPlace: 'Workplace',
contactName: 'Contact Person', contactName: 'Contact Name',
contactPhone: 'Contact Phone', contactPhone: 'Contact Phone',
totalEmployee: 'Total Employee', totalEmployee: 'Total Employee',
officeTel: 'Headoffice Telephone', officeTel: 'Headoffice Telephone',
@ -624,7 +601,7 @@ export default {
placeOfBirth: 'Place of Birth', placeOfBirth: 'Place of Birth',
countryOfbirth: 'Country of Birth', countryOfbirth: 'Country of Birth',
issueCountry: 'Issue Country', issueCountry: 'Issue Country',
entryCount: 'Number of Days in the Country', entryCount: 'Entry Count',
employerSelect: { employerSelect: {
branchName: 'Branch Name', branchName: 'Branch Name',
customerName: 'Employer Name', customerName: 'Employer Name',
@ -774,13 +751,10 @@ export default {
}, },
quotation: { quotation: {
ownOnly: 'View Own Quotation Only',
quotationDate: 'Quotation Date', quotationDate: 'Quotation Date',
seller: 'Seller', seller: 'Seller',
paymentChannels: 'Payment Channels', paymentChannels: 'Payment Channels',
channelsThat: 'Channels That', channelsThat: 'Channels That',
refNo: 'Reference Number',
bankAccount: 'Bank Account',
bankAccountNumber: 'Bank Account Number', bankAccountNumber: 'Bank Account Number',
bankAccountName: 'Bank Account Name', bankAccountName: 'Bank Account Name',
inTheNameOf: 'In The Name Of', inTheNameOf: 'In The Name Of',
@ -811,7 +785,7 @@ export default {
employee: 'Employee', employee: 'Employee',
employeeName: 'Full Name', employeeName: 'Full Name',
workName: 'Work Name', workName: 'Work Name',
contactName: 'Contact Person', contactName: 'Contact Name',
documentReceivePoint: 'Document Drop-Off Point"', documentReceivePoint: 'Document Drop-Off Point"',
dueDate: 'Quotation Due Date', dueDate: 'Quotation Due Date',
specialCondition: 'Special Conditions', specialCondition: 'Special Conditions',
@ -893,7 +867,7 @@ export default {
SplitCustom: 'Custom Installments Bill', SplitCustom: 'Custom Installments Bill',
BillFull: 'Full Amount Bill', BillFull: 'Full Amount Bill',
BillSplit: 'Installments Bill', BillSplit: 'Installments Bill',
BillSplitCustom: 'Custom Installments Bill', BillCustomSplit: 'Custom Installments Bill',
}, },
status: { status: {
@ -929,10 +903,6 @@ export default {
code: 'Agencies Code', code: 'Agencies Code',
group: 'Agencies Group', group: 'Agencies Group',
name: 'Agencies Name', name: 'Agencies Name',
contactName: 'Contact Person',
contactTel: 'Contact Number',
bankInfo: 'Bank Information',
attachment: 'Attachment',
}, },
requestList: { requestList: {
@ -954,9 +924,8 @@ export default {
localEmployee: 'Local Employee', localEmployee: 'Local Employee',
nonLocalEmployee: 'Non Local Employee', nonLocalEmployee: 'Non Local Employee',
noWorkflowTemplate: 'A workflow template has not been selected.', noWorkflowTemplate: 'A workflow template has not been selected.',
salesRepresentative: 'Sales Representative',
dataOffice: 'Employment Office District', salesRepresentative: 'Sales Representative',
ref: 'Reference', ref: 'Reference',
action: { action: {
title: 'Action', title: 'Action',
@ -972,7 +941,6 @@ export default {
Ended: 'Ended', Ended: 'Ended',
Completed: 'Completed', Completed: 'Completed',
Canceled: 'Canceled', Canceled: 'Canceled',
RejectCancel: 'Reject cancellation request',
}, },
Pending: 'Pending', Pending: 'Pending',
@ -981,8 +949,6 @@ export default {
Completed: 'Completed', Completed: 'Completed',
Canceled: 'Canceled', Canceled: 'Canceled',
CancelRequested: 'Cancel Requested', CancelRequested: 'Cancel Requested',
RejectCancel: 'Rejection of cancellation request',
RejectedCancel: 'Cancellation request rejected',
AwaitOrder: 'Awaiting Order', AwaitOrder: 'Awaiting Order',
ReadyOrder: 'Ready for Order', ReadyOrder: 'Ready for Order',
@ -1020,7 +986,7 @@ export default {
issueBranch: 'Issue Branch', issueBranch: 'Issue Branch',
issueDate: 'Issue Date', issueDate: 'Issue Date',
madeBy: 'Made By', madeBy: 'Made By',
contactName: 'Contact Person', contactName: 'Contact Name',
workOrderCode: 'Work Order Code', workOrderCode: 'Work Order Code',
workOrderName: 'Work Order Name', workOrderName: 'Work Order Name',
telephone: 'Telephone', telephone: 'Telephone',
@ -1079,10 +1045,6 @@ export default {
confirmDebitNoteAccept: 'Confirm acceptance of the debit note.', confirmDebitNoteAccept: 'Confirm acceptance of the debit note.',
}, },
message: { 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', quotationAccept: 'Once accepted, no further modifications can be made',
beingUse: '"{msg}" is being used.', beingUse: '"{msg}" is being used.',
incompleteDataEntry: 'Incomplete data entry on {tap} page', incompleteDataEntry: 'Incomplete data entry on {tap} page',
@ -1100,8 +1062,10 @@ export default {
confirmSavingStatus: confirmSavingStatus:
'Do you want to confirm the saving of the status change data?', 'Do you want to confirm the saving of the status change data?',
confirmSending: 'Do you confirm the submission of the task?', confirmSending: 'Do you confirm the submission of the task?',
confirmValidate: 'Do you confirm the validation?',
warningSelectDeliveryStaff: warningSelectDeliveryStaff:
'You have not yet selected a document delivery staff.', 'You have not yet selected a document delivery staff.',
confirmEndWorkWarning: confirmEndWorkWarning:
"Do you want to end the work now? The current statuses 'Pending', 'In Progress', 'To Be Reprocessed' will be changed to 'Redo All'.", "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?', confirmEndWork: 'Do you want to end the work?',
@ -1127,7 +1091,7 @@ export default {
oneOrMoreBranchMissing: oneOrMoreBranchMissing:
'One or more branch cannot be delete and is missing.', 'One or more branch cannot be delete and is missing.',
cantMakeHQAndBranchSameTime: cantMakeHQAndBranchSameTime:
'Cannot make this as headquarters and branch at the same time.', 'Cannot make this as headquaters and branch at the same time.',
unknowHowToVerify: 'Unknown how to verify identity.', unknowHowToVerify: 'Unknown how to verify identity.',
noPermission: noPermission:
'You do not have permission to access or perform with this resource.', 'You do not have permission to access or perform with this resource.',
@ -1209,14 +1173,13 @@ export default {
'Product with the same name already exists. If you want to create with this name please select another code.', 'Product with the same name already exists. If you want to create with this name please select another code.',
userExists: 'User already exits.', userExists: 'User already exits.',
sameNameExists: 'Same name exists.', sameNameExists: 'Same name exists.',
samePropertyNameExists: 'Same property name exists.',
validateError: 'Validate Error', validateError: 'Validate Error',
codeMisMatch: 'Code Mismatch', codeMisMatch: 'Code Mismatch',
crossCompanyNotPermit: 'Cannot move between different headoffice', crossCompanyNotPermit: 'Cannot move between different headoffice',
errorOccurred: errorOccure:
'An error has occurred, causing the system to be unable to function. Please try again later.', 'An error has occurred, causing the system to be unable to function. Please try again later.',
invalidData: 'The information is incorrect. Please try again later.', invalideData: 'The information is incorrect. Please try again later.',
authFailed: 'Authentication Failed. Please try again later. ', authFailed: 'Authentication Failed. Please try again later. ',
installmentsValidateFailed: installmentsValidateFailed:
'Validation failed. Each installment must include at least one product. Please review and update the installments accordingly.', 'Validation failed. Each installment must include at least one product. Please review and update the installments accordingly.',
@ -1234,9 +1197,6 @@ export default {
taskListNotPending: 'One or more task is not pending.', taskListNotPending: 'One or more task is not pending.',
reqNotMet: 'Not Match', reqNotMet: 'Not Match',
systemError: 'A system error occurred.', systemError: 'A system error occurred.',
taskOrderInvalid: 'Please select the product and the organization.',
flowAccountProductIdNotFound: 'Product not found in flow account',
}, },
}, },
@ -1490,42 +1450,4 @@ export default {
11: 'November', 11: 'November',
12: 'December', 12: 'December',
}, },
property: {
title: 'Property',
caption: 'Manage Properties',
table: {
name: 'Name',
nameEn: 'English Name',
type: 'Type',
},
dialog: {
title: '',
name: 'Name',
nameEn: 'English Name',
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)',
},
}; };

View file

@ -1,7 +1,7 @@
import eng from './eng'; import eng from './eng';
import tha from './tha'; // spellchecker:disable-line import tha from './tha';
export default { export default {
eng, eng,
tha, // spellchecker:disable-line tha,
}; };

View file

@ -4,7 +4,7 @@ export default {
save: 'บันทึก', save: 'บันทึก',
open: 'เปิด', open: 'เปิด',
close: 'ปิด', close: 'ปิด',
edit: 'แก้ไข{text}', edit: 'แก้ไข',
cancel: 'ยกเลิก', cancel: 'ยกเลิก',
back: 'ย้อนกลับ', back: 'ย้อนกลับ',
undo: 'ย้อนกลับ', undo: 'ย้อนกลับ',
@ -31,7 +31,6 @@ export default {
displayField: 'ฟิลด์แสดงผล', displayField: 'ฟิลด์แสดงผล',
order: 'ลำดับ', order: 'ลำดับ',
name: 'ชื่อ{msg}', name: 'ชื่อ{msg}',
nameEN: 'ชื่อ (ภาษาอังกฤษ)',
fullName: 'ชื่อ-สกุล', fullName: 'ชื่อ-สกุล',
detail: 'รายละเอียด{msg}', detail: 'รายละเอียด{msg}',
remark: 'หมายเหตุ{msg}', remark: 'หมายเหตุ{msg}',
@ -151,17 +150,6 @@ export default {
notIncluded: 'ไม่รวม', notIncluded: 'ไม่รวม',
dueDate: 'วันครบกำหนด', dueDate: 'วันครบกำหนด',
year: 'ปี', year: 'ปี',
tableOfContent: 'สารบัญ',
draw: 'วาด',
newUpload: 'อัปโหลดใหม่',
nativeLanguage: '{msg} ภาษาต้นทาง',
copy: 'คัดลอก',
paste: 'วาง',
period: 'ช่วงเวลา',
documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
}, },
menu: { menu: {
@ -204,14 +192,11 @@ export default {
title: 'จัดการ', title: 'จัดการ',
branch: 'สาขา', branch: 'สาขา',
personnel: 'บุคลากร', personnel: 'บุคลากร',
group: 'กลุ่ม',
productService: 'สินค้าและบริการ', productService: 'สินค้าและบริการ',
workflow: 'ขั้นตอนการทำงาน', workflow: 'ขั้นตอนการทำงาน',
property: 'คุณสมบัติ',
customer: 'ลูกค้า', customer: 'ลูกค้า',
mainData: 'ข้อมูลหลัก', mainData: 'ข้อมูลหลัก',
agencies: 'หน่วยงาน', agencies: 'หน่วยงาน',
businessType: 'ประเภทกิจการ',
}, },
sales: { sales: {
@ -255,12 +240,6 @@ export default {
mode: 'โหมด', mode: 'โหมด',
addSignature: 'เพิ่มลายเซ็น', addSignature: 'เพิ่มลายเซ็น',
}, },
manual: {
title: 'คู่มือ',
usage: 'การใช้งาน',
troubleshooting: 'การแก้ปัญหา',
},
}, },
noti: { noti: {
@ -338,7 +317,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น', letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น', numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก', requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น", branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
branchNameENField: branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น", "โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ', passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -467,13 +446,6 @@ export default {
citizenId: 'เลขที่บัตรประชาชน', citizenId: 'เลขที่บัตรประชาชน',
citizenIssue: 'วันที่ออกบัตร', citizenIssue: 'วันที่ออกบัตร',
citizenExpire: 'วันที่หมดอายุ', citizenExpire: 'วันที่หมดอายุ',
agencyStatus: 'สถานะเอเจนซี่',
normal: 'ปกติ',
canceled: 'ยกเลิก',
blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
}, },
}, },
customer: { customer: {
@ -500,16 +472,15 @@ export default {
}, },
prefix: { prefix: {
mr: 'นาย', mr: 'Mr.',
mrs: 'นาง', mrs: 'Mrs.',
miss: 'นางสาว', miss: 'Miss.',
}, },
citizenId: 'บัตรประจำตัวประชาชน', citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา', religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ', issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง', passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง', ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ', firstName: 'ชื่อ ',
@ -593,7 +564,7 @@ export default {
family: 'ข้อมูลครอบครัว', family: 'ข้อมูลครอบครัว',
}, },
workerStatus: 'สถานะคนงาน', workerStatus: 'สถานะคนงาน',
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า', previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
employerBranch: 'สาขานายจ้าง', employerBranch: 'สาขานายจ้าง',
employeeCode: 'รหัสลูกจ้าง', employeeCode: 'รหัสลูกจ้าง',
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)', nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
@ -626,7 +597,7 @@ export default {
placeOfBirth: 'สถานที่เกิด', placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด', countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก', issueCountry: 'ประเทศที่ออก',
entryCount: 'จำนวนวันที่เข้าประเทศ', entryCount: 'จำนวนที่เข้าประเทศ',
employerSelect: { employerSelect: {
branchName: 'ชื่อสาขา', branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง', customerName: 'ชื่อนายจ้าง',
@ -654,7 +625,7 @@ export default {
permitIssuedAt: 'สถานที่ออกใบอนุญาต', permitIssuedAt: 'สถานที่ออกใบอนุญาต',
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน', permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน', permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)', identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
}, },
formFamily: { formFamily: {
citizenId: citizenId:
@ -772,13 +743,10 @@ export default {
}, },
quotation: { quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา', quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย', seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน', paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่', channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร', bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี', bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม', inTheNameOf: 'ในนาม',
@ -926,10 +894,6 @@ export default {
code: 'รหัสหน่วยงาน', code: 'รหัสหน่วยงาน',
group: 'กลุ่มหน่วยงาน', group: 'กลุ่มหน่วยงาน',
name: 'ชื่อหน่วยงาน', name: 'ชื่อหน่วยงาน',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
}, },
requestList: { requestList: {
@ -951,7 +915,6 @@ export default {
nonLocalEmployee: 'พนักงานนอกพื้นที่', nonLocalEmployee: 'พนักงานนอกพื้นที่',
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน', noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
salesRepresentative: 'พนักงานขาย', salesRepresentative: 'พนักงานขาย',
dataOffice: 'สำนักงานเขตจัดหางาน',
ref: 'อ้างอิง', ref: 'อ้างอิง',
action: { action: {
title: 'จัดการ', title: 'จัดการ',
@ -967,7 +930,6 @@ export default {
Ended: 'จบงาน', Ended: 'จบงาน',
Completed: 'เสร็จสิ้น', Completed: 'เสร็จสิ้น',
Canceled: 'ยกเลิก', Canceled: 'ยกเลิก',
RejectCancel: 'ปฏิเสธคําขอยกเลิก',
}, },
Pending: 'รอดำเนินการ', Pending: 'รอดำเนินการ',
Ready: 'พร้อมดำเนินการ', Ready: 'พร้อมดำเนินการ',
@ -975,8 +937,6 @@ export default {
Completed: 'เสร็จสิ้น', Completed: 'เสร็จสิ้น',
Canceled: 'ยกเลิก', Canceled: 'ยกเลิก',
CancelRequested: 'ต้องการยกเลิก', CancelRequested: 'ต้องการยกเลิก',
RejectCancel: 'การปฏิเสธคําขอยกเลิก',
RejectedCancel: 'ปฏิเสธคําขอยกเลิกแล้ว',
AwaitOrder: 'รอสั่งงาน', AwaitOrder: 'รอสั่งงาน',
ReadyOrder: 'พร้อมสั่งงาน', ReadyOrder: 'พร้อมสั่งงาน',
@ -1070,9 +1030,6 @@ export default {
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้', confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
}, },
message: { message: {
copy: 'คัดลอก',
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก', quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
beingUse: '"{msg}" มีการใช้งานอยู่', beingUse: '"{msg}" มีการใช้งานอยู่',
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}', incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
@ -1194,14 +1151,13 @@ export default {
'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น', 'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น',
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว', userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว', sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
samePropertyNameExists: 'คุณสมบัตินี้มีอยู่ในระบบอยู่แล้ว',
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ', validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
codeMisMatch: 'รหัสไม่ตรงกัน', codeMisMatch: 'รหัสไม่ตรงกัน',
crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้', crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้',
errorOccurred: errorOccure:
'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง', 'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง',
invalidData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง', invalideData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง', authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง',
installmentsValidateFailed: installmentsValidateFailed:
'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ', 'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ',
@ -1220,8 +1176,6 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ', 'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน', reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด', systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
}, },
}, },
@ -1477,42 +1431,4 @@ export default {
11: 'พฤศจิกายน', 11: 'พฤศจิกายน',
12: 'ธันวาคม', 12: 'ธันวาคม',
}, },
property: {
title: 'คุณสมบัติ',
caption: 'จัดการคุณสมบัติ',
table: {
name: 'ชื่อ',
nameEn: 'ชื่อภาษาอังกฤษ',
type: 'ประเภท',
},
dialog: {
title: '',
name: 'ชื่อ',
nameEn: 'ชื่อภาษาอังกฤษ',
type: 'ประเภท',
},
},
dateRange: {
today: 'วันนี้',
yesterday: 'เมื่อวานนี้',
thisWeek: 'สัปดาห์นี้',
lastWeek: 'สัปดาห์ที่แล้ว',
thisMonth: 'เดือนนี้',
lastMonth: 'เดือนที่แล้ว',
thisYear: 'ปีนี้',
lastYear: 'ปีที่แล้ว',
last7Days: '7 วันที่ผ่านมา',
last30Days: '30 วันที่ผ่านมา',
last90Days: '90 วันที่ผ่านมา',
customDateRange: 'กำหนดช่วงวันที่เอง',
},
businessType: {
title: 'ประเภทกิจการ',
caption: 'จัดการประเภทกิจการ',
name: 'ชื่อประเภทกิจการ',
nameEn: 'ชื่อประเภทกิจการ (ภาษาอังกฤษ)',
},
}; };

View file

@ -6,8 +6,6 @@ import { Icon } from '@iconify/vue';
import useMyBranch from 'stores/my-branch'; import useMyBranch from 'stores/my-branch';
import { getUserId, getRole } from 'src/services/keycloak'; import { getUserId, getRole } from 'src/services/keycloak';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { canAccess } from 'src/stores/utils';
type Menu = { type Menu = {
label: string; label: string;
@ -16,13 +14,11 @@ type Menu = {
hidden?: boolean; hidden?: boolean;
disabled?: boolean; disabled?: boolean;
isax?: boolean; isax?: boolean;
noI18n?: boolean;
children?: Menu[]; children?: Menu[];
}; };
const router = useRouter(); const router = useRouter();
const $q = useQuasar(); const $q = useQuasar();
const { locale } = useI18n();
const userBranch = useMyBranch(); const userBranch = useMyBranch();
const { currentMyBranch } = storeToRefs(userBranch); const { currentMyBranch } = storeToRefs(userBranch);
@ -62,7 +58,39 @@ function branchSetting() {
//TODO: click setting (cog icon) on drawer menu //TODO: click setting (cog icon) on drawer menu
} }
function initMenu() { watch(
() => currentPath.value,
() => {
if (currentPath.value === '/') {
menuActive.value.fill(false);
menuActive.value[0] = true;
} else reActiveMenu();
},
);
watch(
() => props.mini,
() => {
if (props.mini) {
reActiveMenu();
}
},
);
onMounted(async () => {
const uid = getUserId();
role.value = getRole();
if (!uid) return;
if (role.value.includes('system')) {
const result = await userBranch.fetchListOptionBranch();
if (result && result.total > 0) currentMyBranch.value = result.result[0];
}
const result = await userBranch.fetchListMyBranch(uid);
if (result && result.total > 0) currentMyBranch.value = result.result[0];
menuData.value = [ menuData.value = [
{ {
label: 'menu.manage', label: 'menu.manage',
@ -71,45 +99,30 @@ function initMenu() {
{ {
label: 'branch', label: 'branch',
route: '/branch-management', route: '/branch-management',
hidden: !canAccess('branch'), 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')
),
}, },
{ {
label: 'personnel', label: 'personnel',
route: '/personnel-management', route: '/personnel-management',
hidden: !canAccess('personnel'), hidden: !(
}, role.value.includes('admin') ||
{ role.value.includes('head_of_admin') ||
label: 'group', role.value.includes('system') ||
route: '/group-management', role.value.includes('owner') ||
hidden: !canAccess('personnel'), role.value.includes('branch_manager')
}, ),
{
label: 'workflow',
route: '/workflow',
hidden: !canAccess('workflow'),
},
{
label: 'property',
route: '/property',
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: 'workflow', route: '/workflow' },
{ label: 'productService', route: '/product-service' },
{ label: 'customer', route: '/customer-management' },
{ label: 'agencies', route: '/agencies-management' },
], ],
}, },
{ {
@ -160,72 +173,10 @@ function initMenu() {
icon: 'mdi-monitor-dashboard', icon: 'mdi-monitor-dashboard',
children: [ children: [
{ label: 'report', route: '/report' }, { label: 'report', route: '/report' },
{ { label: 'dashboard', route: '/dash-board' },
label: 'dashboard',
route: '/dash-board',
hidden: !canAccess('dashBoard'),
},
],
},
{
label: 'menu.manual',
icon: 'mdi-book-open-variant-outline',
children: [
{
label: 'usage',
route: '/manual',
},
{
label: 'troubleshooting',
route: '/troubleshooting',
},
], ],
}, },
]; ];
}
watch(
() => currentPath.value,
() => {
if (currentPath.value === '/') {
menuActive.value.fill(false);
menuActive.value[0] = true;
} else reActiveMenu();
},
);
watch(
() => props.mini,
() => {
if (props.mini) {
reActiveMenu();
}
},
);
watch(
() => locale.value,
() => {
initMenu();
},
);
onMounted(async () => {
const uid = getUserId();
role.value = getRole();
if (!uid) return;
if (role.value.includes('system')) {
const result = await userBranch.fetchListOptionBranch();
if (result && result.total > 0) currentMyBranch.value = result.result[0];
}
const result = await userBranch.fetchListMyBranch(uid);
if (result && result.total > 0) currentMyBranch.value = result.result[0];
initMenu();
menuActive.value = menuData.value.map(() => false); menuActive.value = menuData.value.map(() => false);
@ -258,20 +209,14 @@ onMounted(async () => {
> >
<q-img <q-img
fit="contain" fit="contain"
:src="mini ? '/logo_jws.png' : '/logo.png'" :src="mini ? 'logo_jws.png' : '/logo.png'"
:style="{ filter: `brightness(${$q.dark.isActive ? '1.3' : '1'})` }" :style="{ filter: `brightness(${$q.dark.isActive ? '1.3' : '1'})` }"
style="height: 64px" style="height: 64px"
/> />
</header> </header>
<div id="drawer-menu" class="q-pl-md q-mr-xs q-gutter-y-sm"> <div id="drawer-menu" class="q-pl-md q-mr-xs q-gutter-y-sm">
<template <template v-for="(menu, i) in menuData" :key="i">
v-for="(menu, i) in menuData.filter(
(v) =>
!(v.children?.length === 0 || v.children?.every((i) => i.hidden)),
)"
:key="i"
>
<q-expansion-item <q-expansion-item
v-if="!menu.hidden" v-if="!menu.hidden"
:id="menu.label" :id="menu.label"
@ -312,10 +257,10 @@ onMounted(async () => {
style="white-space: nowrap" style="white-space: nowrap"
:style="!menuActive[i] && `color: var(--foreground)`" :style="!menuActive[i] && `color: var(--foreground)`"
> >
{{ menu.noI18n ? menu.label : $t(`${menu.label}.title`) }} {{ $t(`${menu.label}.title`) }}
</span> </span>
<q-tooltip :delay="500"> <q-tooltip :delay="500">
{{ menu.noI18n ? menu.label : $t(`${menu.label}.title`) }} {{ $t(`${menu.label}.title`) }}
</q-tooltip> </q-tooltip>
<q-menu <q-menu
@ -344,11 +289,7 @@ onMounted(async () => {
:id="`sub-menu-${sub.label}`" :id="`sub-menu-${sub.label}`"
> >
<span style="white-space: nowrap"> <span style="white-space: nowrap">
{{ {{ $t(`${menu.label}.${sub.label}`) }}
sub.noI18n
? sub.label
: $t(`${menu.label}.${sub.label}`)
}}
</span> </span>
</q-item> </q-item>
</template> </template>
@ -370,16 +311,12 @@ onMounted(async () => {
<nav <nav
class="row items-center no-wrap" class="row items-center no-wrap"
:class="{ :class="{
active: sub.route && currentPath.includes(sub.route), active: currentPath === sub.route,
disabled: sub.disabled, disabled: sub.disabled,
}" }"
> >
<span class="q-px-md" style="white-space: nowrap"> <span class="q-px-md" style="white-space: nowrap">
{{ {{ $t(`${menu.label}.${sub.label}`) }}
sub.noI18n
? sub.label
: $t(`${menu.label}.${sub.label}`)
}}
</span> </span>
</nav> </nav>
</div> </div>
@ -465,9 +402,8 @@ onMounted(async () => {
</span> </span>
</div> </div>
<!-- v-if="!mini" -->
<q-btn <q-btn
v-if="false" v-if="!mini"
dense dense
flat flat
rounded rounded

View file

@ -2,7 +2,7 @@
import { ref, onMounted, computed, reactive } from 'vue'; import { ref, onMounted, computed, reactive } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { getUserId, getUsername, getName, logout, getRole } from 'src/services/keycloak'; import { getUserId, getUsername, logout, getRole } from 'src/services/keycloak';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import moment from 'moment'; import moment from 'moment';
@ -39,7 +39,7 @@ const configStore = useConfigStore();
const { data: notificationData } = storeToRefs(notificationStore); const { data: notificationData } = storeToRefs(notificationStore);
const { visible } = storeToRefs(loaderStore); const { visible } = storeToRefs(loaderStore);
const { t, locale } = useI18n({ useScope: 'global' }); const { t } = useI18n({ useScope: 'global' });
const userStore = useUserStore(); const userStore = useUserStore();
const canvasModal = ref(false); const canvasModal = ref(false);
@ -50,16 +50,11 @@ const leftDrawerMini = ref(false);
const unread = computed<number>( const unread = computed<number>(
() => notificationData.value.filter((v) => !v.read).length || 0, () => notificationData.value.filter((v) => !v.read).length || 0,
); );
// const filterRole = ref<string[]>();
const userImage = ref<string>(); const userImage = ref<string>();
const userGender = ref(''); const userGender = ref('');
const userName = ref({ th: '', en: '' });
const canvasRef = ref(); 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: { const language: {
value: Lang; value: Lang;
label: string; label: string;
@ -129,17 +124,6 @@ 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 () => { onMounted(async () => {
initTheme(); initTheme();
initLang(); initLang();
@ -167,14 +151,9 @@ onMounted(async () => {
if (user === 'admin') return; if (user === 'admin') return;
if (uid) { if (uid) {
const res = await userStore.fetchById(uid); const res = await userStore.fetchById(uid);
if (res) { if (res && res.gender) {
if (res.gender) { userGender.value = res.gender;
userGender.value = res.gender; userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
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();
} }
} }
}); });
@ -388,28 +367,12 @@ onMounted(async () => {
<div class="col column text-caption q-pl-md ellipsis"> <div class="col column text-caption q-pl-md ellipsis">
<span class="block ellipsis full-width text-weight-bold"> <span class="block ellipsis full-width text-weight-bold">
{{ item.title }} {{ item.title }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.title }}
</q-tooltip>
</span> </span>
<span <span
class="block ellipsis full-width text-stone" class="block ellipsis full-width text-stone"
:class="{ 'text-weight-medium': !item.read }" :class="{ 'text-weight-medium': !item.read }"
> >
{{ item.detail }} {{ item.detail }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.detail }}
</q-tooltip>
</span> </span>
</div> </div>
<span <span
@ -419,6 +382,15 @@ onMounted(async () => {
> >
{{ moment(item.createdAt).fromNow() }} {{ moment(item.createdAt).fromNow() }}
</span> </span>
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="1000"
:offset="[10, 10]"
>
{{ item.title }}
{{ item.detail }}
</q-tooltip>
</q-item> </q-item>
</section> </section>
<section <section
@ -495,7 +467,6 @@ onMounted(async () => {
<!-- User --> <!-- User -->
<ProfileMenu <ProfileMenu
id="btn-profile-menu" id="btn-profile-menu"
:user-name="displayName"
@logout="doLogout" @logout="doLogout"
@edit-personal-info="console.log('edit')" @edit-personal-info="console.log('edit')"
@signature=" @signature="
@ -524,15 +495,13 @@ onMounted(async () => {
no-app-box no-app-box
:title="$t('menu.profile.addSignature')" :title="$t('menu.profile.addSignature')"
:close="() => (canvasModal = false)" :close="() => (canvasModal = false)"
:submit="signatureSubmit"
:show="signatureFetch"
> >
<CanvasComponent ref="canvasRef" v-model:modal="canvasModal" /> <CanvasComponent ref="canvasRef" v-model:modal="canvasModal" />
<template #footer> <template #footer>
<q-btn <q-btn
flat flat
dense dense
:label="$t('general.clear')" :label="$t('clear')"
@click=" @click="
() => { () => {
canvasRef.clearCanvas(), canvasRef.clearUpload(); canvasRef.clearCanvas(), canvasRef.clearUpload();

View file

@ -12,7 +12,6 @@ const filterRole = ref<string[]>();
defineProps<{ defineProps<{
userImage?: string; userImage?: string;
gender?: string; gender?: string;
userName?: string;
}>(); }>();
const inputFile = document.createElement('input'); const inputFile = document.createElement('input');
@ -32,7 +31,7 @@ const options = [
label: 'menu.profile.signature', label: 'menu.profile.signature',
value: 'signature', value: 'signature',
color: 'grey', color: 'grey',
disabled: false, disabled: true,
}, },
{ {
icon: 'mdi-brightness-6', icon: 'mdi-brightness-6',
@ -148,9 +147,9 @@ onMounted(async () => {
class="text-weight-bold ellipsis" class="text-weight-bold ellipsis"
style="max-width: 9vw" style="max-width: 9vw"
> >
{{ userName || getName() }} {{ getName() }}
<q-tooltip> <q-tooltip>
{{ userName || getName() }} {{ getName() }}
</q-tooltip> </q-tooltip>
</span> </span>
<span <span
@ -235,12 +234,12 @@ onMounted(async () => {
style="margin-top: 58px" style="margin-top: 58px"
> >
<span v-if="isLoggedIn()"> <span v-if="isLoggedIn()">
{{ userName || getName() }} {{ getName() }}
</span> </span>
<span v-else>{{ 'Guest' }}</span> <span v-else>{{ 'Guest' }}</span>
<q-tooltip> <q-tooltip>
<span v-if="isLoggedIn()"> <span v-if="isLoggedIn()">
{{ userName || getName() }} {{ getName() }}
</span> </span>
<span v-else>{{ 'Guest' }}</span> <span v-else>{{ 'Guest' }}</span>
</q-tooltip> </q-tooltip>

View file

@ -1,2 +0,0 @@
declare module 'markdown-it-image-figures';
declare module 'markdown-it-html5-media';

View file

@ -1,109 +0,0 @@
<script setup lang="ts">
// NOTE: Library
import { storeToRefs } from 'pinia';
import { onMounted, watch } from 'vue';
// NOTE: Components
// NOTE: Stores & Type
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, dataTroubleshooting } = storeToRefs(manualStore);
onMounted(async () => {
navigatorStore.current.title = 'menu.manual.title';
navigatorStore.current.path = [{ text: '' }];
});
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>
<div
class="column full-height no-wrap surface-1 rounded bordered overflow-hidden q-pa-sm"
>
<section class="scroll q-gutter-y-sm">
<q-expansion-item
v-for="v in $route.name === 'Manual' ? dataManual : dataTroubleshooting"
:key="v.labelEN"
:content-inset-level="0.5"
class="rounded overflow-hidden bordered"
dense
>
<template #header>
<div class="row items-center full-width">
<Icon
v-if="!!v.icon"
:icon="v.icon"
:color="'var(--brand-1)'"
class="q-mr-sm"
/>
{{ $i18n.locale === 'eng' ? v.labelEN : v.label }}
</div>
</template>
<q-item
v-for="x in v.page"
:key="x.labelEN"
clickable
dense
class="dot items-center rounded q-my-xs"
:to="
$route.name === 'Manual'
? `/manual/${v.category}/${x.name}`
: `/troubleshooting/${v.category}/${x.name}`
"
>
<Icon
v-if="!!x.icon"
:icon="x.icon"
width="16px"
:color="'var(--brand-1)'"
class="q-mr-sm"
/>
{{ $i18n.locale === 'eng' ? x.labelEN : x.label }}
</q-item>
</q-expansion-item>
</section>
</div>
</template>
<style scoped>
.dot::before {
content: '•';
margin-right: 8px;
font-size: 1.2em;
}
</style>

View file

@ -1,358 +0,0 @@
<script setup lang="ts">
import 'highlight.js/styles/magula.min.css';
import MarkdownIt from 'markdown-it';
import hljs from 'highlight.js';
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import mditFigureWithPCaption from 'markdown-it-image-figures';
import mditMedia from 'markdown-it-html5-media';
import mditAnchor from 'markdown-it-anchor';
import mditHighlight from 'markdown-it-highlightjs';
import { initLang, initTheme } from 'src/utils/ui';
import { baseUrl } from 'src/stores/utils';
import { useManualStore } from 'src/stores/manual';
const ROUTE = useRoute();
const manualStore = useManualStore();
const md = new MarkdownIt()
.use(mditAnchor)
.use(mditMedia.html5Media)
.use(mditHighlight, { hljs })
.use(mditFigureWithPCaption, { figcaption: 'alt' });
const wrapper = ref<HTMLDivElement>();
const category = ref('');
const page = ref('');
const content = ref('');
const contentParsed = ref<ReturnType<typeof md.parse>>();
const contentViewing = ref('');
const toc = ref(true);
onMounted(async () => {
if (typeof ROUTE.params['category'] === 'string') {
category.value = ROUTE.params['category'];
}
if (typeof ROUTE.params['page'] === 'string') {
page.value = ROUTE.params['page'];
}
initLang();
initTheme();
await getContent();
window.addEventListener('scroll', onScroll);
window.addEventListener('resize', onResize);
});
onUnmounted(() => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onResize);
});
async function getContent() {
if (!category.value || !page.value) return;
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, {});
}
}
}
function onScroll() {
let current = '';
document.querySelectorAll<HTMLElement>('h2,h3').forEach((v) => {
if (
window.top &&
window.top.scrollY + window.innerHeight / 2 > v.offsetTop
) {
current = v.id;
}
});
contentViewing.value = current;
}
function onResize() {
if (window.innerWidth > 1024) {
toc.value = true;
}
}
async function scrollTo(id: string) {
const pos = document.getElementById(id)?.offsetTop;
await nextTick(() => {
if (window.innerWidth < 1024) toc.value = false;
});
if (pos) {
wrapper.value?.scrollTo({
top: pos,
behavior: 'smooth',
});
}
}
</script>
<template>
<main
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"
class="surface-1 rounded col-md-3 col-12 scroll full-height"
>
<q-list padding>
<template v-for="(token, idx) in contentParsed" :key="idx">
<q-item
v-if="token.tag === 'h2' && token.type === 'heading_open'"
class="tabNative"
active-class="text-blue-7 active-item text-weight-medium tabActive"
:active="contentViewing === token.attrGet('id')"
@click="scrollTo(token.attrGet('id') || '')"
clickable
v-ripple
dense
exact
>
<q-item-section>
<q-item-label>
<q-icon size="11px" name="mdi-circle-medium" />
<span class="q-pl-xs">
{{ contentParsed?.[idx + 1].content }}
</span>
</q-item-label>
</q-item-section>
</q-item>
<q-item
v-if="token.tag === 'h3' && token.type === 'heading_open'"
class="tabNative child-tab"
active-class="text-blue-7 active-item text-weight-medium tabActive"
:active="contentViewing === token.attrGet('id')"
@click="scrollTo(token.attrGet('id') || '')"
clickable
v-ripple
dense
exact
>
<q-item-section>
<q-item-label>
<span class="q-pl-xl">
{{ contentParsed?.[idx + 1].content }}
</span>
</q-item-label>
</q-item-section>
</q-item>
</template>
</q-list>
</section>
<section v-if="!toc && $q.screen.lt.md">
<q-btn
dense
class="full-width text-capitalize"
flat
@click="toc = true"
color="info"
>
{{ $t('general.tableOfContent') }}
</q-btn>
</section>
<section
v-if="$q.screen.gt.xs || (!toc && $q.screen.xs)"
ref="wrapper"
class="markdown col scroll full-height rounded"
>
<div
style="
width: 100%;
flex: 1;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
padding: 1rem;
"
class="surface-1"
v-html="
md.render(
content.replaceAll(
'assets/',
$route.name === 'ManualView'
? `${baseUrl}/manual/${category}/assets/`
: `${baseUrl}/troubleshooting/${category}/assets/`,
),
)
"
></div>
</section>
</main>
</template>
<style lang="css" scoped>
.toc {
top: 4rem;
}
.active {
color: red;
}
.markdown :deep(:where(h1, h2, h3, h4, h5, h6)) {
line-height: 1.5;
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;
padding: 8px;
margin-bottom: 1rem;
}
.markdown :deep(blockquote > p:last-child) {
margin-bottom: 0;
}
.markdown :deep(img) {
vertical-align: middle;
}
.markdown :deep(p img) {
padding-inline: 0.25rem;
}
.markdown :deep(figure) {
margin: 0;
text-align: center;
padding: 1rem;
width: 100%;
}
.markdown :deep(figure img) {
max-width: 90%;
}
.markdown :deep(p:has(img:only-child) img) {
max-width: 100%;
}
.markdown :deep(h1) {
text-align: left;
margin-top: -1rem;
margin-inline: -1rem;
font-size: 24px;
font-weight: 700;
background-color: var(--surface-2);
border-radius: 8px 8px 0px 0px;
padding: 0px 16px;
}
.markdown :deep(.hljs) {
border-radius: 8px;
}
.markdown :deep(a) {
color: hsla(var(--info-bg));
}
.markdown :deep(figcaption) {
text-align: center;
}
.markdown :deep(h2) {
text-align: left;
margin-block: 0;
font-size: 18px;
font-weight: 600;
padding: 0px 16px;
}
.markdown :deep(h3) {
text-align: left;
margin-block: 0;
font-size: 16px;
font-weight: 600;
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>

View file

@ -94,22 +94,21 @@ onMounted(async () => {
<template> <template>
<main class="column full-height no-wrap"> <main class="column full-height no-wrap">
<div class="surface-1 col bordered rounded column"> <div class="surface-1 col bordered rounded column">
<div class="q-py-xs row items-center" style="padding-inline: 38px"> <div class="q-px-lg q-py-xs row items-center">
<q-checkbox <q-checkbox
size="xs" size="xs"
:model-value="noti.length > 0 && selectedNoti.length === noti.length" :model-value="selectedNoti.length === noti.length"
:disable="noti.length === 0" class="q-px-sm"
@click="toggleSelection('', true)" @click="toggleSelection('', true)"
/> />
<q-separator vertical inset spaced="md" />
<q-btn <q-btn
v-if="selectedNoti.length === 0" v-if="selectedNoti.length === 0"
icon="mdi-refresh" icon="mdi-refresh"
rounded rounded
flat flat
dense dense
size="sm" size="xs"
class="app-text-muted-2 q-mt-xs" class="app-text-muted-2 q-ml-sm q-mt-xs"
@click="async () => await fetchNoti()" @click="async () => await fetchNoti()"
> >
<q-tooltip>Refresh</q-tooltip> <q-tooltip>Refresh</q-tooltip>
@ -120,8 +119,8 @@ onMounted(async () => {
rounded rounded
flat flat
dense dense
size="sm" size="xs"
class="app-text-muted-2" class="app-text-muted-2 q-ml-sm"
@click="async () => await deleteNoti()" @click="async () => await deleteNoti()"
> >
<q-tooltip>{{ $t('general.delete') }}</q-tooltip> <q-tooltip>{{ $t('general.delete') }}</q-tooltip>
@ -132,7 +131,7 @@ onMounted(async () => {
rounded rounded
flat flat
dense dense
size="sm" size="xs"
class="app-text-muted-2 q-mx-sm" class="app-text-muted-2 q-mx-sm"
@click="async () => await markAsRead()" @click="async () => await markAsRead()"
> >
@ -178,13 +177,7 @@ onMounted(async () => {
<q-badge <q-badge
rounded rounded
class="q-ml-md" class="q-ml-md"
:color=" style="background: hsl(var(--info-bg))"
(tab.value === 'all'
? noti.length
: noti.filter((v) => !v.read).length) > 0
? 'info'
: 'grey'
"
> >
{{ {{
tab.value === 'all' tab.value === 'all'

View file

@ -6,11 +6,11 @@ import { Icon } from '@iconify/vue';
import { BranchContact } from 'stores/branch-contact/types'; import { BranchContact } from 'stores/branch-contact/types';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { QTableProps, QTableSlots } from 'quasar'; import type { QSelect, QTableProps, QTableSlots } from 'quasar';
import { resetScrollBar } from 'src/stores/utils'; import { resetScrollBar } from 'src/stores/utils';
import useBranchStore from 'stores/branch'; import useBranchStore from 'stores/branch';
import useFlowStore from 'stores/flow'; import useFlowStore from 'stores/flow';
import { isRoleInclude, canAccess } from 'stores/utils'; import { isRoleInclude } from 'stores/utils';
import { import {
BranchWithChildren, BranchWithChildren,
BranchCreate, BranchCreate,
@ -52,7 +52,6 @@ import {
UndoButton, UndoButton,
} from 'components/button'; } from 'components/button';
import { useNavigator } from 'src/stores/navigator'; import { useNavigator } from 'src/stores/navigator';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const $q = useQuasar(); const $q = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
@ -73,6 +72,7 @@ const typeBranchItem = [
color: 'var(--blue-6-hsl)', color: 'var(--blue-6-hsl)',
}, },
]; ];
const refFilter = ref<InstanceType<typeof QSelect>>();
const holdDialog = ref(false); const holdDialog = ref(false);
const isSubCreate = ref(false); const isSubCreate = ref(false);
const columns = [ const columns = [
@ -175,8 +175,6 @@ const qrCodeDialog = ref(false);
const qrCodeimageUrl = ref<string>(''); const qrCodeimageUrl = ref<string>('');
const formLastSubBranch = ref<number>(0); const formLastSubBranch = ref<number>(0);
const searchDate = ref<string[]>([]);
const branchStore = useBranchStore(); const branchStore = useBranchStore();
const flowStore = useFlowStore(); const flowStore = useFlowStore();
const { locale } = useI18n(); const { locale } = useI18n();
@ -717,20 +715,12 @@ async function fetchList(opts: {
tree?: boolean; tree?: boolean;
withHead?: boolean; withHead?: boolean;
filter?: 'head' | 'sub'; filter?: 'head' | 'sub';
startDate?: string;
endDate?: string;
}) { }) {
await branchStore.fetchList(opts); await branchStore.fetchList(opts);
} }
watch([inputSearch, searchDate], () => { watch(inputSearch, () => {
fetchList({ fetchList({ tree: true, query: inputSearch.value, withHead: true });
tree: true,
query: inputSearch.value,
withHead: true,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
});
currentSubBranch.value = undefined; currentSubBranch.value = undefined;
}); });
@ -791,8 +781,6 @@ async function onSubmit(submitSelectedItem?: boolean) {
); );
if (!res) return; if (!res) return;
formType.value = 'view';
formData.value.codeHeadOffice = formData.value.code = res.code; formData.value.codeHeadOffice = formData.value.code = res.code;
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`; imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
@ -867,9 +855,7 @@ async function onSubmit(submitSelectedItem?: boolean) {
actionText: t('dialog.action.ok'), actionText: t('dialog.action.ok'),
persistent: true, persistent: true,
title: t('form.warning.title'), title: t('form.warning.title'),
cancel: () => { cancel: () => {},
formType.value = 'create';
},
action: async () => { action: async () => {
await createBranch(); await createBranch();
}, },
@ -1050,7 +1036,7 @@ watch(currentHq, () => {
{{ $t('branch.allBranch') }} {{ $t('branch.allBranch') }}
</div> </div>
<q-btn <q-btn
v-if="isRoleInclude(['system'])" v-if="isRoleInclude(['head_of_admin', 'admin', 'system'])"
round round
flat flat
size="md" size="md"
@ -1074,7 +1060,6 @@ watch(currentHq, () => {
<div class="col full-width scroll"> <div class="col full-width scroll">
<div class="q-pa-md"> <div class="q-pa-md">
<TreeComponent <TreeComponent
:hide-create="!canAccess('branch', 'create')"
v-model:nodes="treeData" v-model:nodes="treeData"
v-model:expanded-tree="expandedTree" v-model:expanded-tree="expandedTree"
node-key="id" node-key="id"
@ -1185,49 +1170,26 @@ watch(currentHq, () => {
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="mdi-magnify" /> <q-icon name="mdi-magnify" />
</template> </template>
<template v-slot:append> <template v-if="$q.screen.lt.md" v-slot:append>
<q-separator vertical inset class="q-mr-xs" /> <span class="row">
<q-separator vertical />
<AdvanceSearch <q-btn
v-model="searchDate" icon="mdi-filter-variant"
:active="$q.screen.lt.md && statusFilter !== 'all'" unelevated
> class="q-ml-sm"
<div padding="4px"
v-if="$q.screen.lt.md" size="sm"
class="q-mt-sm text-weight-medium" rounded
> @click="refFilter?.showPopup"
{{ $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',
},
]"
/> />
</AdvanceSearch> </span>
</template> </template>
</q-input> </q-input>
<div class="row col-md-6 justify-end"> <div class="row col-md-6 justify-end">
<q-select <q-select
v-if="$q.screen.gt.sm" v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter" v-model="statusFilter"
outlined outlined
dense dense
@ -1562,18 +1524,8 @@ watch(currentHq, () => {
</q-td> </q-td>
<q-td> <q-td>
<KebabAction <KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status" :status="props.row.status"
:idName="props.row.name" :idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view=" @view="
if (props.row.isHeadOffice) { if (props.row.isHeadOffice) {
triggerEdit( triggerEdit(
@ -1713,18 +1665,8 @@ watch(currentHq, () => {
> >
<template v-slot:action> <template v-slot:action>
<KebabAction <KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status" :status="props.row.status"
:idName="props.row.name" :idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view=" @view="
if (props.row.isHeadOffice) { if (props.row.isHeadOffice) {
triggerEdit( triggerEdit(
@ -2272,22 +2214,13 @@ watch(currentHq, () => {
@click="drawerEdit()" @click="drawerEdit()"
type="button" type="button"
/> />
<DeleteButton
<template v-if="formType !== 'edit'"
v-if=" id="btn-info-basic-delete"
formType !== 'edit' && formTypeBranch === 'headOffice' icon-only
? isRoleInclude(['system']) @click="triggerDelete(currentEdit.id)"
: canAccess('branch', 'create') type="button"
" />
>
<DeleteButton
v-if="formType !== 'edit'"
id="btn-info-basic-delete"
icon-only
@click="triggerDelete(currentEdit.id)"
type="button"
/>
</template>
</div> </div>
</div> </div>
<div <div

View file

@ -1,67 +0,0 @@
<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>

View file

@ -10,8 +10,8 @@ import useOptionStore from 'stores/options';
import useAddressStore from 'stores/address'; import useAddressStore from 'stores/address';
import useMyBranch from 'src/stores/my-branch'; import useMyBranch from 'src/stores/my-branch';
import { calculateAge } from 'src/utils/datetime'; import { calculateAge } from 'src/utils/datetime';
import { useQuasar, type QTableProps } from 'quasar'; import { QSelect, useQuasar, type QTableProps } from 'quasar';
import { dialog, baseUrl, setPrefixName } from 'stores/utils'; import { dialog, baseUrl } from 'stores/utils';
import { useNavigator } from 'src/stores/navigator'; import { useNavigator } from 'src/stores/navigator';
import { isRoleInclude, resetScrollBar } from 'src/stores/utils'; import { isRoleInclude, resetScrollBar } from 'src/stores/utils';
import { BranchUserStats } from 'stores/branch/types'; import { BranchUserStats } from 'stores/branch/types';
@ -49,7 +49,6 @@ import FormPerson from 'components/02_personnel-management/FormPerson.vue';
import FormByType from 'components/02_personnel-management/FormByType.vue'; import FormByType from 'components/02_personnel-management/FormByType.vue';
import FormInformation from 'components/02_personnel-management/FormInformation.vue'; import FormInformation from 'components/02_personnel-management/FormInformation.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue'; import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { locale, t } = useI18n(); const { locale, t } = useI18n();
const $q = useQuasar(); const $q = useQuasar();
@ -74,6 +73,7 @@ const isImageEdit = ref(false);
const imageDialog = ref(false); const imageDialog = ref(false);
const infoDrawerEdit = ref(false); const infoDrawerEdit = ref(false);
const refreshImageState = ref(false); const refreshImageState = ref(false);
const refFilter = ref<InstanceType<typeof QSelect>>();
const firstScroll = ref(false); const firstScroll = ref(false);
const inputSearch = ref(''); const inputSearch = ref('');
@ -93,14 +93,12 @@ const currentUser = ref<User>();
const userCode = ref<string>(); const userCode = ref<string>();
const statusToggle = ref(true); const statusToggle = ref(true);
const userFile = ref<File[]>([]); const agencyFile = ref<File[]>([]);
const userFileList = ref<{ name: string; url: string }[]>([]); const agencyFileList = ref<{ name: string; url: string }[]>([]);
const typeStats = ref<UserTypeStats>(); const typeStats = ref<UserTypeStats>();
const userStats = ref<BranchUserStats[]>(); const userStats = ref<BranchUserStats[]>();
const searchDate = ref<[]>([]);
const urlProfile = ref<string>(); const urlProfile = ref<string>();
const profileFileImg = ref<File | null>(null); const profileFileImg = ref<File | null>(null);
const imageList = ref<{ selectedImage: string; list: string[] }>(); const imageList = ref<{ selectedImage: string; list: string[] }>();
@ -126,7 +124,7 @@ const defaultFormData = {
streetEN: '', streetEN: '',
street: '', street: '',
trainingPlace: null, trainingPlace: null,
importNationality: [], importNationality: null,
sourceNationality: null, sourceNationality: null,
licenseExpireDate: null, licenseExpireDate: null,
licenseIssueDate: null, licenseIssueDate: null,
@ -153,18 +151,6 @@ const defaultFormData = {
citizenExpire: null, citizenExpire: null,
citizenIssue: null, citizenIssue: null,
citizenId: '', 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>({ const formData = ref<UserCreate>({
@ -186,7 +172,7 @@ const formData = ref<UserCreate>({
streetEN: '', streetEN: '',
street: '', street: '',
trainingPlace: null, trainingPlace: null,
importNationality: [], importNationality: null,
sourceNationality: null, sourceNationality: null,
licenseExpireDate: null, licenseExpireDate: null,
licenseIssueDate: null, licenseIssueDate: null,
@ -213,18 +199,6 @@ const formData = ref<UserCreate>({
citizenExpire: null, citizenExpire: null,
citizenIssue: null, citizenIssue: null,
citizenId: '', 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 }[]>([ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
@ -353,7 +327,7 @@ function onClose(excludeDialog?: boolean) {
urlProfile.value = ''; urlProfile.value = '';
profileFileImg.value = null; profileFileImg.value = null;
infoDrawerEdit.value = false; infoDrawerEdit.value = false;
userFile.value = []; agencyFile.value = [];
isEdit.value = false; isEdit.value = false;
statusToggle.value = true; statusToggle.value = true;
isImageEdit.value = false; isImageEdit.value = false;
@ -362,8 +336,6 @@ function onClose(excludeDialog?: boolean) {
mapUserType(currentTab.value); mapUserType(currentTab.value);
imageList.value = { selectedImage: '', list: [] }; imageList.value = { selectedImage: '', list: [] };
onCreateImageList.value = { selectedImage: '', list: [] }; onCreateImageList.value = { selectedImage: '', list: [] };
userFileList.value = [];
userFile.value = [];
flowStore.rotate(); flowStore.rotate();
} }
@ -384,10 +356,12 @@ async function openDialog(
isEdit.value = true; isEdit.value = true;
await assignFormData(id); await assignFormData(id);
const result = await userStore.fetchAttachment(id); if (formData.value.userType === 'AGENCY') {
const result = await userStore.fetchAttachment(id);
if (result) { if (result) {
userFileList.value = result; agencyFileList.value = result;
}
} }
} }
if (userStore.userOption.hqOpts.length !== 0 && !id) { if (userStore.userOption.hqOpts.length !== 0 && !id) {
@ -445,37 +419,15 @@ async function onSubmit(excludeDialog?: boolean) {
: ''; : '';
const formDataEdit = { const formDataEdit = {
...formData.value, ...formData.value,
checkpointEN: formData.value.checkpoint,
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE', 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; } as const;
await userStore.editById(currentUser.value.id, formDataEdit); await userStore.editById(currentUser.value.id, formDataEdit);
if (userFile.value) { if (currentUser.value.id && formDataEdit.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = { const payload: UserAttachmentCreate = {
file: userFile.value, file: agencyFile.value,
}; };
if (payload?.file) { if (payload?.file) {
@ -498,39 +450,16 @@ async function onSubmit(excludeDialog?: boolean) {
: hqId.value : hqId.value
? hqId.value ? hqId.value
: ''; : '';
formData.value.checkpointEN = formData.value.checkpoint;
const result = await userStore.create( 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, onCreateImageList.value,
); );
if (userFile.value && result) { if (result && formData.value.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = { const payload: UserAttachmentCreate = {
file: userFile.value, file: agencyFile.value,
}; };
if (payload?.file) { if (payload?.file) {
@ -622,20 +551,12 @@ async function assignFormData(idEdit: string) {
currentUser.value = foundUser; currentUser.value = foundUser;
formData.value = { formData.value = {
branchId: foundUser.branch[0]?.id, branchId: foundUser.branch[0]?.id,
provinceId: foundUser.addressForeign provinceId: foundUser.provinceId,
? foundUser.provinceText districtId: foundUser.districtId,
: foundUser.provinceId, subDistrictId: foundUser.subDistrictId,
districtId: foundUser.addressForeign
? foundUser.districtText
: foundUser.districtId,
subDistrictId: foundUser.addressForeign
? foundUser.subDistrictText
: foundUser.subDistrictId,
telephoneNo: foundUser.telephoneNo, telephoneNo: foundUser.telephoneNo,
email: foundUser.email, email: foundUser.email,
zipCode: foundUser.addressForeign zipCode: foundUser.zipCode,
? foundUser.zipCodeText
: foundUser.zipCode,
gender: foundUser.gender, gender: foundUser.gender,
addressEN: foundUser.addressEN, addressEN: foundUser.addressEN,
address: foundUser.address, address: foundUser.address,
@ -646,10 +567,7 @@ async function assignFormData(idEdit: string) {
street: foundUser.street, street: foundUser.street,
streetEN: foundUser.streetEN, streetEN: foundUser.streetEN,
trainingPlace: foundUser.trainingPlace, trainingPlace: foundUser.trainingPlace,
importNationality: importNationality: foundUser.importNationality,
typeof foundUser.importNationality === 'string'
? [foundUser.importNationality]
: foundUser.importNationality,
sourceNationality: foundUser.sourceNationality, sourceNationality: foundUser.sourceNationality,
licenseNo: foundUser.licenseNo, licenseNo: foundUser.licenseNo,
discountCondition: foundUser.discountCondition, discountCondition: foundUser.discountCondition,
@ -669,8 +587,6 @@ async function assignFormData(idEdit: string) {
responsibleArea: foundUser.responsibleArea, responsibleArea: foundUser.responsibleArea,
status: foundUser.status, status: foundUser.status,
selectedImage: foundUser.selectedImage, selectedImage: foundUser.selectedImage,
contactName: foundUser.contactName || '',
contactTel: foundUser.contactTel || '',
licenseExpireDate: licenseExpireDate:
(foundUser.licenseExpireDate && (foundUser.licenseExpireDate &&
new Date(foundUser.licenseExpireDate)) || new Date(foundUser.licenseExpireDate)) ||
@ -687,12 +603,6 @@ async function assignFormData(idEdit: string) {
(foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null, (foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null,
citizenExpire: citizenExpire:
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null, (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' formData.value.status === 'ACTIVE' || 'CREATED'
@ -751,8 +661,6 @@ async function fetchUserList(mobileFetch?: boolean) {
: statusFilter.value === 'statusACTIVE' : statusFilter.value === 'statusACTIVE'
? 'ACTIVE' ? 'ACTIVE'
: 'INACTIVE', : 'INACTIVE',
startDate: searchDate.value[0],
endDate: searchDate.value[1],
}); });
if (ret) { if (ret) {
@ -819,17 +727,7 @@ watch(
watch( watch(
() => formData.value.userType, () => formData.value.userType,
async (type) => { async () => {
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; if (!infoDrawerEdit.value) return;
formData.value.registrationNo = null; formData.value.registrationNo = null;
formData.value.startDate = null; formData.value.startDate = null;
@ -837,11 +735,11 @@ watch(
formData.value.responsibleArea = null; formData.value.responsibleArea = null;
formData.value.discountCondition = null; formData.value.discountCondition = null;
formData.value.sourceNationality = null; formData.value.sourceNationality = null;
formData.value.importNationality = []; formData.value.importNationality = null;
formData.value.trainingPlace = null; formData.value.trainingPlace = null;
formData.value.checkpoint = null; formData.value.checkpoint = null;
formData.value.checkpointEN = null; formData.value.checkpointEN = null;
userFile.value = []; agencyFile.value = [];
}, },
); );
@ -852,7 +750,7 @@ watch(
}, },
); );
watch([inputSearch, statusFilter, pageSize, searchDate], async () => { watch([inputSearch, statusFilter, pageSize], async () => {
if (userData.value) userData.value.result = []; if (userData.value) userData.value.result = [];
currentPage.value = 1; currentPage.value = 1;
@ -974,45 +872,26 @@ watch(
<template #prepend> <template #prepend>
<q-icon name="mdi-magnify" /> <q-icon name="mdi-magnify" />
</template> </template>
<template v-slot:append> <template v-if="$q.screen.lt.md" v-slot:append>
<q-separator vertical inset class="q-mr-xs" /> <span class="row">
<AdvanceSearch <q-separator vertical />
v-model="searchDate" <q-btn
:active="$q.screen.lt.md && statusFilter !== 'all'" icon="mdi-filter-variant"
> unelevated
<div class="q-ml-sm"
v-if="$q.screen.lt.md" padding="4px"
class="q-mt-sm text-weight-medium" size="sm"
> rounded
{{ $t('general.status') }} @click="refFilter?.showPopup"
</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',
},
]"
/> />
</AdvanceSearch> </span>
</template> </template>
</q-input> </q-input>
<div class="row col-md-5" style="white-space: nowrap"> <div class="row col-md-5" style="white-space: nowrap">
<q-select <q-select
v-if="$q.screen.gt.sm" v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter" v-model="statusFilter"
outlined outlined
dense dense
@ -1362,7 +1241,7 @@ watch(
{{ {{
locale === 'eng' locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim() ? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim() : `${props.row.firstName} ${props.row.lastName}`.trim()
}} }}
<q-tooltip <q-tooltip
anchor="bottom left" anchor="bottom left"
@ -1372,7 +1251,7 @@ watch(
{{ {{
locale === 'eng' locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim() ? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim() : `${props.row.firstName} ${props.row.lastName}`.trim()
}} }}
</q-tooltip> </q-tooltip>
@ -1638,12 +1517,9 @@ watch(
hide-action hide-action
:is-edit="infoDrawerEdit" :is-edit="infoDrawerEdit"
:title=" :title="
(currentUser.namePrefix locale === 'eng'
? $t('customer.form.prefix.' + currentUser.namePrefix) + ' '
: '') +
(locale === 'eng'
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}` ? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
: `${currentUser.firstName || currentUser.firstNameEN} ${currentUser.lastName || currentUser.lastNameEN}`) : `${currentUser.firstName} ${currentUser.lastName}`
" "
v-model:drawerOpen="infoDrawer" v-model:drawerOpen="infoDrawer"
:submit="() => onSubmit()" :submit="() => onSubmit()"
@ -1675,18 +1551,7 @@ watch(
v-model:toggle-status="formData.status" v-model:toggle-status="formData.status"
hideFade hideFade
:toggle-title="$t('status.title')" :toggle-title="$t('status.title')"
:title=" :title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName || formData.firstNameEN,
lastName: formData.lastName || formData.lastNameEN,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:caption="userCode" :caption="userCode"
:img=" :img="
`${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat( `${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat(
@ -1871,15 +1736,12 @@ watch(
v-model:citizen-id="formData.citizenId" v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue" v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire" v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
:title="'personnel.form.personalInformation'" :title="'personnel.form.personalInformation'"
prefix-id="drawer-info-personnel" prefix-id="drawer-info-personnel"
dense dense
outlined outlined
separator separator
:readonly="!infoDrawerEdit" :readonly="!infoDrawerEdit"
:agency="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
@ -1897,15 +1759,10 @@ watch(
v-model:district-id="formData.districtId" v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId" v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode" 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" :readonly="!infoDrawerEdit"
prefix-id="drawer-info-personnel" prefix-id="drawer-info-personnel"
:title="'personnel.form.addressInformation'" :title="'personnel.form.addressInformation'"
dense dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
<FormByType <FormByType
@ -1924,11 +1781,10 @@ watch(
v-model:import-nationality="formData.importNationality" v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace" v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint" v-model:checkpoint="formData.checkpoint"
v-model:user-file="userFile" v-model:checkpoint-en="formData.checkpointEN"
v-model:user-file-list="userFileList" v-model:agency-file="agencyFile"
v-model:agency-file-list="agencyFileList"
v-model:user-id="currentUser.id" v-model:user-id="currentUser.id"
v-model:remark="formData.remark"
v-model:agency-status="formData.agencyStatus"
/> />
</div> </div>
</div> </div>
@ -1972,18 +1828,7 @@ watch(
}[formData.gender] }[formData.gender]
" "
:toggleTitle="$t('status.title')" :toggleTitle="$t('status.title')"
:title=" :title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName,
lastName: formData.lastName,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:fallbackImg=" :fallbackImg="
{ {
male: '/no-img-man.png', male: '/no-img-man.png',
@ -2009,6 +1854,7 @@ watch(
<div <div
class="col" class="col"
id="personnel-form"
:class="{ :class="{
'q-px-lg q-pb-lg': $q.screen.gt.sm, 'q-px-lg q-pb-lg': $q.screen.gt.sm,
'q-px-md q-pb-sm': !$q.screen.gt.sm, 'q-px-md q-pb-sm': !$q.screen.gt.sm,
@ -2052,7 +1898,7 @@ watch(
? [ ? [
{ {
name: $t('personnel.form.workInformation'), name: $t('personnel.form.workInformation'),
anchor: 'dialog-form-work', anchor: 'dialog-info-work',
}, },
] ]
: [], : [],
@ -2068,7 +1914,6 @@ watch(
</div> </div>
</div> </div>
<div <div
id="personnel-form"
class="col-md-10 col-12 full-height scroll" class="col-md-10 col-12 full-height scroll"
:class="{ :class="{
'q-py-md q-pr-md ': $q.screen.gt.sm, 'q-py-md q-pr-md ': $q.screen.gt.sm,
@ -2094,7 +1939,6 @@ watch(
id="dialog-form-personal" id="dialog-form-personal"
prefix-id="form-dialog-personnel" prefix-id="form-dialog-personnel"
dense dense
:agency="formData.userType === 'AGENCY'"
outlined outlined
separator separator
:title="'personnel.form.personalInformation'" :title="'personnel.form.personalInformation'"
@ -2113,8 +1957,6 @@ watch(
v-model:citizen-id="formData.citizenId" v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue" v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire" v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
class="q-mb-xl" class="q-mb-xl"
/> />
<AddressForm <AddressForm
@ -2131,13 +1973,8 @@ watch(
v-model:district-id="formData.districtId" v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId" v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode" 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" prefix-id="drawer-info-personnel"
dense dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
<FormByType <FormByType
@ -2155,10 +1992,8 @@ watch(
v-model:import-nationality="formData.importNationality" v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace" v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint" v-model:checkpoint="formData.checkpoint"
v-model:agency-status="formData.agencyStatus" v-model:checkpoint-en="formData.checkpointEN"
v-model:remark="formData.remark" v-model:agency-file="agencyFile"
v-model:user-file="userFile"
v-model:user-file-list="userFileList"
/> />
</div> </div>
</div> </div>

View file

@ -9,7 +9,7 @@ import { baseUrl } from 'src/stores/utils';
import useCustomerStore from 'stores/customer'; import useCustomerStore from 'stores/customer';
import useFlowStore from 'stores/flow'; import useFlowStore from 'stores/flow';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import { dialog, canAccess } from 'stores/utils'; import { dialog } from 'stores/utils';
import { Status } from 'stores/types'; import { Status } from 'stores/types';
import { Employee } from 'stores/employee/types'; import { Employee } from 'stores/employee/types';
@ -18,8 +18,6 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
import { columnsEmployee } from './constant'; import { columnsEmployee } from './constant';
import { useCustomerBranchForm, useEmployeeForm } from './form'; 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 EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue';
import FloatingActionButton from 'components/FloatingActionButton.vue'; import FloatingActionButton from 'components/FloatingActionButton.vue';
import SideMenu from 'components/SideMenu.vue'; import SideMenu from 'components/SideMenu.vue';
@ -91,11 +89,6 @@ const prop = withDefaults(
currentCitizenId?: string; currentCitizenId?: string;
gender: string; gender: string;
selectedImage: string; selectedImage: string;
fetchImageList: (
id: string,
selectedName: string,
type: 'customer' | 'employee',
) => Promise<void>;
}>(), }>(),
{ {
color: 'green', color: 'green',
@ -103,6 +96,7 @@ const prop = withDefaults(
); );
const currentBranchEmployee = ref<string>(''); const currentBranchEmployee = ref<string>('');
const listEmployee = ref<Employee[]>([]); const listEmployee = ref<Employee[]>([]);
const customerId = defineModel<string>('customerId', { required: true }); const customerId = defineModel<string>('customerId', { required: true });
defineEmits<{ defineEmits<{
@ -112,6 +106,16 @@ defineEmits<{
(e: 'dialog'): void; (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 = [ const columns = [
{ {
name: 'branchName', name: 'branchName',
@ -253,6 +257,10 @@ async function fetchEmployee(opts: { branchId: string; pageSize?: number }) {
} }
} }
onMounted(async () => {
await fetchList();
});
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => { watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
await fetchList(); await fetchList();
}); });
@ -272,22 +280,12 @@ watch(
} }
}, },
); );
onMounted(async () => {
customerBranchFormState.value.currentCustomerId = route.params
.customerId as string;
await fetchList();
branch.value?.forEach((v) => {
currentBtnOpen.value.push(false);
});
});
</script> </script>
<template> <template>
<FloatingActionButton <FloatingActionButton
style="z-index: 999" style="z-index: 999"
v-if="$route.name !== 'CustomerManagement' && canAccess('customer', 'edit')" v-if="$route.name !== 'CustomerManagement'"
@click="openEmployerBranchForm('create')" @click="openEmployerBranchForm('create')"
hide-icon hide-icon
></FloatingActionButton> ></FloatingActionButton>
@ -475,6 +473,7 @@ onMounted(async () => {
<q-tr <q-tr
:class="{ :class="{
'app-text-muted': props.row.status === 'INACTIVE', 'app-text-muted': props.row.status === 'INACTIVE',
'cursor-pointer': props.row._count?.branch !== 0,
}" }"
:props="props" :props="props"
@click="$emit('viewDetail', props.row, props.rowIndex)" @click="$emit('viewDetail', props.row, props.rowIndex)"
@ -548,13 +547,7 @@ onMounted(async () => {
v-if="branchFieldSelected.includes('businessTypePure')" v-if="branchFieldSelected.includes('businessTypePure')"
class="text-left" class="text-left"
> >
{{ {{ useOptionStore().mapOption(props.row.businessType) || '-' }}
props.row.businessType
? props.row.businessType[
$i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-'
}}
</q-td> </q-td>
<q-td <q-td
v-if="branchFieldSelected.includes('totalEmployee')" v-if="branchFieldSelected.includes('totalEmployee')"
@ -573,6 +566,8 @@ onMounted(async () => {
await fetchEmployee({ await fetchEmployee({
branchId: currentBranchEmployee, branchId: currentBranchEmployee,
pageSize: 999, pageSize: 999,
passport: true,
visa: true,
}); });
currentBtnOpen.map((v, i) => { currentBtnOpen.map((v, i) => {
@ -620,7 +615,7 @@ onMounted(async () => {
<div class="text-center"> <div class="text-center">
<TableEmpoloyee <TableEmpoloyee
:prefix-id="props.row.registerName || props.row.firstName" :prefix-id="props.row.registerName || props.row.firstName"
:add-button="canAccess('customer', 'edit')" add-button
in-table in-table
:list-employee="listEmployee" :list-employee="listEmployee"
:columns-employee="columnsEmployee" :columns-employee="columnsEmployee"
@ -643,15 +638,10 @@ onMounted(async () => {
" "
@history="(item) => {}" @history="(item) => {}"
@view=" @view="
async (item) => { (item) => {
employeeFormState.drawerModal = true; employeeFormState.drawerModal = true;
//employeeFormState.isEmployeeEdit = true; //employeeFormState.isEmployeeEdit = true;
employeeFormStore.assignFormDataEmployee(item.id); employeeFormStore.assignFormDataEmployee(item.id);
await fetchImageList(
item.id,
item.selectedImage || '',
'employee',
);
} }
" "
/> />
@ -678,11 +668,9 @@ onMounted(async () => {
? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}` ? `${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 || ''}`, : `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`,
telephone: props.row.telephoneNo, telephone: props.row.telephoneNo,
businessTypePure: props.row.businessType businessTypePure: useOptionStore().mapOption(
? props.row.businessType[ props.row.businessType,
$i18n.locale === 'eng' ? 'nameEN' : 'name' ),
]
: '-',
totalEmployee: props.row._count?.employee, totalEmployee: props.row._count?.employee,
}" }"
:visible-columns="branchFieldSelected" :visible-columns="branchFieldSelected"
@ -771,10 +759,7 @@ onMounted(async () => {
/> />
<DeleteButton <DeleteButton
icon-only icon-only
v-if=" v-if="customerBranchFormState.dialogType === 'info'"
customerBranchFormState.dialogType === 'info' &&
canAccess('customer', 'edit')
"
@click=" @click="
() => { () => {
deleteBranchById(customerBranchFormData.id || ''); deleteBranchById(customerBranchFormData.id || '');
@ -868,6 +853,7 @@ onMounted(async () => {
v-model:last-name-en="customerBranchFormData.lastNameEN" v-model:last-name-en="customerBranchFormData.lastNameEN"
v-model:gender="customerBranchFormData.gender" v-model:gender="customerBranchFormData.gender"
v-model:birth-date="customerBranchFormData.birthDate" v-model:birth-date="customerBranchFormData.birthDate"
v-model:customer-name="customerBranchFormData.customerName"
v-model:legal-person-no="customerBranchFormData.legalPersonNo" v-model:legal-person-no="customerBranchFormData.legalPersonNo"
v-model:branch-code="customerBranchFormData.code" v-model:branch-code="customerBranchFormData.code"
v-model:register-name="customerBranchFormData.registerName" v-model:register-name="customerBranchFormData.registerName"
@ -897,14 +883,15 @@ onMounted(async () => {
</div> </div>
<EmployerFormBusiness <EmployerFormBusiness
dense dense
class="q-mb-xl"
outlined outlined
prefix-id="employer-branch" prefix-id="employer-branch"
:readonly="customerBranchFormState.dialogType === 'info'" :readonly="customerBranchFormState.dialogType === 'info'"
v-model:business-type-id="customerBranchFormData.businessTypeId" v-model:bussiness-type="customerBranchFormData.businessType"
v-model:job-position="customerBranchFormData.jobPosition" v-model:job-position="customerBranchFormData.jobPosition"
v-model:job-description="customerBranchFormData.jobDescription" v-model:job-description="customerBranchFormData.jobDescription"
v-model:pay-date="customerBranchFormData.payDate" v-model:pay-date="customerBranchFormData.payDate"
v-model:pay-date-en="customerBranchFormData.payDateEN" v-model:pay-date-e-n="customerBranchFormData.payDateEN"
v-model:wage-rate="customerBranchFormData.wageRate" v-model:wage-rate="customerBranchFormData.wageRate"
v-model:wage-rate-text="customerBranchFormData.wageRateText" v-model:wage-rate-text="customerBranchFormData.wageRateText"
/> />
@ -995,7 +982,7 @@ onMounted(async () => {
v-model:email="customerBranchFormData.email" v-model:email="customerBranchFormData.email"
v-model:contact-tel="customerBranchFormData.contactTel" v-model:contact-tel="customerBranchFormData.contactTel"
v-model:office-tel="customerBranchFormData.officeTel" v-model:office-tel="customerBranchFormData.officeTel"
v-model:agent-user-id="customerBranchFormData.agentUserId" v-model:agent="customerBranchFormData.agent"
/> />
</div> </div>
</div> </div>
@ -1011,18 +998,6 @@ onMounted(async () => {
/> />
</template> </template>
</DialogFormContainer> </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> </template>
<style scoped> <style scoped>

Some files were not shown because too many files have changed in this diff Show more