Compare commits

..

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

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

12398
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -7,69 +7,62 @@
"type": "module",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev",
"build": "quasar build",
"postinstall": "quasar prepare",
"changelog:generate": "git-cliff -o CHANGELOG.md"
},
"dependencies": {
"@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0",
"@quasar/extras": "^1.16.17",
"@tato30/vue-pdf": "^1.11.3",
"@quasar/extras": "^1.16.12",
"@tato30/vue-pdf": "^1.11.0",
"@vuepic/vue-datepicker": "^8.8.1",
"apexcharts": "^4.5.0",
"axios": "^1.8.4",
"axios": "^1.7.4",
"cropperjs": "^1.6.2",
"dayjs": "^1.11.13",
"highlight.js": "^11.11.1",
"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",
"keycloak-js": "^25.0.4",
"mime": "^4.0.4",
"moment": "^2.30.1",
"number-to-words": "^1.2.4",
"open-props": "^1.7.14",
"pinia": "^2.3.1",
"quasar": "^2.18.1",
"signature_pad": "^5.0.7",
"open-props": "^1.7.5",
"pinia": "^2.2.2",
"quasar": "^2.16.9",
"signature_pad": "^5.0.2",
"socket.io-client": "^4.7.5",
"tesseract.js": "^5.1.1",
"thai-baht-text": "^2.0.5",
"udsv": "^0.6.0",
"uuid": "^10.0.0",
"vue": "^3.5.13",
"vue": "^3.4.38",
"vue3-apexcharts": "^1.7.0",
"vue-dragscroll": "^4.0.6",
"vue-i18n": "^11.1.2",
"vue-i18n": "^9.14.0",
"vue-pdf": "^4.3.0",
"vue-router": "^4.5.0",
"vue-tsc": "^2.2.8",
"vue3-apexcharts": "^1.8.0"
"vue-router": "^4.4.3"
},
"devDependencies": {
"@faker-js/faker": "^9.6.0",
"@iconify/vue": "^4.3.0",
"@intlify/unplugin-vue-i18n": "^6.0.5",
"@playwright/test": "^1.51.1",
"@quasar/app-vite": "^2.2.0",
"@types/markdown-it": "^14.1.2",
"@types/markdown-it-highlightjs": "^3.3.4",
"@types/node": "^20.17.28",
"@faker-js/faker": "^9.3.0",
"@iconify/vue": "^4.1.2",
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@playwright/test": "^1.46.1",
"@quasar/app-vite": "2.0.0-beta.19",
"@types/node": "^20.16.1",
"@types/number-to-words": "^1.2.3",
"@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",
"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",
"vue-component-type-helpers": "^2.2.8"
"vue-component-type-helpers": "^2.1.10"
},
"engines": {
"node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",
"node": "^24 || ^22 || ^20 || ^18",
"npm": ">= 6.13.4",
"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": {
"visaType": [
{
"label": "Non-LA",
"value": "nla"
},
{
"label": "Non-B",
"value": "nb"
},
{
"label": "TV.60",
"value": "tv60"
},
{
"label": "Non-TR",
"value": "ntr"
},
{
"label": "TV.30",
"value": "tv30"
}
],
"workerStatus": [
{
"label": "Normal",
@ -177,21 +154,20 @@
{ "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" },
{ "label": "CUST", "value": "CUST" }
{ "label": "WP44", "value": "WP44" }
],
"prefix": [
{
"label": "MR",
"label": "Mr",
"value": "mr"
},
{
"label": "MRS",
"label": "Mrs",
"value": "mrs"
},
{
"label": "MISS",
"label": "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"
},
{
"label": "Koh Song, Ranong province",
"label": "Myanmar Labor Training Center - Kawthoung, Ranong Province",
"value": "trainingRanong"
},
{
"label": "Nong Khai, Nong Khai Province",
"label": "Laos Labor Training Center - Nong Khai, Nong Khai Province",
"value": "trainingNongKhai"
},
{
"label": "Aranyaprathet, Sa Kaeo Province",
"label": "Cambodian Labor Training Center - Aranyaprathet, Sa Kaeo Province",
"value": "trainingSaKaeo"
},
{
"label": "Ban Laem, Chanthaburi Province",
"label": "Cambodian Labor Training Center - Ban Laem, Chanthaburi Province",
"value": "trainingChanthaburi"
}
],
"training": [
{
"label": "The first center accepts work. and end of employment Tak Province",
"value": "trainingTak"
},
{
"label": "The first center accepts work. and end of employment Nong Khai Province",
"value": "trainingNongKhai"
},
{
"label": "The first center accepts work. and end of employment Sa Kaeo Province",
"value": "trainingSaKaeo"
}
],
"nationality": [
{
"label": "Thai",
@ -1043,75 +1004,10 @@
"label": "Ministry of Labour",
"value": "MOL"
}
],
"propertiesField": [
{
"label": "Worker status",
"value": "workerStatus",
"type": "array"
},
{
"label": "Work type",
"value": "workType",
"type": "array"
},
{
"label": "Hospital name / Medical entitlement",
"value": "hospitalNEntitlement",
"type": "string"
},
{
"label": "Reference Number (Ref)",
"value": "refNo",
"type": "string"
},
{
"label": "Work Status",
"value": "workStatus",
"type": "string"
},
{
"label": "Contact channels",
"value": "contactChannel",
"type": "string"
},
{
"label": "Remark",
"value": "remark",
"type": "string"
},
{ "label": "Document Check", "value": "documentCheck", "type": "string" },
{ "label": "Duty", "value": "duty", "type": "string" },
{ "label": "Messenger", "value": "messenger", "type": "string" },
{ "label": "Form", "value": "designForm", "type": "string" }
]
},
"tha": {
"visaType": [
{
"label": "Non-LA",
"value": "nla"
},
{
"label": "Non-B",
"value": "nb"
},
{
"label": "ผผ.60",
"value": "tv60"
},
{
"label": "Non-TR",
"value": "ntr"
},
{
"label": "ผผ.30",
"value": "tv30"
}
],
"workerStatus": [
{
"label": "ปกติ",
@ -1266,8 +1162,7 @@
{ "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" },
{ "label": "CUST", "value": "CUST" }
{ "label": "WP44", "value": "WP44" }
],
"prefix": [
@ -1296,44 +1191,29 @@
}
],
"border": [
"training": [
{
"label": "แม่สอด จ.ตาก",
"label": "สถานที่อบรมแรงงานเมียนมา-แม่สอด จ.ตาก",
"value": "trainingTak"
},
{
"label": "เกาะสอง จ.ระนอง",
"label": "สถานที่อบรมแรงงานเมียนมา-เกาะสอง จ.ระนอง",
"value": "trainingRanong"
},
{
"label": "หนองคาย จ.หนองคาย",
"label": "สถานที่อบรมแรงงานลาว-หนองคาย จ.หนองคาย",
"value": "trainingNongKhai"
},
{
"label": "อรัญประเทศ จ.สระแก้ว",
"label": "สถานที่อบรมแรงงานกัมพูชา-อรัญประเทศ จ.สระแก้ว",
"value": "trainingSaKaeo"
},
{
"label": "บ้านแหลม จ.จันทบุรี",
"label": "สถานที่อบรมแรงงานกัมพูชา-บ้านแหลม จ.จันทบุรี",
"value": "trainingChanthaburi"
}
],
"training": [
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดตาก",
"value": "trainingTak"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดหนองคาย",
"value": "trainingNongKhai"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดสระแก้ว",
"value": "trainingSaKaeo"
}
],
"nationality": [
{
"label": "ไทย",
@ -2133,48 +2013,6 @@
"label": "กรมแรงงาน",
"value": "MOL"
}
],
"propertiesField": [
{
"label": "สถานะคนงาน",
"value": "workerStatus",
"type": "array"
},
{
"label": "ประเภทงาน",
"value": "workType",
"type": "array"
},
{
"label": "ชื่อ รพ./สิทธิการรักษา",
"value": "hospitalNEntitlement",
"type": "string"
},
{
"label": "เลขที่อ้างอิง (Ref)",
"value": "refNo",
"type": "string"
},
{
"label": "สถานะงาน",
"value": "workStatus",
"type": "string"
},
{
"label": "ช่องทางการติดต่อ",
"value": "contactChannel",
"type": "string"
},
{
"label": "หมายเหตุ",
"value": "remark",
"type": "string"
},
{ "label": "ตรวจสอบเอกสาร", "value": "documentCheck", "type": "string" },
{ "label": "อากร", "value": "duty", "type": "string" },
{ "label": "พนักงานส่งเอกสาร", "value": "messenger", "type": "string" },
{ "label": "ออกแบบฟอร์ม", "value": "designForm", "type": "string" }
]
}
}

View file

@ -1,22 +1,26 @@
/* eslint-env node */
// Configuration for your app
// 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';
export default defineConfig((ctx) => {
export default configure((ctx) => {
return {
eslint: {
fix: true,
warnings: true,
errors: true,
},
boot: ['i18n', 'axios', 'components'],
css: ['app.scss'],
extras: ['mdi-v7'],
build: {
target: {
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node20',
},
typescript: {
vueShim: true,
},
vueRouterMode: 'history',
vitePlugins: [
[
@ -31,7 +35,7 @@ export default defineConfig((ctx) => {
devServer: {
host: '0.0.0.0',
open: false,
port: 5174,
port: 5173,
},
framework: {
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 { defineBoot } from '#q-app/wrappers';
import { boot } from 'quasar/wrappers';
import { getToken } from 'src/services/keycloak';
import { dialog } from 'stores/utils';
import useLoader from 'stores/loader';
import useFlowStore from 'src/stores/flow';
declare module 'vue' {
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance;
$api: AxiosInstance;
@ -24,10 +24,10 @@ function parseError(
status: number,
body?: { status: number; message: string; code: string },
) {
if (status === 422) return 'invalidData';
if (status === 422) return 'invalideData';
if (body && body.code) return body.code;
return 'errorOccurred';
return 'errorOccure';
}
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
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 '@vuepic/vue-datepicker/dist/main.css';
import GlobalDialog from 'components/GlobalDialog.vue';
@ -6,7 +6,7 @@ import GlobalLoading from 'components/GlobalLoading.vue';
import VueDragscroll from 'vue-dragscroll';
import VueApexCharts from 'vue3-apexcharts';
export default defineBoot(({ app }) => {
export default boot(({ app }) => {
app.component('global-dialog', GlobalDialog);
app.component('global-loading', GlobalLoading);
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 messages from 'src/i18n';
import { Lang } from 'src/utils/ui';
export type MessageLanguages = keyof typeof messages;
// Type-define 'eng' as the master schema for the resource
@ -22,17 +21,16 @@ declare module 'vue-i18n' {
}
/* eslint-enable @typescript-eslint/no-empty-interface */
export const i18n = createI18n<
{ message: MessageSchema },
MessageLanguages,
false
>({
export const i18n = createI18n({
locale: 'tha',
legacy: false,
messages,
messages: {
'en-US': {},
...messages,
},
});
export default defineBoot(({ app }) => {
export default boot(({ app }) => {
// Set i18n instance on app
app.use(i18n);
});

View file

@ -15,7 +15,6 @@ defineProps<{
hidden?: boolean;
disabled?: boolean;
isax?: boolean;
tab?: string;
color:
| 'green'
| 'red'
@ -29,8 +28,8 @@ defineProps<{
}[];
}>();
function navigateTo(destination: string, tab?: string) {
router.push({ path: `${destination}`, query: tab ? { tab } : {} });
function navigateTo(destination: string) {
router.push(`${destination}`);
}
</script>
@ -45,7 +44,7 @@ function navigateTo(destination: string, tab?: string) {
v-for="(v, i) in list.filter((item) => !item.hidden)"
:key="i"
:bordered="$q.dark.isActive"
@click="!v.disabled && navigateTo(v.value, v.tab)"
@click="!v.disabled && navigateTo(v.value)"
>
<AppCircle
:id="`menu-icon-${v.value}`"

View file

@ -89,7 +89,15 @@ defineProps<{
</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>
<template v-if="!$slots.data">
<div

View file

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

View file

@ -159,6 +159,42 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
]"
for="input-name-en"
/>
<q-select
v-if="
typeBranch !== 'headOffice' &&
isRoleInclude(['head_of_admin', 'head_of_account'])
"
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
class="col-2"
dense
for="input-branch-status"
:readonly="readonly || isRoleInclude(['head_of_account'])"
:options="['Virtual', 'Branch']"
:hide-dropdown-icon="readonly"
:label="$t('general.branchStatus')"
:model-value="virtual ? 'Virtual' : 'Branch'"
@update:model-value="(v) => (virtual = v === 'Virtual')"
:rules="[(val) => val && val.length > 0]"
:error-message="$t('form.error.required')"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 row q-col-gutter-sm">

View file

@ -1,13 +1,13 @@
<script setup lang="ts">
import useUserStore from 'stores/user';
import useOptionStore from 'stores/options';
import { UserAttachmentDelete, AgencyStatus } from 'stores/user/types';
import { dialog } from 'stores/utils';
import { onMounted, ref } from 'vue';
import { UserAttachmentDelete } from 'stores/user/types';
import { dialog, selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { Icon } from '@iconify/vue';
import { QSelect } from 'quasar';
import DatePicker from '../shared/DatePicker.vue';
import SelectInput from 'src/components/shared/SelectInput.vue';
import SelectOffice from 'components/shared/select-muliple/SelectOffice.vue';
@ -29,16 +29,15 @@ const discountCondition = defineModel<string | null | undefined>(
const sourceNationality = defineModel<string | null | undefined>(
'sourceNationality',
);
const importNationality = defineModel<string[] | null | undefined>(
const importNationality = defineModel<string | null | undefined>(
'importNationality',
);
const trainingPlace = defineModel<string | null | undefined>('trainingPlace');
const checkpoint = defineModel<string | null | undefined>('checkpoint');
const userFile = defineModel<File[]>('userFile');
const userFileList =
defineModel<{ name: string; url: string }[]>('userFileList');
const remark = defineModel<string | null | undefined>('remark');
const agencyStatus = defineModel<string | null | undefined>('agencyStatus');
const checkpoint = defineModel<string | null | undefined>('checkPoint');
const checkpointEN = defineModel<string | null | undefined>('checkPointEn');
const agencyFile = defineModel<File[]>('agencyFile');
const agencyFileList =
defineModel<{ name: string; url: string }[]>('agencyFileList');
const attachmentRef = ref();
@ -70,12 +69,66 @@ function deleteFile(name: string) {
userStore.deleteAttachment(userId.value, payload);
const result = await userStore.fetchAttachment(userId.value);
if (result) {
userFileList.value = result;
agencyFileList.value = result;
}
},
cancel: () => {},
});
}
const nationalityOptions = ref<Record<string, unknown>[]>([]);
let nationalityFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const trainingPlaceOptions = ref<Record<string, unknown>[]>([]);
let trainingPlaceFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const responsibleAreaOptions = ref<Record<string, unknown>[]>([]);
let responsibleAreaFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
onMounted(() => {
if (optionStore.globalOption?.nationality) {
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.nationality),
nationalityOptions,
'label',
);
trainingPlaceFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.training),
trainingPlaceOptions,
'label',
);
responsibleAreaFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.area),
responsibleAreaOptions,
'label',
);
}
});
watch(
() => optionStore.globalOption,
() => {
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.nationality),
nationalityOptions,
'label',
);
trainingPlaceFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.training),
trainingPlaceOptions,
'label',
);
},
);
</script>
<template>
<div class="row col-12">
@ -133,12 +186,11 @@ function deleteFile(name: string) {
/>
<SelectOffice
v-if="userType === 'MESSENGER'"
for="input-responsible-area"
v-model:value="responsibleArea"
v-if="userType === 'MESSENGER'"
:readonly="readonly"
:label="$t('personnel.form.responsibleArea')"
class="col"
/>
</div>
<div
@ -166,103 +218,138 @@ function deleteFile(name: string) {
class="row col-12 q-col-gutter-sm"
style="margin-left: 0px; padding-left: 0px"
>
<SelectInput
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
<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-source-nationality"
for="input-source-nationality"
:option="optionStore.globalOption.nationality"
class="col-md-3 col-6"
:readonly
clearable
:dense="dense"
:readonly="readonly"
:hide-dropdown-icon="readonly"
:label="$t('personnel.form.sourceNationality')"
:options="nationalityOptions"
@filter="nationalityFilter"
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
@update:model-value="
(v) => (typeof v === 'string' ? (sourceNationality = v) : '')
"
/>
<SelectInput
v-model="importNationality"
@clear="sourceNationality = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-value="value"
option-label="label"
class="col-md-3 col-6"
id="input-import-nationality"
for="input-import-nationality"
:option="optionStore.globalOption.nationality"
class="col-md-3 col-6"
:readonly
multiple
:hideSelected="false"
clearable
fillInput
:dense="dense"
:readonly="readonly"
:hide-dropdown-icon="readonly"
:label="$t('personnel.form.importNationality')"
/>
<SelectInput
:model-value="readonly ? checkpoint || '-' : checkpoint"
id="select-checkpoint"
for="select-checkpoint"
:option="optionStore.globalOption.border"
class="col-md-6 col-12"
:readonly
:label="$t('personnel.form.checkpoint')"
clearable
:options="nationalityOptions"
@filter="nationalityFilter"
:model-value="readonly ? importNationality || '-' : importNationality"
@update:model-value="
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
(v) => (typeof v === 'string' ? (importNationality = v) : '')
"
/>
<SelectInput
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
@clear="importNationality = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="label"
class="col-md-6 col-12"
id="select-trainig-place"
for="select-trainig-place"
:option="optionStore.globalOption.training"
class="col-md-8 col-12"
:readonly
:dense="dense"
:readonly="readonly"
:hide-dropdown-icon="readonly"
:label="$t('personnel.form.trainingPlace')"
clearable
:options="trainingPlaceOptions"
@filter="trainingPlaceFilter"
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
@update:model-value="
(v) => (typeof v === 'string' ? (trainingPlace = v) : '')
"
/>
<SelectInput
:model-value="readonly ? agencyStatus || '-' : agencyStatus"
id="select-checkpoint-en"
for="select-checkpoint-en"
:option="[
{ label: $t('personnel.form.normal'), value: AgencyStatus.Normal },
{
label: $t('personnel.form.canceled'),
value: AgencyStatus.Canceled,
},
{
label: $t('personnel.form.blacklist'),
value: AgencyStatus.Blacklist,
},
]"
class="col-md-4 col-12"
:readonly
:label="$t('personnel.form.agencyStatus')"
clearable
@update:model-value="
(v) => (typeof v === 'string' ? (agencyStatus = v) : '')
"
/>
@clear="trainingPlace = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-input
for="input-discount-condition"
for="input-checkpoint"
:dense="dense"
outlined
:readonly
:label="$t('general.remark')"
class="col-12"
type="textarea"
:model-value="readonly ? remark || '-' : remark"
:readonly="readonly"
:label="$t('personnel.form.checkpoint')"
class="col-6"
:model-value="readonly ? checkpoint || '-' : checkpoint"
@update:model-value="
(v) => (typeof v === 'string' ? (remark = v) : '')
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
"
@clear="remark = ''"
@clear="checkpoint = ''"
/>
<q-input
for="input-checkpoint-en"
:dense="dense"
outlined
:readonly="readonly"
:label="$t('personnel.form.checkpointEN')"
class="col-6"
:model-value="readonly ? checkpointEN || '-' : checkpointEN"
@update:model-value="
(v) => (typeof v === 'string' ? (checkpointEN = v) : '')
"
@clear="checkpointEN = ''"
/>
</div>
<q-file
v-if="userType && !readonly"
ref="attachmentRef"
for="input-attachment"
:dense="dense"
@ -271,8 +358,8 @@ function deleteFile(name: string) {
multiple
append
:label="$t('personnel.form.attachment')"
class="col"
v-model="userFile"
class="col-12"
v-model="agencyFile"
>
<template v-slot:prepend>
<Icon
@ -299,12 +386,12 @@ function deleteFile(name: string) {
</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-item
id="attachment-file"
for="attachment-file"
v-for="item in userFileList"
v-for="item in agencyFileList"
clickable
:key="item.url"
class="items-center row"
@ -334,4 +421,5 @@ function deleteFile(name: string) {
</div>
</div>
</div>
</div>
</template>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@ import { QSelect } from 'quasar';
import { CustomerBranch } from 'stores/customer/types';
import { selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref, watch } from 'vue';
import SelectCustomer from 'components/shared/select/SelectCustomer.vue';
import {
EditButton,
DeleteButton,
@ -12,7 +11,6 @@ import {
} from 'components/button';
import { useI18n } from 'vue-i18n';
import useOptionStore from 'stores/options';
import { formatAddress } from 'src/utils/address';
const { locale } = useI18n();
@ -23,13 +21,6 @@ const optionsBranch = defineModel<{ id: string; name: string }[]>(
);
// employee
const customerBranchId = defineModel<string>('customerBranchId');
const currentCustomerBranch = defineModel<CustomerBranch>(
'currentCustomerBranch',
);
const customerBranch = defineModel<{
id: string;
address: string;
@ -54,7 +45,6 @@ defineProps<{
employeeOwnerOption?: CustomerBranch[];
prefixId: string;
showBtnSave?: boolean;
disableCustomerSelect?: boolean;
}>();
defineEmits<{
@ -117,18 +107,180 @@ defineEmits<{
</div>
<div class="col-12 row" style="gap: var(--size-2)">
<SelectCustomer
id="form-select-customer-branch-id"
for="form-select-customer-branch-id"
v-model:value="customerBranchId"
v-model:value-option="currentCustomerBranch"
<q-select
:id="`${prefixId}-select-employer-branch`"
:for="`${prefixId}-select-employer-branch`"
:use-input="!customerBranch"
autocomplete="off"
input-debounce="0"
:hide-dropdown-icon="readonly"
:dense="dense"
outlined
:readonly="readonly"
hide-bottom-space
class="col-12"
:label="$t('customer.form.branchCode')"
class="col-12 field-two"
simple
required
:readonly
:disabled="disableCustomerSelect && !readonly"
v-model="customerBranch"
:option-value="
(v) => ({
id: v.id,
address: v.address,
addressEN: v.addressEN,
provinceId: v.provinceId,
districtId: v.districtId,
subDistrictId: v.subDistrictId,
zipCode: v.zipCode,
})
"
emit-value
map-options
:options="employeeOwnerOption"
@filter="(val, update) => $emit('filterOwnerBranch', val, update)"
:rules="[
(val: string) =>
!!val ||
$t('form.error.selectField', {
field: $t('customerEmployee.branch'),
}),
]"
>
<template v-slot:option="scope">
<q-item
v-if="scope.opt"
v-bind="scope.itemProps"
class="row items-start col-12 no-padding"
>
<div class="q-ma-sm">
<i class="isax isax-frame5" style="color: var(--brand-1)" />
</div>
<div class="q-mt-sm">
<div>
<span v-if="scope.opt.customer.customerType">
<span style="font-weight: 600">
{{
scope.opt.customer.customerType === 'CORP'
? $t('customer.form.registerName')
: $t('customer.form.ownerName')
}}:
</span>
{{
scope.opt.customer.customerType === 'CORP'
? $i18n.locale === 'eng'
? scope.opt.registerNameEN
: scope.opt.registerName
: $i18n.locale === 'eng'
? `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstNameEN} ${scope.opt.lastNameEN}` ||
'-'
: `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstName} ${scope.opt.lastName}` ||
'-'
}}
({{ scope.opt.code }})
</span>
</div>
<div class="text-caption app-text-muted-2 q-mb-xs">
<span v-if="scope.opt.customer" class="col column">
{{
$t(
`branch.form.title.${scope.opt.code.endsWith('-00') ? 'branchHQLabel' : 'branchLabel'}`,
)
}}
{{
!scope.opt.code.endsWith('-00')
? +scope.opt.code.split('-')[1]
: ''
}}
</span>
<span v-if="scope.opt.province" class="col">
{{ $t('general.address') }}
{{
$i18n.locale === 'eng'
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
}}
{{ scope.opt.subDistrict?.zipCode || '' }}
</span>
</div>
</div>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template v-slot:selected-item="scope">
<div
v-if="scope.opt"
class="row items-center no-wrap"
style="width: 1px"
>
<div class="q-mr-sm">
<span style="font-weight: 600">
{{
scope.opt.customer.customerType === 'CORP'
? $t('customer.form.registerName')
: $t('customer.form.ownerName')
}}:
</span>
{{
scope.opt.customer.customerType === 'CORP'
? $i18n.locale === 'eng'
? scope.opt.registerNameEN
: scope.opt.registerName
: $i18n.locale === 'eng'
? `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstNameEN} ${scope.opt.lastNameEN}` ||
'-'
: `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstName} ${scope.opt.lastName}` ||
'-'
}}
({{ scope.opt.code }})
</div>
<div
class="text-caption app-text-muted-2"
v-if="scope.opt.customer && scope.opt.province"
>
{{
$t(
`branch.form.title.${scope.opt.code.endsWith('-00') ? 'branchHQLabel' : 'branchLabel'}`,
)
}}
{{
!scope.opt.code.endsWith('-00')
? +scope.opt.code.split('-')[1]
: ''
}}
{{ $t('general.address') }}
{{
$i18n.locale === 'eng'
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
}}
{{ scope.opt.subDistrict?.zipCode || '' }}
<q-tooltip v-if="scope.opt.customer && scope.opt.province">
{{ $t('customerBranch.form.title') }}:
{{ $t('general.address') }}
{{
$i18n.locale === 'eng'
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
}}
{{ scope.opt.subDistrict?.zipCode || '' }}
</q-tooltip>
</div>
</div>
</template>
<template v-slot:append>
<q-icon
v-if="!readonly && customerBranch"
name="mdi-close-circle"
@click.stop="customerBranch = undefined"
class="cursor-pointer clear-btn"
/>
</template>
</q-select>
<q-input
:for="`${prefixId}-input-code`"

View file

@ -6,14 +6,12 @@ import { useI18n } from 'vue-i18n';
import useUserStore from 'src/stores/user';
import useOptionStore from 'src/stores/options';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { baseUrl } from 'stores/utils';
import { getRole } from 'src/services/keycloak';
import {
WorkflowUserInTable,
WorkflowTemplatePayload,
WorkFlowPayloadStep,
Group,
} from 'src/stores/workflow-template/types';
import { User } from 'src/stores/user/types';
@ -22,18 +20,15 @@ import ToggleButton from 'src/components/button/ToggleButton.vue';
import NoData from '../NoData.vue';
import SelectBranch from '../shared/select/SelectBranch.vue';
import AddButton from '../button/AddButton.vue';
import { QField } from 'quasar';
defineProps<{
readonly?: boolean;
onDrawer?: boolean;
hideAction?: boolean;
}>();
const { t } = useI18n();
const userStore = useUserStore();
const optionStore = useOptionStore();
const workflowStore = useWorkflowTemplate();
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
default: [],
@ -48,7 +43,7 @@ const flowData = defineModel<WorkflowTemplatePayload>('flowData', {
},
});
let objectOptions = [
const objectOptions = [
...(optionStore.globalOption?.agenciesType || []),
{ label: t('flow.customer'), value: 'customer' },
{ label: t('flow.officer'), value: 'officer' },
@ -56,9 +51,7 @@ let objectOptions = [
const options = ref(objectOptions);
const role = ref<string[]>([]);
const userList = ref<User[]>([]);
const groupList = ref<Group[]>([]);
const responsiblePersonSearch = ref('');
const responsibleMenu = ref(false);
async function getUserList(opts?: { query: string }) {
const resUser = await userStore.fetchList({
@ -67,10 +60,10 @@ async function getUserList(opts?: { query: string }) {
if (resUser) userList.value = resUser.result;
}
async function getGroupList() {
const resGroup = await workflowStore.getGroupList();
if (resGroup) groupList.value = resGroup;
}
// async function getUserById(responsiblePersonId: string) {
// const resUser = await userStore.fetchById(responsiblePersonId);
// if (resUser) userInTable.value.push(resUser);
// }
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
const currStep = flowData.value.step[stepIndex];
@ -85,7 +78,6 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
userInTable.value[stepIndex] = {
name: flowData.value.step[stepIndex].name,
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(
val: Record<string, unknown>,
responsibleInstitution?: string[],
@ -177,7 +142,6 @@ watch(
onMounted(async () => {
role.value = getRole() || [];
await getUserList();
await getGroupList();
await userStore.fetchHqOption();
});
</script>
@ -202,7 +166,6 @@ onMounted(async () => {
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
>
<ToggleButton
:disable="hideAction"
class="q-mr-sm"
two-way
:model-value="flowData.status !== 'INACTIVE'"
@ -492,41 +455,40 @@ onMounted(async () => {
:key="i"
class="surface-2 bordered rounded q-px-xs"
>
{{
optionStore.mapOption(
att.fieldName ?? '',
'propertiesField',
)
}}
{{ optionStore.mapOption(att.fieldName ?? '') }}
</span>
</section>
</div>
</div>
<!-- RESPONSIBLE-PERSON -->
<q-field
<q-select
v-if="step.responsiblePersonId"
behavior="menu"
:for="`select-responsible-person-${index}-${onDrawer ? 'drawer' : 'dialog'}`"
:bg-color="readonly ? 'transparent' : ''"
:readonly
outlined
:stack-label="
userInTable[index]?.responsiblePerson.length > 0 ||
userInTable[index]?.responsibleGroup.length > 0
"
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')"
dense
class="col-md-6 col-12"
:class="{ 'cursor-pointer': !readonly }"
:hide-dropdown-icon="readonly"
>
<template #control>
<q-item
dense
class="items-center full-width no-padding"
<template v-slot:selected-item="scope">
<div class="column full-width">
<div
class="row items-center no-wrap"
v-for="person in userInTable[
index
]?.responsiblePerson.filter((p) =>
step.responsiblePersonId.includes(p.id),
]?.responsiblePerson.filter(
(p) => p.id === scope.opt,
)"
:key="person.id"
>
@ -580,52 +542,12 @@ onMounted(async () => {
{{ person.code }}
</span>
</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>
<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>
</div>
</template>
<template v-if="!readonly" #append>
<q-icon
name="mdi-menu-down"
:class="{ rotated: responsibleMenu }"
class="transition-rotate"
/>
</template>
<q-menu
v-if="!readonly"
no-focus
no-refocus
:offset="[0, 4]"
@before-show="() => (responsibleMenu = true)"
@before-hide="() => (responsibleMenu = false)"
>
<template v-slot:option></template>
<q-menu v-if="!readonly" :offset="[0, 4]">
<q-list>
<q-item>
<q-input
@ -654,7 +576,6 @@ onMounted(async () => {
{{ $t('general.noData') }}
</q-item>
<q-item
v-else
v-for="(person, i) in userList"
dense
:key="i"
@ -729,7 +650,6 @@ onMounted(async () => {
{{ $t('personnel.MESSENGER') }}
</span>
<q-item
dense
clickable
@click="step.messengerByArea = !step.messengerByArea"
class="column"
@ -745,49 +665,9 @@ onMounted(async () => {
</div>
</div>
</q-item>
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.group') }}
</span>
<q-item
v-if="groupList.length === 0"
class="app-text-muted q-px-lg"
>
{{ $t('general.noData') }}
</q-item>
<q-item
v-else
v-for="(group, i) in groupList"
dense
clickable
@click="selectResponsibleGroup(index, group.name)"
class="column"
>
<div class="row items-center">
<q-checkbox
size="xs"
:model-value="
step.responsibleGroup.includes(group.name)
"
@click.stop="
selectResponsibleGroup(index, group.name)
"
/>
<q-avatar class="q-ml-sm" size="md">
<q-img
class="text-center"
:ratio="1"
:src="`/img-group.png`"
/>
</q-avatar>
<div class="column q-pl-md">
<span>{{ group.name }}</span>
</div>
</div>
</q-item>
</q-list>
</q-menu>
</q-field>
</q-select>
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
<q-select
@ -930,11 +810,4 @@ onMounted(async () => {
:deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) {
visibility: hidden;
}
.transition-rotate {
transition: transform 0.3s ease;
}
.rotated {
transform: rotate(180deg);
}
</style>

View file

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

View file

@ -98,8 +98,7 @@ watch(
(c, o) => {
const list = c.map((v: { name: string }) => v.name);
const oldList = o.map((v: { name: string }) => v.name);
const index = workName.value ? oldList.indexOf(workName.value) : -1;
if (index === -1) return;
const index = oldList.indexOf(workName.value || '');
if (
list[index] !== oldList[index] &&
@ -585,12 +584,7 @@ watch(
:key="i"
class="surface-2 bordered rounded q-px-xs"
>
{{
optionStore.mapOption(
att.fieldName ?? '',
'propertiesField',
)
}}
{{ optionStore.mapOption(att.fieldName ?? '') }}
</span>
</div>
<div v-else class="app-text-muted-2">

View file

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

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,6 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SelectInput from '../shared/SelectInput.vue';
import useOptionStore from 'src/stores/options';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useInstitution } from 'src/stores/institution';
const optionStore = useOptionStore();
@ -14,31 +10,12 @@ defineProps<{
readonly?: boolean;
onDrawer?: boolean;
}>();
const emit = defineEmits<{
(e: 'deleteAttachment', name: string): void;
}>();
const attachmentRef = ref();
const group = defineModel('group', { default: '' });
const name = defineModel('name', { default: '' });
const nameEn = defineModel('nameEn', { default: '' });
const contactName = defineModel('contactName', { default: '' });
const email = defineModel('email', { default: '' });
const contactTel = defineModel('contactTel', { default: '' });
const attachment = defineModel<File[]>('attachment');
const attachmentList =
defineModel<{ name: string; url: string }[]>('attachmentList');
type Options = { label: string; value: string };
function openNewTab(url: string) {
window.open(url, '_blank');
}
function deleteAttachment(name: string) {
emit('deleteAttachment', name);
}
</script>
<template>
<div class="row col-12">
@ -58,7 +35,7 @@ function deleteAttachment(name: string) {
<div class="col-12 row q-col-gutter-sm">
<SelectInput
class="col"
:class="{ col: $q.screen.lt.md }"
:disable="!readonly && onDrawer"
:readonly="readonly"
for="input-agencies-code"
@ -85,15 +62,10 @@ function deleteAttachment(name: string) {
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
class="col-md col-12"
:label="$t('agencies.name')"
v-model="name"
:rules="[
(val) => !!val || $t('form.error.required'),
(val) =>
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
$t('form.error.branchNameField'),
]"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input
for="input-agencies-name-en"
@ -101,159 +73,10 @@ function deleteAttachment(name: string) {
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
class="col-md col-12"
:label="'Agencies Name'"
v-model="nameEn"
:rules="
nameEn
? [
(val) =>
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
$t('form.error.branchNameENField'),
]
: []
"
/>
<q-input
for="input-agencies-contact-name"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
:label="$t('agencies.contactName')"
:model-value="readonly ? contactName || '-' : contactName"
@update:model-value="
(v) => (typeof v === 'string' ? (contactName = v) : '')
"
/>
<q-input
for="input-agencies-email"
dense
outlined
hide-bottom-space
:readonly="readonly"
:label="$t('form.email')"
:rules="
readonly
? undefined
: [
(v: string) =>
!v ||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
$t('form.error.invalid'),
]
"
class="col-md-4 col-12"
:model-value="readonly ? email || '-' : email"
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
@clear="email = ''"
>
<template #prepend>
<q-icon
size="xs"
name="mdi-email-outline"
class="cursor-pointer"
color="primary"
/>
</template>
</q-input>
<q-input
for="input-agencies-contact-tel"
id="input-agencies-contact-tel"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
:label="$t('agencies.contactTel')"
:model-value="readonly ? contactTel || '-' : contactTel"
@update:model-value="
(v) => (typeof v === 'string' ? (contactTel = v) : '')
"
>
<template #prepend>
<q-icon
size="xs"
name="mdi-phone-outline"
class="cursor-pointer"
color="primary"
/>
</template>
</q-input>
<q-file
v-if="!readonly"
ref="attachmentRef"
for="input-attachment"
dense
outlined
multiple
append
:readonly
:label="$t('personnel.form.attachment')"
class="col"
v-model="attachment"
>
<template v-slot:prepend>
<Icon
icon="material-symbols:attach-file"
width="20px"
style="color: var(--brand-1)"
/>
</template>
<template v-slot:file="file">
<div class="row full-width items-center">
<span class="col ellipsis">
{{ file.file.name }}
</span>
<q-btn
dense
rounded
flat
padding="2 2"
class="app-text-muted"
icon="mdi-close-circle"
@click.stop="attachmentRef.removeAtIndex(file.index)"
/>
</div>
</template>
</q-file>
<div v-if="attachmentList && attachmentList?.length > 0" class="col-12">
<q-list bordered separator class="rounded" style="padding: 0">
<q-item
id="attachment-file"
for="attachment-file"
v-for="item in attachmentList"
clickable
:key="item.url"
class="items-center row"
@click="() => openNewTab(item.url)"
>
<q-item-section>
<div class="row items-center justify-between">
<div class="col">
{{ item.name }}
</div>
<q-btn
v-if="!readonly"
id="delete-file"
rounded
flat
dense
unelevated
size="md"
icon="mdi-trash-can-outline"
class="app-text-negative"
@click.stop="deleteAttachment(item.name)"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</div>
</div>
</div>
</template>

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue/dist/iconify.js';
import { formatNumberDecimal } from 'src/stores/utils';
defineEmits<{ (e: 'labelClick', value: string, index: number | null): void }>();
@ -27,38 +28,26 @@ withDefaults(
class="app-text-muted q-pr-sm"
:width="iconSize || '2rem'"
/>
<span :id="`dd-wrapper-${label}`" class="row col">
<span
:id="`dd-label-${label}`"
class="col-12 app-text-muted-2"
style="font-size: 10px"
>
<span class="row col">
<span class="col-12 app-text-muted-2" style="font-size: 10px">
{{ label }}
</span>
<span :id="`dd-value-wrapper-${label}`" class="col-12 ellipsis">
<span class="col-12 ellipsis">
<slot name="value">
<span
:class="{ 'link cursor-pointer': clickable }"
v-if="typeof value === 'string'"
@click="clickable ? $emit('labelClick', value, null) : undefined"
:id="`link-${value}`"
:for="`link-${value}`"
@click="$emit('labelClick', value, null)"
>
{{ value }}
{{ formatNumberDecimal(+value, 2) }}
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
</span>
<span
:id="`dd-value-${label}`"
v-else
:class="{ 'link cursor-pointer': clickable }"
>
<span v-else :class="{ 'link cursor-pointer': clickable }">
<span
v-for="(item, index) in value"
:key="index"
@click="$emit('labelClick', item, index)"
class="link cursor-pointer"
:id="`link-${item}`"
:for="`link-${item}`"
>
{{ item }}
<span v-if="index < value.length - 1">,&nbsp;</span>

View file

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

View file

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

View file

@ -42,15 +42,6 @@ defineProps<{
const modal = defineModel('modal', { default: false });
const currentTab = defineModel<string>('currentTab');
async function onValidationError(ref: any) {
const el = ref.$el as Element;
el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest',
});
}
</script>
<template>
<q-dialog
@ -69,7 +60,6 @@ async function onValidationError(ref: any) {
@submit.prevent
@validation-success="submit"
class="column full-height"
@validation-error="onValidationError"
>
<!-- header -->
<div

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,6 @@ defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
id?: string;
icon?: string;
color: string;
iconOnly?: boolean;
@ -19,14 +18,13 @@ defineProps<{
<template>
<button
:id="id"
@click="(e) => $emit('click', e)"
class="main-btn"
:class="{
'main-btn__solid': solid && !outlined,
'main-btn__outline': !solid && outlined,
'main-btn__pill': pill,
'main-btn__dark': dark || $q.dark.isActive,
'main-btn__dark': dark,
'main-btn__icon-only': iconOnly,
}"
:style="{ '--button-main-color': color }"

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;
disabled?: boolean;
dark?: boolean;
color?: string;
label?: string;
icon?: string;
@ -24,7 +23,7 @@ defineProps<{
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-content-save-outline'"
:color="color || '207 96% 32%'"
color="207 96% 32%"
:title="iconOnly ? $t('general.save') : undefined"
>
{{ 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 NextButton } from './NextButton.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 });
</script>
@ -50,7 +41,6 @@ const state = defineModel({ default: false });
}"
>
<q-form
@validation-error="onValidationError"
@submit.prevent="(e) => $emit('submit', e)"
@reset="$emit('reset')"
greedy

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -75,9 +75,9 @@ function setDefaultValue() {
</script>
<template>
<SelectInput
for="select-hq-id"
v-model="value"
incremental
id="select-hq-id"
:label
:placeholder
: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 = {
simple?: boolean;
simpleBranchNo?: boolean;
selectFirstValue?: boolean;
};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -65,14 +64,8 @@ onMounted(async () => {
setFirstValue();
}
if (props.selectFirstValue) {
setDefaultValue();
} else await getSelectedOption();
await getSelectedOption();
});
function setDefaultValue() {
setFirstValue();
}
</script>
<template>
<SelectInput
@ -165,9 +158,11 @@ function setDefaultValue() {
</template>
<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 />
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #append v-if="clearable">
@ -180,11 +175,3 @@ function setDefaultValue() {
</template>
</SelectInput>
</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({
query: query === '' ? undefined : query,
...props.params,
activeOnly: true,
});
if (ret) return ret.result;
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ export default {
save: 'บันทึก',
open: 'เปิด',
close: 'ปิด',
edit: 'แก้ไข{text}',
edit: 'แก้ไข',
cancel: 'ยกเลิก',
back: 'ย้อนกลับ',
undo: 'ย้อนกลับ',
@ -31,7 +31,6 @@ export default {
displayField: 'ฟิลด์แสดงผล',
order: 'ลำดับ',
name: 'ชื่อ{msg}',
nameEN: 'ชื่อ (ภาษาอังกฤษ)',
fullName: 'ชื่อ-สกุล',
detail: 'รายละเอียด{msg}',
remark: 'หมายเหตุ{msg}',
@ -151,17 +150,6 @@ export default {
notIncluded: 'ไม่รวม',
dueDate: 'วันครบกำหนด',
year: 'ปี',
tableOfContent: 'สารบัญ',
draw: 'วาด',
newUpload: 'อัปโหลดใหม่',
nativeLanguage: '{msg} ภาษาต้นทาง',
copy: 'คัดลอก',
paste: 'วาง',
period: 'ช่วงเวลา',
documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
},
menu: {
@ -204,14 +192,11 @@ export default {
title: 'จัดการ',
branch: 'สาขา',
personnel: 'บุคลากร',
group: 'กลุ่ม',
productService: 'สินค้าและบริการ',
workflow: 'ขั้นตอนการทำงาน',
property: 'คุณสมบัติ',
customer: 'ลูกค้า',
mainData: 'ข้อมูลหลัก',
agencies: 'หน่วยงาน',
businessType: 'ประเภทกิจการ',
},
sales: {
@ -255,12 +240,6 @@ export default {
mode: 'โหมด',
addSignature: 'เพิ่มลายเซ็น',
},
manual: {
title: 'คู่มือ',
usage: 'การใช้งาน',
troubleshooting: 'การแก้ปัญหา',
},
},
noti: {
@ -338,7 +317,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -467,13 +446,6 @@ export default {
citizenId: 'เลขที่บัตรประชาชน',
citizenIssue: 'วันที่ออกบัตร',
citizenExpire: 'วันที่หมดอายุ',
agencyStatus: 'สถานะเอเจนซี่',
normal: 'ปกติ',
canceled: 'ยกเลิก',
blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
},
},
customer: {
@ -500,16 +472,15 @@ export default {
},
prefix: {
mr: 'นาย',
mrs: 'นาง',
miss: 'นางสาว',
mr: 'Mr.',
mrs: 'Mrs.',
miss: 'Miss.',
},
citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ',
@ -593,7 +564,7 @@ export default {
family: 'ข้อมูลครอบครัว',
},
workerStatus: 'สถานะคนงาน',
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า',
previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
employerBranch: 'สาขานายจ้าง',
employeeCode: 'รหัสลูกจ้าง',
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
@ -626,7 +597,7 @@ export default {
placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก',
entryCount: 'จำนวนวันที่เข้าประเทศ',
entryCount: 'จำนวนที่เข้าประเทศ',
employerSelect: {
branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง',
@ -772,13 +743,10 @@ export default {
},
quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม',
@ -926,10 +894,6 @@ export default {
code: 'รหัสหน่วยงาน',
group: 'กลุ่มหน่วยงาน',
name: 'ชื่อหน่วยงาน',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
},
requestList: {
@ -951,7 +915,6 @@ export default {
nonLocalEmployee: 'พนักงานนอกพื้นที่',
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
salesRepresentative: 'พนักงานขาย',
dataOffice: 'สำนักงานเขตจัดหางาน',
ref: 'อ้างอิง',
action: {
title: 'จัดการ',
@ -967,7 +930,6 @@ export default {
Ended: 'จบงาน',
Completed: 'เสร็จสิ้น',
Canceled: 'ยกเลิก',
RejectCancel: 'ปฏิเสธคําขอยกเลิก',
},
Pending: 'รอดำเนินการ',
Ready: 'พร้อมดำเนินการ',
@ -975,8 +937,6 @@ export default {
Completed: 'เสร็จสิ้น',
Canceled: 'ยกเลิก',
CancelRequested: 'ต้องการยกเลิก',
RejectCancel: 'การปฏิเสธคําขอยกเลิก',
RejectedCancel: 'ปฏิเสธคําขอยกเลิกแล้ว',
AwaitOrder: 'รอสั่งงาน',
ReadyOrder: 'พร้อมสั่งงาน',
@ -1070,9 +1030,6 @@ export default {
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
},
message: {
copy: 'คัดลอก',
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
beingUse: '"{msg}" มีการใช้งานอยู่',
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
@ -1194,14 +1151,13 @@ export default {
'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น',
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
samePropertyNameExists: 'คุณสมบัตินี้มีอยู่ในระบบอยู่แล้ว',
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
codeMisMatch: 'รหัสไม่ตรงกัน',
crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้',
errorOccurred:
errorOccure:
'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง',
invalidData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
invalideData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง',
installmentsValidateFailed:
'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ',
@ -1220,8 +1176,6 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
},
},
@ -1386,7 +1340,6 @@ export default {
Product: 'รายงานสินค้าและบริการ',
Sale: 'รายงานสรุปยอดขาย',
Profit: 'รายงานกำไรและขาดทุน',
Debt: 'รายงานลูกค้าที่มีหนี้ค้างชำระ',
},
document: {
code: 'รหัส',
@ -1423,12 +1376,6 @@ export default {
expenses: 'ต้นทุน',
income: 'รายได้',
},
debtReport: {
title: 'รายงานลูกค้าที่มีหนี้ค้างชำระ ',
customerName: 'ชื่อลูกค้า',
unpaid: 'ยังไม่ได้ชำระ',
paid: 'ชำระแล้ว',
},
},
dashboard: {
title: 'Dashboard',
@ -1477,42 +1424,4 @@ export default {
11: 'พฤศจิกายน',
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 { getUserId, getRole } from 'src/services/keycloak';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { canAccess } from 'src/stores/utils';
type Menu = {
label: string;
@ -16,13 +14,11 @@ type Menu = {
hidden?: boolean;
disabled?: boolean;
isax?: boolean;
noI18n?: boolean;
children?: Menu[];
};
const router = useRouter();
const $q = useQuasar();
const { locale } = useI18n();
const userBranch = useMyBranch();
const { currentMyBranch } = storeToRefs(userBranch);
@ -62,7 +58,39 @@ function branchSetting() {
//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 = [
{
label: 'menu.manage',
@ -71,45 +99,30 @@ function initMenu() {
{
label: 'branch',
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',
route: '/personnel-management',
hidden: !canAccess('personnel'),
},
{
label: 'group',
route: '/group-management',
hidden: !canAccess('personnel'),
},
{
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',
hidden: !(
role.value.includes('admin') ||
role.value.includes('head_of_admin') ||
role.value.includes('system') ||
role.value.includes('owner') ||
role.value.includes('branch_manager')
),
},
{ 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',
children: [
{ label: 'report', route: '/report' },
{
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',
},
{ label: 'dashboard', route: '/dash-board' },
],
},
];
}
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);
@ -258,20 +209,14 @@ onMounted(async () => {
>
<q-img
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="height: 64px"
/>
</header>
<div id="drawer-menu" class="q-pl-md q-mr-xs q-gutter-y-sm">
<template
v-for="(menu, i) in menuData.filter(
(v) =>
!(v.children?.length === 0 || v.children?.every((i) => i.hidden)),
)"
:key="i"
>
<template v-for="(menu, i) in menuData" :key="i">
<q-expansion-item
v-if="!menu.hidden"
:id="menu.label"
@ -312,10 +257,10 @@ onMounted(async () => {
style="white-space: nowrap"
:style="!menuActive[i] && `color: var(--foreground)`"
>
{{ menu.noI18n ? menu.label : $t(`${menu.label}.title`) }}
{{ $t(`${menu.label}.title`) }}
</span>
<q-tooltip :delay="500">
{{ menu.noI18n ? menu.label : $t(`${menu.label}.title`) }}
{{ $t(`${menu.label}.title`) }}
</q-tooltip>
<q-menu
@ -344,11 +289,7 @@ onMounted(async () => {
:id="`sub-menu-${sub.label}`"
>
<span style="white-space: nowrap">
{{
sub.noI18n
? sub.label
: $t(`${menu.label}.${sub.label}`)
}}
{{ $t(`${menu.label}.${sub.label}`) }}
</span>
</q-item>
</template>
@ -370,16 +311,12 @@ onMounted(async () => {
<nav
class="row items-center no-wrap"
:class="{
active: sub.route && currentPath.includes(sub.route),
active: currentPath === sub.route,
disabled: sub.disabled,
}"
>
<span class="q-px-md" style="white-space: nowrap">
{{
sub.noI18n
? sub.label
: $t(`${menu.label}.${sub.label}`)
}}
{{ $t(`${menu.label}.${sub.label}`) }}
</span>
</nav>
</div>
@ -465,9 +402,8 @@ onMounted(async () => {
</span>
</div>
<!-- v-if="!mini" -->
<q-btn
v-if="false"
v-if="!mini"
dense
flat
rounded

View file

@ -2,7 +2,7 @@
import { ref, onMounted, computed, reactive } from 'vue';
import { storeToRefs } from 'pinia';
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 { useI18n } from 'vue-i18n';
import moment from 'moment';
@ -39,7 +39,7 @@ const configStore = useConfigStore();
const { data: notificationData } = storeToRefs(notificationStore);
const { visible } = storeToRefs(loaderStore);
const { t, locale } = useI18n({ useScope: 'global' });
const { t } = useI18n({ useScope: 'global' });
const userStore = useUserStore();
const canvasModal = ref(false);
@ -50,16 +50,11 @@ const leftDrawerMini = ref(false);
const unread = computed<number>(
() => notificationData.value.filter((v) => !v.read).length || 0,
);
// const filterRole = ref<string[]>();
const userImage = ref<string>();
const userGender = ref('');
const userName = ref({ th: '', en: '' });
const canvasRef = ref();
const displayName = computed(() => {
if (!userName.value.th && !userName.value.en) return getName() || 'Guest';
return locale.value === 'eng' ? userName.value.en : userName.value.th;
});
const language: {
value: Lang;
label: string;
@ -120,24 +115,8 @@ function doLogout() {
}
function readNoti(id: string) {
const notification = notificationData.value.find((n) => n.id === id);
state.notiDialog = true;
state.notiId = id;
if (notification) {
notification.read = true;
}
}
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 () => {
@ -167,15 +146,10 @@ onMounted(async () => {
if (user === 'admin') return;
if (uid) {
const res = await userStore.fetchById(uid);
if (res) {
if (res.gender) {
if (res && res.gender) {
userGender.value = res.gender;
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
}
//
userName.value.th = `${res.firstName || ''} ${res.lastName || ''}`.trim();
userName.value.en = `${res.firstNameEN || ''} ${res.lastNameEN || ''}`.trim();
}
}
});
</script>
@ -333,17 +307,17 @@ onMounted(async () => {
}
"
>
<div class="q-pa-sm row col-12 items-center">
<div class="q-px-md q-py-sm row col-12 items-center">
<div class="text-subtitle1 text-weight-bold">
{{ $t('noti.title') }}
</div>
<q-space />
</div>
<div class="q-px-sm q-pb-md">
<div class="q-px-md q-pb-md q-gutter-x-md">
<q-btn
rounded
padding="0px 10px"
class="text-weight-medium text-capitalize"
class="text-weight-medium"
v-for="(btn, index) in notiMenu"
:flat="!btn.active"
:unelevated="btn.active"
@ -353,14 +327,14 @@ onMounted(async () => {
@click="setActive(btn)"
/>
</div>
<div style="max-height: 30vh; width: 400px; overflow-y: auto">
<section
v-if="
state.filterUnread
? notificationData.filter((v) => !v.read).length
: notificationData.length
"
class="caption cursor-pointer scroll"
style="max-height: 30vh; width: 300px"
class="caption cursor-pointer"
>
<q-item
v-for="(item, i) in state.filterUnread
@ -368,74 +342,53 @@ onMounted(async () => {
: notificationData"
dense
clickable
class="items-center q-py-xs q-px-sm"
class="q-py-sm"
v-ripple
@click="readNoti(item.id)"
:key="i"
>
<div
class="rounded q-mr-sm"
:style="`background: hsl(var(--info-bg)/${item.read ? 0 : 1}); width: 6px; height: 6px`"
/>
<q-avatar
v-if="$q.screen.gt.xs"
color="positive"
style="height: 36px; width: 36px"
>
<q-icon color="white" name="mdi-check" />
</q-avatar>
<div class="col column text-caption q-pl-md ellipsis">
<span class="block ellipsis full-width text-weight-bold">
{{ item.title }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.title }}
</q-tooltip>
</span>
<div class="col-6 column text-caption q-pl-md ellipsis">
<span
class="block ellipsis full-width text-stone"
:class="{ 'text-weight-medium': !item.read }"
class="block ellipsis full-width text-weight-bold"
>
{{ item.title }}
</span>
<span class="block ellipsis full-width text-stone">
{{ item.detail }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.detail }}
</q-tooltip>
</span>
</div>
<span
align="right"
class="text-caption text-stone q-pl-md"
:class="{ 'text-weight-bold': !item.read }"
>
<span align="right" class="col text-caption text-stone">
{{ moment(item.createdAt).fromNow() }}
</span>
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="1000"
:offset="[10, 10]"
>
{{ item.detail }}
</q-tooltip>
</q-item>
</section>
<section
v-else
class="text-center q-py-sm"
style="max-height: 30vh; width: 300px"
>
<section v-else class="text-center q-py-sm">
<span class="app-text-muted">
{{ $t('general.noData') }}
</span>
</section>
</div>
<div class="col bordered-t">
<q-btn
flat
dense
color="info"
class="full-width text-capitalize"
class="full-width"
@click="() => $router.push('/notification')"
>
{{ $t('noti.viewAll') }}
@ -495,7 +448,6 @@ onMounted(async () => {
<!-- User -->
<ProfileMenu
id="btn-profile-menu"
:user-name="displayName"
@logout="doLogout"
@edit-personal-info="console.log('edit')"
@signature="
@ -524,15 +476,13 @@ onMounted(async () => {
no-app-box
:title="$t('menu.profile.addSignature')"
:close="() => (canvasModal = false)"
:submit="signatureSubmit"
:show="signatureFetch"
>
<CanvasComponent ref="canvasRef" v-model:modal="canvasModal" />
<template #footer>
<q-btn
flat
dense
:label="$t('general.clear')"
:label="$t('clear')"
@click="
() => {
canvasRef.clearCanvas(), canvasRef.clearUpload();

View file

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

@ -75,13 +75,8 @@ async function deleteNoti() {
}
function readNoti(id: string) {
const notification = noti.value.find((n) => n.id === id);
state.notiId = id;
state.notiDialog = true;
if (notification) {
notification.read = true;
}
}
onMounted(async () => {
@ -94,22 +89,21 @@ onMounted(async () => {
<template>
<main class="column full-height no-wrap">
<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
size="xs"
:model-value="noti.length > 0 && selectedNoti.length === noti.length"
:disable="noti.length === 0"
:model-value="selectedNoti.length === noti.length"
class="q-px-sm"
@click="toggleSelection('', true)"
/>
<q-separator vertical inset spaced="md" />
<q-btn
v-if="selectedNoti.length === 0"
icon="mdi-refresh"
rounded
flat
dense
size="sm"
class="app-text-muted-2 q-mt-xs"
size="xs"
class="app-text-muted-2 q-ml-sm q-mt-xs"
@click="async () => await fetchNoti()"
>
<q-tooltip>Refresh</q-tooltip>
@ -120,8 +114,8 @@ onMounted(async () => {
rounded
flat
dense
size="sm"
class="app-text-muted-2"
size="xs"
class="app-text-muted-2 q-ml-sm"
@click="async () => await deleteNoti()"
>
<q-tooltip>{{ $t('general.delete') }}</q-tooltip>
@ -132,7 +126,7 @@ onMounted(async () => {
rounded
flat
dense
size="sm"
size="xs"
class="app-text-muted-2 q-mx-sm"
@click="async () => await markAsRead()"
>
@ -178,13 +172,7 @@ onMounted(async () => {
<q-badge
rounded
class="q-ml-md"
:color="
(tab.value === 'all'
? noti.length
: noti.filter((v) => !v.read).length) > 0
? 'info'
: 'grey'
"
style="background: hsl(var(--info-bg))"
>
{{
tab.value === 'all'
@ -215,10 +203,10 @@ onMounted(async () => {
class="q-py-sm q-px-md rounded row no-wrap items-center"
@click.stop="readNoti(n.id)"
>
<div
<!-- <div
class="rounded"
:style="`background: hsl(var(--info-bg)/${n.read ? 0 : 1}); width: 6px; height: 6px`"
/>
/> -->
<q-checkbox
:model-value="selectedNoti.includes(n.id)"
size="xs"

View file

@ -6,11 +6,11 @@ import { Icon } from '@iconify/vue';
import { BranchContact } from 'stores/branch-contact/types';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import type { QTableProps, QTableSlots } from 'quasar';
import type { QSelect, QTableProps, QTableSlots } from 'quasar';
import { resetScrollBar } from 'src/stores/utils';
import useBranchStore from 'stores/branch';
import useFlowStore from 'stores/flow';
import { isRoleInclude, canAccess } from 'stores/utils';
import { isRoleInclude } from 'stores/utils';
import {
BranchWithChildren,
BranchCreate,
@ -52,7 +52,6 @@ import {
UndoButton,
} from 'components/button';
import { useNavigator } from 'src/stores/navigator';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const $q = useQuasar();
const { t } = useI18n();
@ -73,6 +72,7 @@ const typeBranchItem = [
color: 'var(--blue-6-hsl)',
},
];
const refFilter = ref<InstanceType<typeof QSelect>>();
const holdDialog = ref(false);
const isSubCreate = ref(false);
const columns = [
@ -175,8 +175,6 @@ const qrCodeDialog = ref(false);
const qrCodeimageUrl = ref<string>('');
const formLastSubBranch = ref<number>(0);
const searchDate = ref<string[]>([]);
const branchStore = useBranchStore();
const flowStore = useFlowStore();
const { locale } = useI18n();
@ -717,20 +715,12 @@ async function fetchList(opts: {
tree?: boolean;
withHead?: boolean;
filter?: 'head' | 'sub';
startDate?: string;
endDate?: string;
}) {
await branchStore.fetchList(opts);
}
watch([inputSearch, searchDate], () => {
fetchList({
tree: true,
query: inputSearch.value,
withHead: true,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
});
watch(inputSearch, () => {
fetchList({ tree: true, query: inputSearch.value, withHead: true });
currentSubBranch.value = undefined;
});
@ -791,8 +781,6 @@ async function onSubmit(submitSelectedItem?: boolean) {
);
if (!res) return;
formType.value = 'view';
formData.value.codeHeadOffice = formData.value.code = res.code;
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
@ -867,9 +855,7 @@ async function onSubmit(submitSelectedItem?: boolean) {
actionText: t('dialog.action.ok'),
persistent: true,
title: t('form.warning.title'),
cancel: () => {
formType.value = 'create';
},
cancel: () => {},
action: async () => {
await createBranch();
},
@ -1050,7 +1036,7 @@ watch(currentHq, () => {
{{ $t('branch.allBranch') }}
</div>
<q-btn
v-if="isRoleInclude(['system'])"
v-if="isRoleInclude(['head_of_admin', 'admin', 'system'])"
round
flat
size="md"
@ -1074,7 +1060,6 @@ watch(currentHq, () => {
<div class="col full-width scroll">
<div class="q-pa-md">
<TreeComponent
:hide-create="!canAccess('branch', 'create')"
v-model:nodes="treeData"
v-model:expanded-tree="expandedTree"
node-key="id"
@ -1185,49 +1170,26 @@ watch(currentHq, () => {
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
<template v-slot:append>
<q-separator vertical inset class="q-mr-xs" />
<AdvanceSearch
v-model="searchDate"
:active="$q.screen.lt.md && statusFilter !== 'all'"
>
<div
v-if="$q.screen.lt.md"
class="q-mt-sm text-weight-medium"
>
{{ $t('general.status') }}
</div>
<q-select
v-if="$q.screen.lt.md"
v-model="statusFilter"
outlined
dense
autocomplete="off"
option-value="value"
option-label="label"
map-options
emit-value
:for="'field-select-status'"
:options="[
{ label: $t('general.all'), value: 'all' },
{
label: $t('status.ACTIVE'),
value: 'statusACTIVE',
},
{
label: $t('status.INACTIVE'),
value: 'statusINACTIVE',
},
]"
<template v-if="$q.screen.lt.md" v-slot:append>
<span class="row">
<q-separator vertical />
<q-btn
icon="mdi-filter-variant"
unelevated
class="q-ml-sm"
padding="4px"
size="sm"
rounded
@click="refFilter?.showPopup"
/>
</AdvanceSearch>
</span>
</template>
</q-input>
<div class="row col-md-6 justify-end">
<q-select
v-if="$q.screen.gt.sm"
v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter"
outlined
dense
@ -1562,18 +1524,8 @@ watch(currentHq, () => {
</q-td>
<q-td>
<KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status"
:idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
@ -1713,18 +1665,8 @@ watch(currentHq, () => {
>
<template v-slot:action>
<KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status"
:idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
@ -2272,14 +2214,6 @@ watch(currentHq, () => {
@click="drawerEdit()"
type="button"
/>
<template
v-if="
formType !== 'edit' && formTypeBranch === 'headOffice'
? isRoleInclude(['system'])
: canAccess('branch', 'create')
"
>
<DeleteButton
v-if="formType !== 'edit'"
id="btn-info-basic-delete"
@ -2287,7 +2221,6 @@ watch(currentHq, () => {
@click="triggerDelete(currentEdit.id)"
type="button"
/>
</template>
</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 useMyBranch from 'src/stores/my-branch';
import { calculateAge } from 'src/utils/datetime';
import { useQuasar, type QTableProps } from 'quasar';
import { dialog, baseUrl, setPrefixName } from 'stores/utils';
import { QSelect, useQuasar, type QTableProps } from 'quasar';
import { dialog, baseUrl } from 'stores/utils';
import { useNavigator } from 'src/stores/navigator';
import { isRoleInclude, resetScrollBar } from 'src/stores/utils';
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 FormInformation from 'components/02_personnel-management/FormInformation.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { locale, t } = useI18n();
const $q = useQuasar();
@ -74,6 +73,7 @@ const isImageEdit = ref(false);
const imageDialog = ref(false);
const infoDrawerEdit = ref(false);
const refreshImageState = ref(false);
const refFilter = ref<InstanceType<typeof QSelect>>();
const firstScroll = ref(false);
const inputSearch = ref('');
@ -93,14 +93,12 @@ const currentUser = ref<User>();
const userCode = ref<string>();
const statusToggle = ref(true);
const userFile = ref<File[]>([]);
const userFileList = ref<{ name: string; url: string }[]>([]);
const agencyFile = ref<File[]>([]);
const agencyFileList = ref<{ name: string; url: string }[]>([]);
const typeStats = ref<UserTypeStats>();
const userStats = ref<BranchUserStats[]>();
const searchDate = ref<[]>([]);
const urlProfile = ref<string>();
const profileFileImg = ref<File | null>(null);
const imageList = ref<{ selectedImage: string; list: string[] }>();
@ -126,7 +124,7 @@ const defaultFormData = {
streetEN: '',
street: '',
trainingPlace: null,
importNationality: [],
importNationality: null,
sourceNationality: null,
licenseExpireDate: null,
licenseIssueDate: null,
@ -153,18 +151,6 @@ const defaultFormData = {
citizenExpire: null,
citizenIssue: null,
citizenId: '',
contactName: '',
contactTel: '',
remark: '',
agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
};
const formData = ref<UserCreate>({
@ -186,7 +172,7 @@ const formData = ref<UserCreate>({
streetEN: '',
street: '',
trainingPlace: null,
importNationality: [],
importNationality: null,
sourceNationality: null,
licenseExpireDate: null,
licenseIssueDate: null,
@ -213,18 +199,6 @@ const formData = ref<UserCreate>({
citizenExpire: null,
citizenIssue: null,
citizenId: '',
contactName: '',
contactTel: '',
remark: '',
agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
});
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
@ -353,7 +327,7 @@ function onClose(excludeDialog?: boolean) {
urlProfile.value = '';
profileFileImg.value = null;
infoDrawerEdit.value = false;
userFile.value = [];
agencyFile.value = [];
isEdit.value = false;
statusToggle.value = true;
isImageEdit.value = false;
@ -362,8 +336,6 @@ function onClose(excludeDialog?: boolean) {
mapUserType(currentTab.value);
imageList.value = { selectedImage: '', list: [] };
onCreateImageList.value = { selectedImage: '', list: [] };
userFileList.value = [];
userFile.value = [];
flowStore.rotate();
}
@ -384,10 +356,12 @@ async function openDialog(
isEdit.value = true;
await assignFormData(id);
if (formData.value.userType === 'AGENCY') {
const result = await userStore.fetchAttachment(id);
if (result) {
userFileList.value = result;
agencyFileList.value = result;
}
}
}
if (userStore.userOption.hqOpts.length !== 0 && !id) {
@ -445,37 +419,15 @@ async function onSubmit(excludeDialog?: boolean) {
: '';
const formDataEdit = {
...formData.value,
checkpointEN: formData.value.checkpoint,
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE',
provinceId: formData.value.addressForeign
? null
: formData.value.provinceId,
districtId: formData.value.addressForeign
? null
: formData.value.districtId,
subDistrictId: formData.value.addressForeign
? null
: formData.value.subDistrictId,
provinceText: formData.value.addressForeign
? formData.value.provinceId
: null,
districtText: formData.value.addressForeign
? formData.value.districtId
: null,
subDistrictText: formData.value.addressForeign
? formData.value.subDistrictId
: null,
zipCodeText: formData.value.addressForeign
? formData.value.zipCode
: null,
} as const;
await userStore.editById(currentUser.value.id, formDataEdit);
if (userFile.value) {
if (currentUser.value.id && formDataEdit.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = {
file: userFile.value,
file: agencyFile.value,
};
if (payload?.file) {
@ -498,39 +450,16 @@ async function onSubmit(excludeDialog?: boolean) {
: hqId.value
? hqId.value
: '';
formData.value.checkpointEN = formData.value.checkpoint;
const result = await userStore.create(
{
...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,
},
formData.value,
onCreateImageList.value,
);
if (userFile.value && result) {
if (result && formData.value.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = {
file: userFile.value,
file: agencyFile.value,
};
if (payload?.file) {
@ -622,20 +551,12 @@ async function assignFormData(idEdit: string) {
currentUser.value = foundUser;
formData.value = {
branchId: foundUser.branch[0]?.id,
provinceId: foundUser.addressForeign
? foundUser.provinceText
: foundUser.provinceId,
districtId: foundUser.addressForeign
? foundUser.districtText
: foundUser.districtId,
subDistrictId: foundUser.addressForeign
? foundUser.subDistrictText
: foundUser.subDistrictId,
provinceId: foundUser.provinceId,
districtId: foundUser.districtId,
subDistrictId: foundUser.subDistrictId,
telephoneNo: foundUser.telephoneNo,
email: foundUser.email,
zipCode: foundUser.addressForeign
? foundUser.zipCodeText
: foundUser.zipCode,
zipCode: foundUser.zipCode,
gender: foundUser.gender,
addressEN: foundUser.addressEN,
address: foundUser.address,
@ -646,10 +567,7 @@ async function assignFormData(idEdit: string) {
street: foundUser.street,
streetEN: foundUser.streetEN,
trainingPlace: foundUser.trainingPlace,
importNationality:
typeof foundUser.importNationality === 'string'
? [foundUser.importNationality]
: foundUser.importNationality,
importNationality: foundUser.importNationality,
sourceNationality: foundUser.sourceNationality,
licenseNo: foundUser.licenseNo,
discountCondition: foundUser.discountCondition,
@ -669,8 +587,6 @@ async function assignFormData(idEdit: string) {
responsibleArea: foundUser.responsibleArea,
status: foundUser.status,
selectedImage: foundUser.selectedImage,
contactName: foundUser.contactName || '',
contactTel: foundUser.contactTel || '',
licenseExpireDate:
(foundUser.licenseExpireDate &&
new Date(foundUser.licenseExpireDate)) ||
@ -687,12 +603,6 @@ async function assignFormData(idEdit: string) {
(foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null,
citizenExpire:
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null,
remark: foundUser.remark || '',
agencyStatus: foundUser.agencyStatus || '',
addressForeign: foundUser.addressForeign || false,
provinceTextEN: foundUser.provinceTextEN,
districtTextEN: foundUser.districtTextEN,
subDistrictTextEN: foundUser.subDistrictTextEN,
};
formData.value.status === 'ACTIVE' || 'CREATED'
@ -751,8 +661,6 @@ async function fetchUserList(mobileFetch?: boolean) {
: statusFilter.value === 'statusACTIVE'
? 'ACTIVE'
: 'INACTIVE',
startDate: searchDate.value[0],
endDate: searchDate.value[1],
});
if (ret) {
@ -819,17 +727,7 @@ watch(
watch(
() => formData.value.userType,
async (type) => {
if (type !== 'AGENCY') {
formData.value.addressForeign = false;
formData.value.provinceId = null;
formData.value.districtId = null;
formData.value.subDistrictId = null;
formData.value.provinceTextEN = null;
formData.value.districtTextEN = null;
formData.value.subDistrictTextEN = null;
formData.value.zipCodeText = null;
}
async () => {
if (!infoDrawerEdit.value) return;
formData.value.registrationNo = null;
formData.value.startDate = null;
@ -837,11 +735,11 @@ watch(
formData.value.responsibleArea = null;
formData.value.discountCondition = null;
formData.value.sourceNationality = null;
formData.value.importNationality = [];
formData.value.importNationality = null;
formData.value.trainingPlace = null;
formData.value.checkpoint = 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 = [];
currentPage.value = 1;
@ -974,45 +872,26 @@ watch(
<template #prepend>
<q-icon name="mdi-magnify" />
</template>
<template v-slot:append>
<q-separator vertical inset class="q-mr-xs" />
<AdvanceSearch
v-model="searchDate"
:active="$q.screen.lt.md && statusFilter !== 'all'"
>
<div
v-if="$q.screen.lt.md"
class="q-mt-sm text-weight-medium"
>
{{ $t('general.status') }}
</div>
<q-select
v-if="$q.screen.lt.md"
v-model="statusFilter"
outlined
dense
option-value="value"
option-label="label"
map-options
emit-value
autocomplete="off"
:for="'field-select-status'"
:options="[
{ label: $t('general.all'), value: 'all' },
{ label: $t('general.active'), value: 'statusACTIVE' },
{
label: $t('general.inactive'),
value: 'statusINACTIVE',
},
]"
<template v-if="$q.screen.lt.md" v-slot:append>
<span class="row">
<q-separator vertical />
<q-btn
icon="mdi-filter-variant"
unelevated
class="q-ml-sm"
padding="4px"
size="sm"
rounded
@click="refFilter?.showPopup"
/>
</AdvanceSearch>
</span>
</template>
</q-input>
<div class="row col-md-5" style="white-space: nowrap">
<q-select
v-if="$q.screen.gt.sm"
v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter"
outlined
dense
@ -1362,7 +1241,7 @@ watch(
{{
locale === 'eng'
? `${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
anchor="bottom left"
@ -1372,7 +1251,7 @@ watch(
{{
locale === 'eng'
? `${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>
@ -1638,12 +1517,9 @@ watch(
hide-action
:is-edit="infoDrawerEdit"
:title="
(currentUser.namePrefix
? $t('customer.form.prefix.' + currentUser.namePrefix) + ' '
: '') +
(locale === 'eng'
locale === 'eng'
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
: `${currentUser.firstName || currentUser.firstNameEN} ${currentUser.lastName || currentUser.lastNameEN}`)
: `${currentUser.firstName} ${currentUser.lastName}`
"
v-model:drawerOpen="infoDrawer"
:submit="() => onSubmit()"
@ -1675,18 +1551,7 @@ watch(
v-model:toggle-status="formData.status"
hideFade
:toggle-title="$t('status.title')"
:title="
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName || formData.firstNameEN,
lastName: formData.lastName || formData.lastNameEN,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
:caption="userCode"
:img="
`${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat(
@ -1871,15 +1736,12 @@ watch(
v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
:title="'personnel.form.personalInformation'"
prefix-id="drawer-info-personnel"
dense
outlined
separator
:readonly="!infoDrawerEdit"
:agency="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
@ -1897,15 +1759,10 @@ watch(
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode"
v-model:address-foreign="formData.addressForeign"
v-model:province-text-en="formData.provinceTextEN"
v-model:district-text-en="formData.districtTextEN"
v-model:sub-district-text-en="formData.subDistrictTextEN"
:readonly="!infoDrawerEdit"
prefix-id="drawer-info-personnel"
:title="'personnel.form.addressInformation'"
dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
<FormByType
@ -1924,11 +1781,10 @@ watch(
v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint"
v-model:user-file="userFile"
v-model:user-file-list="userFileList"
v-model:checkpoint-en="formData.checkpointEN"
v-model:agency-file="agencyFile"
v-model:agency-file-list="agencyFileList"
v-model:user-id="currentUser.id"
v-model:remark="formData.remark"
v-model:agency-status="formData.agencyStatus"
/>
</div>
</div>
@ -1972,18 +1828,7 @@ watch(
}[formData.gender]
"
:toggleTitle="$t('status.title')"
:title="
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName,
lastName: formData.lastName,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
:fallbackImg="
{
male: '/no-img-man.png',
@ -2009,6 +1854,7 @@ watch(
<div
class="col"
id="personnel-form"
:class="{
'q-px-lg q-pb-lg': $q.screen.gt.sm,
'q-px-md q-pb-sm': !$q.screen.gt.sm,
@ -2052,7 +1898,7 @@ watch(
? [
{
name: $t('personnel.form.workInformation'),
anchor: 'dialog-form-work',
anchor: 'dialog-info-work',
},
]
: [],
@ -2068,7 +1914,6 @@ watch(
</div>
</div>
<div
id="personnel-form"
class="col-md-10 col-12 full-height scroll"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
@ -2094,7 +1939,6 @@ watch(
id="dialog-form-personal"
prefix-id="form-dialog-personnel"
dense
:agency="formData.userType === 'AGENCY'"
outlined
separator
:title="'personnel.form.personalInformation'"
@ -2113,8 +1957,6 @@ watch(
v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
class="q-mb-xl"
/>
<AddressForm
@ -2131,13 +1973,8 @@ watch(
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode"
v-model:address-foreign="formData.addressForeign"
v-model:province-text-en="formData.provinceTextEN"
v-model:district-text-en="formData.districtTextEN"
v-model:sub-district-text-en="formData.subDistrictTextEN"
prefix-id="drawer-info-personnel"
dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
<FormByType
@ -2155,10 +1992,8 @@ watch(
v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint"
v-model:agency-status="formData.agencyStatus"
v-model:remark="formData.remark"
v-model:user-file="userFile"
v-model:user-file-list="userFileList"
v-model:checkpoint-en="formData.checkpointEN"
v-model:agency-file="agencyFile"
/>
</div>
</div>

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