Compare commits

..

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

219 changed files with 24023 additions and 18555 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,24 +7,23 @@
"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",
"keycloak-js": "^25.0.4",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-highlightjs": "^4.2.0",
@ -32,44 +31,49 @@
"markdown-it-html5-media": "^0.7.1",
"markdown-it-image-figures": "^2.1.1",
"markdown-it-video": "^0.6.3",
"mime": "^4.0.6",
"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",
"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",
"vue3-apexcharts": "^1.7.0"
},
"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",
"@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/markdown-it": "^14.1.2",
"@types/markdown-it-highlightjs": "^3.3.4",
"@types/node": "^20.17.28",
"@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"
}

3660
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",
@ -1089,29 +1050,6 @@
},
"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 +1204,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 +1233,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": "ไทย",

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

@ -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'"
@ -504,29 +467,33 @@ onMounted(async () => {
</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 +547,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 +581,6 @@ onMounted(async () => {
{{ $t('general.noData') }}
</q-item>
<q-item
v-else
v-for="(person, i) in userList"
dense
:key="i"
@ -729,7 +655,6 @@ onMounted(async () => {
{{ $t('personnel.MESSENGER') }}
</span>
<q-item
dense
clickable
@click="step.messengerByArea = !step.messengerByArea"
class="column"
@ -745,49 +670,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 +815,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] &&

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

@ -48,7 +48,6 @@ defineProps<{
readonly?: boolean;
onDrawer?: boolean;
inputOnly?: boolean;
disableToggle?: boolean;
}>();
defineEmits<{
@ -77,7 +76,6 @@ defineEmits<{
<ToggleButton
class="q-mr-sm"
two-way
:disable="disableToggle"
:model-value="status !== 'INACTIVE'"
@click="
() => {

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

@ -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

@ -27,38 +27,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 }}
<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,7 +18,6 @@ defineProps<{
<template>
<button
:id="id"
@click="(e) => $emit('click', e)"
class="main-btn"
:class="{

View file

@ -1,32 +0,0 @@
<script lang="ts" setup>
import MainButton from './MainButton.vue';
defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
iconOnly?: boolean;
solid?: boolean;
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
label?: string;
icon?: string;
amount?: number;
}>();
</script>
<template>
<MainButton
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-file-replace'"
color="207 96% 32%"
:title="iconOnly ? $t('general.paste') : undefined"
>
{{ label || $t('general.paste') }}
{{ amount && amount > 0 ? `(${amount})` : '' }}
</MainButton>
</template>

View file

@ -10,7 +10,6 @@ defineProps<{
outlined?: boolean;
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

@ -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

@ -16,7 +16,6 @@ const props = withDefaults(
useUpload?: boolean;
useCancel?: boolean;
useRejectCancel?: boolean;
useCopy?: boolean;
disableCancel?: boolean;
disableDelete?: boolean;
}>(),
@ -32,7 +31,6 @@ defineEmits<{
(e: 'link'): void;
(e: 'upload'): void;
(e: 'delete'): void;
(e: 'copy'): void;
(e: 'cancel'): void;
(e: 'rejectCancel'): void;
(e: 'changeStatus'): void;
@ -174,27 +172,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

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',
@ -152,16 +151,6 @@ export default {
dueDate: 'Due date',
year: 'year',
tableOfContent: 'Table of Contents',
draw: 'Draw',
newUpload: 'New Upload',
nativeLanguage: '{msg} Native Language',
copy: 'Copy',
paste: 'Paste',
period: 'Period',
documentStatus: 'Document Status',
advanceSearch: 'Advance Search',
totalPeople: '{meg} people',
price: 'Price {price} Baht',
},
menu: {
@ -204,14 +193,12 @@ 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: {
@ -258,8 +245,7 @@ export default {
manual: {
title: 'Manual',
usage: 'Usage',
troubleshooting: 'Troubleshooting',
usage: 'การใช้งาน',
},
},
@ -341,7 +327,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 +374,7 @@ export default {
branchLabel: 'Branch',
branchHQLabel: 'Headoffice',
taxNo: 'Legal Person',
contactName: 'Contact Person',
contactName: 'Contact Name',
},
page: {
captionManage: 'Manage',
@ -409,8 +395,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 +446,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 +457,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 +470,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 +483,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 +508,7 @@ export default {
registerDate: 'Registered On',
registerCompanyName: 'Registered Name',
authorizedCapital: 'Authorized Capital',
workplace: 'Workplace',
workplaceEN: 'Workplace (EN)',
address: 'Address',
@ -533,6 +516,7 @@ export default {
branchCode: 'Branch Code',
customerCode: 'Employer Code',
legalPersonCode: 'Legal Entity Code',
codeAbbrev: 'Company Abbreviation',
codeNumber: 'Company Number',
registeredBranch: 'Registered Branch',
@ -570,7 +554,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 +608,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 +758,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 +792,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 +874,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 +910,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 +931,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',
@ -1020,7 +996,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 +1055,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 +1072,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 +1101,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 +1183,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 +1207,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',
},
},
@ -1506,26 +1476,4 @@ export default {
type: 'Type',
},
},
dateRange: {
today: 'Today',
yesterday: 'Yesterday',
thisWeek: 'This Week',
lastWeek: 'Last Week',
thisMonth: 'This Month',
lastMonth: 'Last Month',
thisYear: 'This Year',
lastYear: 'Last Year',
last7Days: 'Last 7 Days',
last30Days: 'Last 30 Days',
last90Days: 'Last 90 Days',
customDateRange: 'Custom Date Range',
},
businessType: {
title: 'Business Type',
caption: 'Manage Business Type',
name: 'Business Type Name',
nameEn: 'Business Type Name (English)',
},
};

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}',
@ -152,16 +151,6 @@ export default {
dueDate: 'วันครบกำหนด',
year: 'ปี',
tableOfContent: 'สารบัญ',
draw: 'วาด',
newUpload: 'อัปโหลดใหม่',
nativeLanguage: '{msg} ภาษาต้นทาง',
copy: 'คัดลอก',
paste: 'วาง',
period: 'ช่วงเวลา',
documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
},
menu: {
@ -204,14 +193,12 @@ export default {
title: 'จัดการ',
branch: 'สาขา',
personnel: 'บุคลากร',
group: 'กลุ่ม',
productService: 'สินค้าและบริการ',
workflow: 'ขั้นตอนการทำงาน',
property: 'คุณสมบัติ',
customer: 'ลูกค้า',
mainData: 'ข้อมูลหลัก',
agencies: 'หน่วยงาน',
businessType: 'ประเภทกิจการ',
},
sales: {
@ -259,7 +246,6 @@ export default {
manual: {
title: 'คู่มือ',
usage: 'การใช้งาน',
troubleshooting: 'การแก้ปัญหา',
},
},
@ -338,7 +324,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -467,13 +453,6 @@ export default {
citizenId: 'เลขที่บัตรประชาชน',
citizenIssue: 'วันที่ออกบัตร',
citizenExpire: 'วันที่หมดอายุ',
agencyStatus: 'สถานะเอเจนซี่',
normal: 'ปกติ',
canceled: 'ยกเลิก',
blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
},
},
customer: {
@ -500,16 +479,15 @@ export default {
},
prefix: {
mr: 'นาย',
mrs: 'นาง',
miss: 'นางสาว',
mr: 'Mr.',
mrs: 'Mrs.',
miss: 'Miss.',
},
citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ',
@ -593,7 +571,7 @@ export default {
family: 'ข้อมูลครอบครัว',
},
workerStatus: 'สถานะคนงาน',
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า',
previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
employerBranch: 'สาขานายจ้าง',
employeeCode: 'รหัสลูกจ้าง',
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
@ -626,7 +604,7 @@ export default {
placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก',
entryCount: 'จำนวนวันที่เข้าประเทศ',
entryCount: 'จำนวนที่เข้าประเทศ',
employerSelect: {
branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง',
@ -772,13 +750,10 @@ export default {
},
quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม',
@ -926,10 +901,6 @@ export default {
code: 'รหัสหน่วยงาน',
group: 'กลุ่มหน่วยงาน',
name: 'ชื่อหน่วยงาน',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
},
requestList: {
@ -951,7 +922,6 @@ export default {
nonLocalEmployee: 'พนักงานนอกพื้นที่',
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
salesRepresentative: 'พนักงานขาย',
dataOffice: 'สำนักงานเขตจัดหางาน',
ref: 'อ้างอิง',
action: {
title: 'จัดการ',
@ -1070,9 +1040,6 @@ export default {
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
},
message: {
copy: 'คัดลอก',
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
beingUse: '"{msg}" มีการใช้งานอยู่',
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
@ -1194,14 +1161,13 @@ export default {
'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น',
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
samePropertyNameExists: 'คุณสมบัตินี้มีอยู่ในระบบอยู่แล้ว',
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
codeMisMatch: 'รหัสไม่ตรงกัน',
crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้',
errorOccurred:
errorOccure:
'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง',
invalidData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
invalideData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง',
installmentsValidateFailed:
'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ',
@ -1220,8 +1186,6 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
},
},
@ -1493,26 +1457,4 @@ export default {
type: 'ประเภท',
},
},
dateRange: {
today: 'วันนี้',
yesterday: 'เมื่อวานนี้',
thisWeek: 'สัปดาห์นี้',
lastWeek: 'สัปดาห์ที่แล้ว',
thisMonth: 'เดือนนี้',
lastMonth: 'เดือนที่แล้ว',
thisYear: 'ปีนี้',
lastYear: 'ปีที่แล้ว',
last7Days: '7 วันที่ผ่านมา',
last30Days: '30 วันที่ผ่านมา',
last90Days: '90 วันที่ผ่านมา',
customDateRange: 'กำหนดช่วงวันที่เอง',
},
businessType: {
title: 'ประเภทกิจการ',
caption: 'จัดการประเภทกิจการ',
name: 'ชื่อประเภทกิจการ',
nameEn: 'ชื่อประเภทกิจการ (ภาษาอังกฤษ)',
},
};

View file

@ -7,7 +7,6 @@ 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;
@ -71,45 +70,39 @@ 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'),
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: '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')
),
},
{ label: 'productService', route: '/product-service' },
{ label: 'customer', route: '/customer-management' },
{ label: 'agencies', route: '/agencies-management' },
],
},
{
@ -160,11 +153,7 @@ function initMenu() {
icon: 'mdi-monitor-dashboard',
children: [
{ label: 'report', route: '/report' },
{
label: 'dashboard',
route: '/dash-board',
hidden: !canAccess('dashBoard'),
},
{ label: 'dashboard', route: '/dash-board' },
],
},
@ -174,11 +163,7 @@ function initMenu() {
children: [
{
label: 'usage',
route: '/manual',
},
{
label: 'troubleshooting',
route: '/troubleshooting',
route: `/manual`,
},
],
},
@ -265,13 +250,7 @@ onMounted(async () => {
</header>
<div id="drawer-menu" class="q-pl-md q-mr-xs q-gutter-y-sm">
<template
v-for="(menu, i) in menuData.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"
@ -465,9 +444,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;
@ -129,17 +124,6 @@ function readNoti(id: string) {
}
}
function signatureSubmit() {
const signature = canvasRef.value.setCanvas();
userStore.setSignature(signature);
canvasModal.value = false;
}
async function signatureFetch() {
const ret = await userStore.getSignature();
if (ret) canvasRef.value.getCanvas(ret);
}
onMounted(async () => {
initTheme();
initLang();
@ -167,15 +151,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>
@ -388,28 +367,12 @@ onMounted(async () => {
<div class="col column text-caption q-pl-md ellipsis">
<span class="block ellipsis full-width text-weight-bold">
{{ item.title }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.title }}
</q-tooltip>
</span>
<span
class="block ellipsis full-width text-stone"
:class="{ 'text-weight-medium': !item.read }"
>
{{ item.detail }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.detail }}
</q-tooltip>
</span>
</div>
<span
@ -419,6 +382,15 @@ onMounted(async () => {
>
{{ moment(item.createdAt).fromNow() }}
</span>
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="1000"
:offset="[10, 10]"
>
{{ item.title }}
{{ item.detail }}
</q-tooltip>
</q-item>
</section>
<section
@ -495,7 +467,6 @@ onMounted(async () => {
<!-- User -->
<ProfileMenu
id="btn-profile-menu"
:user-name="displayName"
@logout="doLogout"
@edit-personal-info="console.log('edit')"
@signature="
@ -524,15 +495,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,7 +1,7 @@
<script setup lang="ts">
// NOTE: Library
import { storeToRefs } from 'pinia';
import { onMounted, watch } from 'vue';
import { onMounted } from 'vue';
// NOTE: Components
@ -10,44 +10,22 @@ import { onMounted, watch } from 'vue';
import { useManualStore } from 'src/stores/manual';
import { useNavigator } from 'src/stores/navigator';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useRoute, useRouter } from 'vue-router';
// NOTE: Variable
const route = useRoute();
const router = useRouter();
const manualStore = useManualStore();
const navigatorStore = useNavigator();
const { dataManual, dataTroubleshooting } = storeToRefs(manualStore);
const { dataManual } = storeToRefs(manualStore);
async function fetchManual() {
const res = await manualStore.getManual();
dataManual.value = res ? res : [];
}
onMounted(async () => {
navigatorStore.current.title = 'menu.manual.title';
navigatorStore.current.path = [{ text: '' }];
await fetchManual();
});
watch(
() => route.name,
async () => {
if (route.name === 'Manual') {
const res = await manualStore.getManual();
dataManual.value = res ? res : [];
}
if (route.name === 'Troubleshooting') {
const res = await manualStore.getTroubleshooting();
dataTroubleshooting.value = res ? res : [];
if (
res.length &&
res.length === 1 &&
res[0].page &&
res[0].page.length === 1
) {
router.replace(
`/troubleshooting/${res[0].category}/${res[0].page[0].name}`,
);
}
}
},
{ immediate: true },
);
</script>
<template>
@ -56,7 +34,7 @@ watch(
>
<section class="scroll q-gutter-y-sm">
<q-expansion-item
v-for="v in $route.name === 'Manual' ? dataManual : dataTroubleshooting"
v-for="v in dataManual"
:key="v.labelEN"
:content-inset-level="0.5"
class="rounded overflow-hidden bordered"
@ -80,11 +58,7 @@ watch(
clickable
dense
class="dot items-center rounded q-my-xs"
:to="
$route.name === 'Manual'
? `/manual/${v.category}/${x.name}`
: `/troubleshooting/${v.category}/${x.name}`
"
:to="`/manual/${v.category}/${x.name}`"
>
<Icon
v-if="!!x.icon"

View file

@ -5,7 +5,9 @@ import hljs from 'highlight.js';
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router';
// @ts-expect-error
import mditFigureWithPCaption from 'markdown-it-image-figures';
// @ts-expect-error
import mditMedia from 'markdown-it-html5-media';
import mditAnchor from 'markdown-it-anchor';
import mditHighlight from 'markdown-it-highlightjs';
@ -56,8 +58,6 @@ onUnmounted(() => {
async function getContent() {
if (!category.value || !page.value) return;
if (ROUTE.name === 'ManualView') {
const res = await manualStore.getManualByPage({
category: category.value,
pageName: page.value,
@ -68,18 +68,6 @@ async function getContent() {
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 = '';
@ -116,8 +104,8 @@ async function scrollTo(id: string) {
<template>
<main
class="full-height q-gutter-sm no-wrap"
:class="{ column: !toc && $q.screen.lt.md, 'row reverse': $q.screen.gt.sm }"
class="full-height q-gutter-sm"
:class="{ 'row reverse': $q.screen.gt.xs, column: $q.screen.xs }"
>
<section
v-if="toc"
@ -168,7 +156,7 @@ async function scrollTo(id: string) {
</q-list>
</section>
<section v-if="!toc && $q.screen.lt.md">
<section v-if="!toc && $q.screen.xs">
<q-btn
dense
class="full-width text-capitalize"
@ -181,7 +169,7 @@ async function scrollTo(id: string) {
</section>
<section
v-if="$q.screen.gt.xs || (!toc && $q.screen.xs)"
v-if="content || (!toc && $q.screen.xs)"
ref="wrapper"
class="markdown col scroll full-height rounded"
>
@ -198,9 +186,7 @@ async function scrollTo(id: string) {
md.render(
content.replaceAll(
'assets/',
$route.name === 'ManualView'
? `${baseUrl}/manual/${category}/assets/`
: `${baseUrl}/troubleshooting/${category}/assets/`,
`${baseUrl}/manual/${category}/assets/`,
),
)
"
@ -223,38 +209,6 @@ async function scrollTo(id: string) {
padding-block: 1rem !important;
}
.markdown {
counter-set: h1 0;
counter-reset: h1;
}
.markdown :deep(h1) {
counter-reset: h2;
}
.markdown :deep(h2) {
counter-reset: h3;
}
.markdown :deep(h3) {
counter-reset: h4;
}
.markdown :deep(h2:before) {
counter-increment: h2;
content: counter(h2) '. ';
}
.markdown :deep(h3:before) {
counter-increment: h3;
content: counter(h2) '.' counter(h3) ' ';
}
.markdown :deep(h4:before) {
counter-increment: h4;
content: counter(h2) '.' counter(h3) '.' counter(h4) ' ';
}
.markdown :deep(blockquote) {
background-color: var(--surface-2);
border-radius: 8px;
@ -328,31 +282,7 @@ async function scrollTo(id: string) {
padding: 0px 16px;
}
.markdown :deep(h4) {
text-align: left;
margin-block: 0;
font-size: 16px;
font-weight: 600;
padding: 0px 16px;
}
.markdown :deep(video) {
width: 100%;
}
.markdown :deep(table) {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5rem;
}
.markdown :deep(:where(table th)) {
background: var(--surface-2);
border: 1px solid var(--border-color);
}
.markdown :deep(:where(table td, table th)) {
border: 1px solid var(--border-color);
padding: 0.25rem 1rem;
}
</style>

View file

@ -94,22 +94,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 +119,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 +131,7 @@ onMounted(async () => {
rounded
flat
dense
size="sm"
size="xs"
class="app-text-muted-2 q-mx-sm"
@click="async () => await markAsRead()"
>
@ -178,13 +177,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'

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>

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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