Merge branch 'develop'
31
.github/workflows/local-build-dev.yaml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: local-build-dev
|
||||
|
||||
# Intended for local network use.
|
||||
# Remote access is possible if the host has a public IP address.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ${{ vars.DOCKER_REGISTRY }}
|
||||
|
||||
jobs:
|
||||
local-build-dev:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."${{ env.REGISTRY }}"]
|
||||
http = true
|
||||
insecure = true
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/jws/jws-frontend:dev
|
||||
allow: security.insecure
|
||||
31
.github/workflows/local-build-release.yaml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: local-build-release
|
||||
|
||||
# Intended for local network use.
|
||||
# Remote access is possible if the host has a public IP address.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ${{ vars.DOCKER_REGISTRY }}
|
||||
|
||||
jobs:
|
||||
local-build-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."${{ env.REGISTRY }}"]
|
||||
http = true
|
||||
insecure = true
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/jws/jws-frontend:latest
|
||||
allow: security.insecure
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:20-slim as build-stage
|
||||
FROM node:20-slim AS build-stage
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
|
@ -19,7 +19,7 @@ ENV VITE_API_BASE_URL_OCR=ENV_API_BASE_URL_OCR
|
|||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
RUN pnpm run build
|
||||
|
||||
FROM alpine as production-stage
|
||||
FROM alpine AS production-stage
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
|||
BIN
public/images/customer-CORP-avartar-female.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
public/images/customer-CORP-avartar-male.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 218 KiB |
BIN
public/images/customer-PERS-avartar-female.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
public/images/customer-PERS-avartar-male.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 37 KiB |
BIN
public/images/quotation-avatar.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/images/quotation-banner.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/images/quotation-bg-avatar.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -854,6 +854,21 @@
|
|||
"label": "Certificate of Identity",
|
||||
"value": "ci"
|
||||
}
|
||||
],
|
||||
|
||||
"expenseType": [
|
||||
{
|
||||
"label": "Service Fee",
|
||||
"value": "serviceFee"
|
||||
},
|
||||
{
|
||||
"label": "Fee",
|
||||
"value": "fee"
|
||||
},
|
||||
{
|
||||
"label": "Processing Fee",
|
||||
"value": "processingFee"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
|
@ -1712,6 +1727,21 @@
|
|||
"label": "เอกสารสำคัญประจำตัวเพื่อใช้แทนหนังสือเดินทาง",
|
||||
"value": "ci"
|
||||
}
|
||||
],
|
||||
|
||||
"expenseType": [
|
||||
{
|
||||
"label": "ค่าบริการ",
|
||||
"value": "serviceFee"
|
||||
},
|
||||
{
|
||||
"label": "ค่าธรรมเนียม",
|
||||
"value": "fee"
|
||||
},
|
||||
{
|
||||
"label": "ค่าดำเนินการ",
|
||||
"value": "processingFee"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
167
reports.json
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ 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/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
|
|
@ -32,6 +33,7 @@ function parseError(
|
|||
api.interceptors.request.use(async (config) => {
|
||||
useLoader().show();
|
||||
config.headers.Authorization = `Bearer ${await getToken()}`;
|
||||
config.headers['X-Rtid'] = useFlowStore().rtid;
|
||||
return config;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,25 +3,22 @@ import { baseUrl } from 'stores/utils';
|
|||
|
||||
defineProps<{
|
||||
inactive?: boolean;
|
||||
color?: 'none' | 'hq' | 'br';
|
||||
i18nKey?: string;
|
||||
color?: 'none' | 'hq' | 'br' | 'br-virtual';
|
||||
data: {
|
||||
branchLabelCode: string;
|
||||
branchLabelCode?: string;
|
||||
branchLabelName: string;
|
||||
branchLabelTel: string;
|
||||
branchLabelAddress: string;
|
||||
branchLabelType: string;
|
||||
branchImgUrl: string;
|
||||
taxNo?: string;
|
||||
branchLabelTel?: string;
|
||||
contactName?: string;
|
||||
branchLabelAddress?: string;
|
||||
branchImgUrl?: string;
|
||||
};
|
||||
|
||||
virtualBranch: boolean;
|
||||
metadata?: unknown;
|
||||
badgeField?: string[];
|
||||
fieldSelected?: (
|
||||
| 'orderNumber'
|
||||
| 'branchLabelName'
|
||||
| 'branchLabelAddress'
|
||||
| 'branchLabelTel'
|
||||
| 'branchLabelType'
|
||||
)[];
|
||||
fieldSelected?: string[];
|
||||
|
||||
footer?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
|
@ -36,12 +33,15 @@ defineProps<{
|
|||
color !== 'hq' && color !== 'br' && (!color || color === 'none'),
|
||||
'branch-card__hq': color === 'hq',
|
||||
'branch-card__br': color === 'br',
|
||||
'branch-card__br-virtual': color === 'br-virtual',
|
||||
}"
|
||||
@click="$emit('open')"
|
||||
>
|
||||
<div class="branch-card__header">
|
||||
<div class="branch-card__wrapper">
|
||||
<slot name="image"></slot>
|
||||
<q-img
|
||||
v-if="!$slots.image"
|
||||
:src="baseUrl + data.branchImgUrl"
|
||||
style="
|
||||
height: 3rem;
|
||||
|
|
@ -58,13 +58,32 @@ defineProps<{
|
|||
</template>
|
||||
</q-img>
|
||||
</div>
|
||||
<div class="branch-card__name">
|
||||
<div class="branch-card__name flex justify-center q-ml-sm">
|
||||
<b>{{ data.branchLabelName }}</b>
|
||||
<small class="branch-card__code">{{ data.branchLabelCode }}</small>
|
||||
<small class="branch-card__code" v-if="data.branchLabelCode">
|
||||
{{ data.branchLabelCode }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="branch-card__action">
|
||||
<slot name="action" />
|
||||
<div class="text-right">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
<div
|
||||
v-if="color === 'br'"
|
||||
class="q-pa-xs rounded"
|
||||
:style="`background: hsl(var(${virtualBranch ? '--blue-6-hsl' : '--purple-6-hsl'}) / 0.1)`"
|
||||
>
|
||||
<b
|
||||
:style="`color: hsl(var(${virtualBranch ? '--blue-8-hsl' : '--purple-8-hsl'}) )`"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
`${i18nKey || 'branch.card'}.${virtualBranch ? 'branchVirtual' : 'branchLabel'}`,
|
||||
)
|
||||
}}
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -76,17 +95,21 @@ defineProps<{
|
|||
margin-bottom: var(--size-2);
|
||||
"
|
||||
/>
|
||||
<div
|
||||
v-for="key in fieldSelected?.sort() || [
|
||||
'branchLabelAddress',
|
||||
'branchLabelTel',
|
||||
'branchLabelType',
|
||||
]"
|
||||
class="branch-card__data"
|
||||
>
|
||||
<div>{{ $t(`branch.card.${key}`) }}</div>
|
||||
<div>{{ data[key as keyof typeof data] }}</div>
|
||||
</div>
|
||||
<slot name="data"></slot>
|
||||
<template v-if="!$slots.data">
|
||||
<div
|
||||
v-for="key in fieldSelected || [
|
||||
'branchLabelAddress',
|
||||
'branchLabelTel',
|
||||
'branchLabelType',
|
||||
]"
|
||||
:key="key"
|
||||
class="branch-card__data"
|
||||
>
|
||||
<div>{{ $t(`${i18nKey || 'branch.card'}.${key}`) }}</div>
|
||||
<div>{{ data[key as keyof typeof data] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -189,6 +212,10 @@ defineProps<{
|
|||
}
|
||||
}
|
||||
|
||||
&.branch-card__br-virtual {
|
||||
--_branch-card-bg: var(--blue-6-hsl);
|
||||
}
|
||||
|
||||
&.branch-card__inactive {
|
||||
--_branch-status-color: var(--red-4-hsl);
|
||||
--_branch-badge-bg: var(--red-4-hsl);
|
||||
|
|
|
|||
|
|
@ -8,10 +8,28 @@ import { QSelect } from 'quasar';
|
|||
import { AddButton, DeleteButton } from 'components/button';
|
||||
import ToggleButton from '../button/ToggleButton.vue';
|
||||
|
||||
import ImageHover from '../ImageHover.vue';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
||||
const bankBookList = defineModel<BankBook[]>('bankBookList', { default: [] });
|
||||
|
||||
const bankQrUrl = ref<string[]>([]);
|
||||
const listIndex = ref(0);
|
||||
|
||||
const reader = new FileReader();
|
||||
const inputFile = (() => {
|
||||
const _element = document.createElement('input');
|
||||
_element.type = 'file';
|
||||
_element.accept = 'image/*';
|
||||
_element.addEventListener('change', change);
|
||||
return _element;
|
||||
})();
|
||||
reader.addEventListener('load', () => {
|
||||
if (typeof reader.result === 'string')
|
||||
bankQrUrl.value[listIndex.value] = reader.result;
|
||||
});
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
dense?: boolean;
|
||||
|
|
@ -20,6 +38,11 @@ defineProps<{
|
|||
view?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
(e: 'viewQr', index: number): void;
|
||||
(e: 'editQr', index: number): void;
|
||||
}>();
|
||||
|
||||
const bankBookOptions = ref<Record<string, unknown>[]>([]);
|
||||
let bankBoookFilter: (
|
||||
value: string,
|
||||
|
|
@ -43,6 +66,15 @@ function addBankBook() {
|
|||
});
|
||||
}
|
||||
|
||||
function change(e: Event) {
|
||||
const _element = e.target as HTMLInputElement | null;
|
||||
const _file = _element?.files?.[0];
|
||||
if (_file) {
|
||||
bankBookList.value[listIndex.value].bankQr = _file;
|
||||
reader.readAsDataURL(_file);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (optionStore.globalOption) {
|
||||
bankBoookFilter = selectFilterOptionRefMod(
|
||||
|
|
@ -99,7 +131,7 @@ watch(
|
|||
|
||||
<div
|
||||
v-for="(book, i) in bankBookList"
|
||||
class="col-12 row q-col-gutter-sm"
|
||||
class="col-12 row"
|
||||
:class="{ 'q-pt-lg': i !== 0 }"
|
||||
:key="i"
|
||||
>
|
||||
|
|
@ -129,144 +161,132 @@ watch(
|
|||
v-if="bankBookList.length !== 1 && !readonly"
|
||||
id="btn-delete-bank"
|
||||
icon-only
|
||||
@click="deleteItem(bankBookList, i)"
|
||||
@click="
|
||||
() => {
|
||||
deleteItem(bankBookList, i);
|
||||
bankQrUrl[i] = '';
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
emit-value
|
||||
fill-input
|
||||
map-options
|
||||
hide-bottom-space
|
||||
option-value="value"
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
class="col-12 col-md-4"
|
||||
autocomplete="off"
|
||||
:dense="dense"
|
||||
:label="$t('branch.form.bank')"
|
||||
:options="bankBookOptions"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
for="select-bankbook"
|
||||
:model-value="readonly ? book.bankName || '-' : book.bankName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.bankName = v) : '')
|
||||
"
|
||||
@filter="bankBoookFilter"
|
||||
@clear="book.bankName = ''"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item
|
||||
v-if="scope.opt"
|
||||
v-bind="scope.itemProps"
|
||||
class="row items-center"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-img
|
||||
:src="`/img/bank/${scope.opt.value}.png`"
|
||||
class="bordered"
|
||||
style="border-radius: 50%"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ scope.opt.label }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<template v-slot:selected-item="scope">
|
||||
<q-item-section
|
||||
v-if="scope.opt && book.bankName"
|
||||
avatar
|
||||
class="q-py-sm"
|
||||
>
|
||||
<q-img
|
||||
:src="`/img/bank/${scope.opt.value}.png`"
|
||||
class="bordered"
|
||||
style="border-radius: 50%"
|
||||
/>
|
||||
</q-item-section>
|
||||
</template>
|
||||
|
||||
<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
|
||||
outlined
|
||||
for="input-bankbook"
|
||||
class="col-md-4 col-12"
|
||||
lazy-rules="ondemand"
|
||||
hide-bottom-space
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="$t('branch.form.bankBranch')"
|
||||
:model-value="readonly ? book.bankBranch || '-' : book.bankBranch"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.bankBranch = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
option-value="value"
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
class="col-12 col-md-4"
|
||||
autocomplete="off"
|
||||
:dense="dense"
|
||||
:label="$t('branch.form.bankAccountType')"
|
||||
:options="accountTypeOptions"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
for="select-bankbook"
|
||||
:model-value="readonly ? book.accountType || '-' : book.accountType"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.accountType = v) : '')
|
||||
"
|
||||
@filter="accountTypeFilter"
|
||||
@clear="book.accountType = ''"
|
||||
<div
|
||||
class="bordered q-mr-sm rounded"
|
||||
:class="{ 'cursor-pointer': !readonly }"
|
||||
>
|
||||
<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
|
||||
outlined
|
||||
for="input-bankbook"
|
||||
class="col-12 col-md-4"
|
||||
lazy-rules="ondemand"
|
||||
hide-bottom-space
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="$t('branch.form.bankAccountNumber')"
|
||||
:maxlength="13"
|
||||
:model-value="readonly ? book.accountNumber || '-' : book.accountNumber"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.accountNumber = v) : '')
|
||||
"
|
||||
/>
|
||||
<!-- :rules="[
|
||||
<ImageHover
|
||||
:img="book.bankUrl"
|
||||
@view="() => $emit('viewQr', i)"
|
||||
@edit="
|
||||
() => {
|
||||
$emit('editQr', i);
|
||||
}
|
||||
"
|
||||
icon="mdi-qrcode"
|
||||
color="gray"
|
||||
bg-color="white"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
emit-value
|
||||
fill-input
|
||||
map-options
|
||||
hide-bottom-space
|
||||
option-value="value"
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
class="col-12 col-md-4"
|
||||
autocomplete="off"
|
||||
:dense="dense"
|
||||
:label="$t('branch.form.bank')"
|
||||
:options="bankBookOptions"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
for="select-bankbook"
|
||||
:model-value="readonly ? book.bankName || '-' : book.bankName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.bankName = v) : '')
|
||||
"
|
||||
@filter="bankBoookFilter"
|
||||
@clear="book.bankName = ''"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item
|
||||
v-if="scope.opt"
|
||||
v-bind="scope.itemProps"
|
||||
class="row items-center"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-img
|
||||
:src="`/img/bank/${scope.opt.value}.png`"
|
||||
class="bordered"
|
||||
style="border-radius: 50%"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ scope.opt.label }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<template v-slot:selected-item="scope">
|
||||
<q-item-section
|
||||
v-if="scope.opt && book.bankName"
|
||||
avatar
|
||||
class="q-py-sm"
|
||||
>
|
||||
<q-img
|
||||
:src="`/img/bank/${scope.opt.value}.png`"
|
||||
class="bordered"
|
||||
style="border-radius: 50%"
|
||||
/>
|
||||
</q-item-section>
|
||||
</template>
|
||||
|
||||
<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
|
||||
outlined
|
||||
for="input-bankbook"
|
||||
class="col-12 col-md-4"
|
||||
hide-bottom-space
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="$t('branch.form.bankAccountNumber')"
|
||||
:maxlength="15"
|
||||
:model-value="
|
||||
readonly ? book.accountNumber || '-' : book.accountNumber
|
||||
"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.accountNumber = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
for="input-bankbook"
|
||||
class="col-md-4 col-12"
|
||||
hide-bottom-space
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="$t('branch.form.bankBranch')"
|
||||
:model-value="readonly ? book.bankBranch || '-' : book.bankBranch"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.bankBranch = v) : '')
|
||||
"
|
||||
/>
|
||||
<!-- :rules="[
|
||||
(val: string) =>
|
||||
(val.length >= 7 && val.length <= 13) ||
|
||||
$t('form.error.please', {
|
||||
|
|
@ -274,20 +294,57 @@ watch(
|
|||
}),
|
||||
]" -->
|
||||
|
||||
<q-input
|
||||
outlined
|
||||
for="input-bankbook"
|
||||
class="col-12 col-md-4"
|
||||
lazy-rules="ondemand"
|
||||
hide-bottom-space
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="$t('branch.form.bankAccountName')"
|
||||
:model-value="readonly ? book.accountName || '-' : book.accountName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.accountName = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
for="input-bankbook"
|
||||
class="col-12 col-md-4"
|
||||
hide-bottom-space
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="$t('branch.form.bankAccountName')"
|
||||
:model-value="readonly ? book.accountName || '-' : book.accountName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.accountName = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
option-value="value"
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
class="col-12 col-md-4"
|
||||
autocomplete="off"
|
||||
:dense="dense"
|
||||
:label="$t('branch.form.bankAccountType')"
|
||||
:options="accountTypeOptions"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
for="select-bankbook"
|
||||
:model-value="readonly ? book.accountType || '-' : book.accountType"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (book.accountType = v) : '')
|
||||
"
|
||||
@filter="accountTypeFilter"
|
||||
@clear="book.accountType = ''"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
|
|||
|
|
@ -32,13 +32,22 @@ defineProps<{
|
|||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-4"
|
||||
:label="$t('form.email')"
|
||||
:rules="
|
||||
readonly
|
||||
? undefined
|
||||
: [
|
||||
(v: string) =>
|
||||
!v ||
|
||||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
|
||||
$t('form.error.invalid'),
|
||||
]
|
||||
"
|
||||
for="input-email"
|
||||
:model-value="readonly ? email || '-' : email"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
|
||||
|
|
@ -54,7 +63,6 @@ defineProps<{
|
|||
</q-input>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -78,7 +86,6 @@ defineProps<{
|
|||
</q-input>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -91,7 +98,6 @@ defineProps<{
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -106,7 +112,6 @@ defineProps<{
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -130,7 +135,6 @@ defineProps<{
|
|||
</q-input>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { isRoleInclude } from 'src/stores/utils';
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
|
||||
const code = defineModel<string>('code');
|
||||
const branchCount = defineModel<number>('branchCount', { default: 0 });
|
||||
const codeSubBranch = defineModel<string>('codeSubBranch');
|
||||
|
|
@ -7,6 +10,11 @@ const name = defineModel<string>('name');
|
|||
const abbreviation = defineModel<string>('abbreviation');
|
||||
const nameEN = defineModel<string>('nameEN');
|
||||
const typeBranch = defineModel<string>('typeBranch');
|
||||
const virtual = defineModel<boolean>('virtual');
|
||||
|
||||
const permitExpireDate = defineModel<Date>('permitExpireDate');
|
||||
const permitIssueDate = defineModel<Date>('permitIssueDate');
|
||||
const permitNo = defineModel<string>('permitNo');
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
|
|
@ -42,7 +50,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
<div class="col-12 row q-col-gutter-sm">
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:disable="view && !readonly"
|
||||
|
|
@ -53,14 +60,16 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
for="input-abbreviation"
|
||||
:model-value="view ? formatCode(abbreviation, 'code') : abbreviation"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(val) =>
|
||||
(val && val.length > 0 && /^[a-zA-Z]+$/.test(val)) ||
|
||||
$t('form.error.invalid'),
|
||||
(val && val.length > 0 && /^[a-zA-Z0-9]+$/.test(val)) ||
|
||||
$t('form.error.invalidCustomeMessage', {
|
||||
msg: $t('form.error.letterAndNumOnly'),
|
||||
}),
|
||||
]"
|
||||
@update:model-value="(v) => (abbreviation = v as string)"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
readonly
|
||||
|
|
@ -83,26 +92,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
@update:model-value="(v) => (code = v as string)"
|
||||
/>
|
||||
|
||||
<!-- view ? `${formatCode(code, 'number')}${branchCount}` : code -->
|
||||
<!-- <q-input
|
||||
lazy-rules="ondemand"
|
||||
v-if="typeBranch !== 'headOffice'"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:disable="view && !readonly"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('branch.form.codeBranch')"
|
||||
for="input-code-sub-branch"
|
||||
:model-value="
|
||||
view ? formatCode(codeSubBranch, 'number') : codeSubBranch
|
||||
"
|
||||
@update:model-value="(v) => (codeSubBranch = v as string)"
|
||||
/> -->
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -110,11 +100,14 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
class="col-md-5 col-12"
|
||||
:label="$t('branch.form.taxNo')"
|
||||
v-model="taxNo"
|
||||
mask="#############"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(val) =>
|
||||
(val && val.length === 13 && /[0-9]+/.test(val)) ||
|
||||
$t('form.error.invalid'),
|
||||
$t('form.error.invalidCustomeMessage', {
|
||||
msg: $t('form.error.requireLength', { msg: 13 }),
|
||||
}),
|
||||
]"
|
||||
for="input-tax-no"
|
||||
/>
|
||||
|
|
@ -122,7 +115,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -139,7 +131,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
for="input-name"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -151,9 +142,81 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
? $t('branch.form.headofficeNameEN')
|
||||
: $t('branch.form.branchNameEN')
|
||||
"
|
||||
:rules="[
|
||||
(val) => !!val || $t('form.error.required'),
|
||||
(val) =>
|
||||
/^[A-Za-z0-9\s.,]+$/.test(val) || $t('form.error.letterOnly'),
|
||||
]"
|
||||
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')"
|
||||
for="input-name-en"
|
||||
>
|
||||
<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">
|
||||
<q-input
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-4"
|
||||
:label="$t('general.licenseNumber')"
|
||||
v-model="permitNo"
|
||||
:rules="[(val) => val && val.length > 0]"
|
||||
:error-message="$t('form.error.required')"
|
||||
for="input-name"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
class="col-3"
|
||||
id="input-start-date"
|
||||
:readonly="readonly"
|
||||
:label="$t('general.dateOfIssue')"
|
||||
v-model="permitIssueDate"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
class="col-3"
|
||||
id="input-start-date"
|
||||
:readonly="readonly"
|
||||
:label="$t('general.expirationDate')"
|
||||
v-model="permitExpireDate"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue';
|
||||
import ImageHover from '../ImageHover.vue';
|
||||
|
||||
const qr = defineModel<string | null>('qr');
|
||||
|
||||
|
|
@ -13,6 +13,8 @@ defineProps<{
|
|||
|
||||
defineEmits<{
|
||||
(e: 'upload'): void;
|
||||
(e: 'viewQr'): void;
|
||||
(e: 'editQr'): void;
|
||||
}>();
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -35,47 +37,14 @@ defineEmits<{
|
|||
}"
|
||||
class="col-12 row branch-form-show-qr-code"
|
||||
>
|
||||
<div class="col-12 column flex-center q-py-md">
|
||||
<q-img
|
||||
v-if="qr"
|
||||
:src="qr as string"
|
||||
style="width: 150px; height: 150px"
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
style="background: none"
|
||||
class="full-width full-height items-center justify-center flex"
|
||||
>
|
||||
<q-img src="/no-data.png" width="5rem" />
|
||||
</div>
|
||||
</template>
|
||||
</q-img>
|
||||
|
||||
<q-btn
|
||||
v-else
|
||||
@click="$emit('upload')"
|
||||
class="branch-form-btn-qr-code q-mb-md"
|
||||
:class="{ 'dark-form-btn-qr-code': $q.dark.isActive }"
|
||||
unelevated
|
||||
:color="$q.dark.isActive ? 'black' : 'grey-2'"
|
||||
:text-color="$q.dark.isActive ? 'white' : 'grey-5'"
|
||||
>
|
||||
<Icon icon="teenyicons:add-outline" width="30px" height="50px" />
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
v-if="!readonly"
|
||||
:text-color="$q.dark.isActive ? 'black' : 'white'"
|
||||
style="
|
||||
background: var(--blue-5);
|
||||
color: var(--blue-0);
|
||||
font-size: 12px;
|
||||
"
|
||||
unelevated
|
||||
rounded
|
||||
:label="$t('general.upload')"
|
||||
@click="$emit('upload')"
|
||||
id="btn-upload-qr-code"
|
||||
<div class="col-12 column flex-center text-center q-py-md">
|
||||
<ImageHover
|
||||
:img="qr"
|
||||
@view="() => $emit('viewQr')"
|
||||
@edit="() => $emit('editQr')"
|
||||
icon="mdi-qrcode"
|
||||
color="gray"
|
||||
bg-color="white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -148,7 +148,6 @@ watch(
|
|||
style="margin-left: 0px; padding-left: 0px"
|
||||
>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-regis-no"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -193,7 +192,6 @@ watch(
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
id="input-responsible-area"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
|
|
@ -222,7 +220,6 @@ watch(
|
|||
style="row-gap: 16px"
|
||||
>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-discount-condition"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -254,9 +251,9 @@ watch(
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-3 col-6"
|
||||
id="input-source-nationality"
|
||||
for="input-source-nationality"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
|
|
@ -290,8 +287,8 @@ watch(
|
|||
option-value="value"
|
||||
option-label="label"
|
||||
class="col-md-3 col-6"
|
||||
lazy-rules="ondemand"
|
||||
id="input-import-nationality"
|
||||
for="input-import-nationality"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
|
|
@ -324,9 +321,9 @@ watch(
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="label"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-6 col-12"
|
||||
id="select-trainig-place"
|
||||
for="select-trainig-place"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:hide-dropdown-icon="readonly"
|
||||
|
|
@ -348,7 +345,6 @@ watch(
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-checkpoint"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -362,7 +358,6 @@ watch(
|
|||
@clear="checkpoint = ''"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-checkpoint-en"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -416,6 +411,7 @@ watch(
|
|||
<q-list bordered separator class="rounded" style="padding: 0">
|
||||
<q-item
|
||||
id="attachment-file"
|
||||
for="attachment-file"
|
||||
v-for="item in agencyFileList"
|
||||
clickable
|
||||
:key="item.url"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import useUserStore from 'stores/user';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { isRoleInclude } from 'src/stores/utils';
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ const userRole = defineModel<string>('userRole');
|
|||
const username = defineModel<string | null | undefined>('username');
|
||||
const userCode = defineModel<string>('userCode');
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
dense?: boolean;
|
||||
outlined?: boolean;
|
||||
|
|
@ -73,9 +73,33 @@ const roleFilter = selectFilterOptionRefMod(
|
|||
);
|
||||
|
||||
onMounted(async () => {
|
||||
if (userStore.userOption.hqOpts[0].value)
|
||||
if (userStore.userOption.hqOpts[0].value && !props.readonly) {
|
||||
await userStore.fetchBrOption(userStore.userOption.hqOpts[0].value);
|
||||
if (userStore.userOption.brOpts.length === 1) {
|
||||
brId.value = userStore.userOption.brOpts[0].value;
|
||||
}
|
||||
brFilter = selectFilterOptionRefMod(
|
||||
ref(userStore.userOption.brOpts),
|
||||
brOptions,
|
||||
'label',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => hqId.value,
|
||||
async (v) => {
|
||||
if (v) {
|
||||
userStore.userOption.brOpts = [];
|
||||
await userStore.fetchBrOption(v);
|
||||
brFilter = selectFilterOptionRefMod(
|
||||
ref(userStore.userOption.brOpts),
|
||||
brOptions,
|
||||
'label',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
|
|
@ -107,7 +131,6 @@ onMounted(async () => {
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-2 col-12"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
|
|
@ -148,7 +171,6 @@ onMounted(async () => {
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-2 col-12"
|
||||
:disable="isRoleInclude(['branch_manager']) && !readonly"
|
||||
:dense="dense"
|
||||
|
|
@ -170,7 +192,6 @@ onMounted(async () => {
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-username"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -204,7 +225,6 @@ onMounted(async () => {
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
for="select-user-type"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
|
|
@ -241,7 +261,6 @@ onMounted(async () => {
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
for="select-user-role"
|
||||
:dense="dense"
|
||||
v-model="userRole"
|
||||
|
|
@ -266,7 +285,7 @@ onMounted(async () => {
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<!-- <q-input lazy-rules="ondemand"
|
||||
<!-- <q-input
|
||||
id="input-user-code"
|
||||
:dense="dense"
|
||||
:outlined="readonly ? false : outlined"
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@ const birthDate = defineModel<Date | string | null>('birthDate');
|
|||
const nationality = defineModel<string>('nationality');
|
||||
const midName = defineModel<string | null>('midName');
|
||||
const midNameEN = defineModel<string | null>('midNameEN');
|
||||
const citizenId = defineModel<string>('citizenId');
|
||||
const citizenIssue = defineModel<Date | null>('citizenIssue');
|
||||
const citizenExpire = defineModel<Date | null>('citizenExpire');
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
dense?: boolean;
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
|
|
@ -91,8 +94,9 @@ watch(
|
|||
watch(
|
||||
() => prefixName.value,
|
||||
(v) => {
|
||||
if (props.readonly) return;
|
||||
if (v === 'mr') gender.value = 'male';
|
||||
else gender.value = 'female';
|
||||
else if (v !== '') gender.value = 'female';
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
|
@ -111,6 +115,26 @@ watch(
|
|||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
v-if="!employee"
|
||||
outlined
|
||||
class="col-md-5 col-12"
|
||||
hide-bottom-space
|
||||
v-model="citizenId"
|
||||
mask="#############"
|
||||
:readonly="readonly"
|
||||
:dense="dense"
|
||||
:label="$t('personnel.form.citizenId')"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(val) =>
|
||||
(val && val.length === 13 && /[0-9]+/.test(val)) ||
|
||||
$t('form.error.invalidCustomeMessage', {
|
||||
msg: $t('form.error.requireLength', { msg: 13 }),
|
||||
}),
|
||||
]"
|
||||
for="input-citizen-id"
|
||||
/>
|
||||
<div class="col-12 row" style="display: flex; gap: var(--size-2)">
|
||||
<q-select
|
||||
outlined
|
||||
|
|
@ -123,7 +147,6 @@ watch(
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
hide-dropdown-icon
|
||||
class="col-md-1 col-6"
|
||||
:dense="dense"
|
||||
|
|
@ -148,7 +171,6 @@ watch(
|
|||
</q-select>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-first-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -161,7 +183,6 @@ watch(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mid-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -177,7 +198,6 @@ watch(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-last-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -192,7 +212,6 @@ watch(
|
|||
|
||||
<div class="col-12 row" style="display: flex; gap: var(--size-2)">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-first-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -200,7 +219,7 @@ watch(
|
|||
:readonly="readonly"
|
||||
:disable="!readonly"
|
||||
class="col-md-1 col-6"
|
||||
:label="$t('personnel.form.prefixName')"
|
||||
label="Title"
|
||||
:model-value="
|
||||
readonly
|
||||
? capitalize(prefixName || '') || '-'
|
||||
|
|
@ -213,26 +232,28 @@ watch(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-first-name-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('personnel.form.firstNameEN')"
|
||||
label="Name"
|
||||
v-model="firstNameEN"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
:rules="[
|
||||
(val: string) => !!val || $t('form.error.required'),
|
||||
(val: string) =>
|
||||
/^[A-Za-z]+$/.test(val) || $t('form.error.letterOnly'),
|
||||
]"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mid-name-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('personnel.form.middleNameEN')"
|
||||
label="Mid Name"
|
||||
:model-value="readonly ? midNameEN || '-' : midNameEN"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (midNameEN = v) : '')
|
||||
|
|
@ -240,21 +261,23 @@ watch(
|
|||
@clear="midNameEN = ''"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-last-name-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('personnel.form.lastNameEN')"
|
||||
label="Surname"
|
||||
v-model="lastNameEN"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
:rules="[
|
||||
(val: string) => !!val || $t('form.error.required'),
|
||||
(val: string) =>
|
||||
/^[A-Za-z]+$/.test(val) || $t('form.error.letterOnly'),
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
v-if="!employee"
|
||||
:for="`${prefixId}-input-telephone`"
|
||||
:dense="dense"
|
||||
|
|
@ -268,20 +291,48 @@ watch(
|
|||
(v) => (typeof v === 'string' ? (telephoneNo = v) : '')
|
||||
"
|
||||
@clear="telephoneNo = ''"
|
||||
/>
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
v-if="!employee"
|
||||
:for="`${prefixId}-input-email`"
|
||||
:dense="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-3 col-6"
|
||||
: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-select
|
||||
v-if="!employee"
|
||||
|
|
@ -295,7 +346,6 @@ watch(
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-2 col-6"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
|
|
@ -332,7 +382,6 @@ watch(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-age`"
|
||||
:id="`${prefixId}-input-age`"
|
||||
:dense="dense"
|
||||
|
|
@ -348,6 +397,33 @@ watch(
|
|||
"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
v-if="!employee"
|
||||
v-model="citizenIssue"
|
||||
class="col-md-2 col-6"
|
||||
:id="`${prefixId}-input-citizen-issue`"
|
||||
:readonly="readonly"
|
||||
:label="$t('personnel.form.citizenIssue')"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('personnel.form.citizenIssue'),
|
||||
}),
|
||||
]"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
v-if="!employee"
|
||||
v-model="citizenExpire"
|
||||
class="col-md-2 col-6"
|
||||
:id="`${prefixId}-input-citizen-expire`"
|
||||
:readonly="readonly"
|
||||
:label="$t('personnel.form.citizenExpire')"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
v-if="employee"
|
||||
outlined
|
||||
|
|
@ -361,7 +437,6 @@ watch(
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
class="col-2"
|
||||
:dense="dense"
|
||||
v-model="gender"
|
||||
|
|
@ -395,7 +470,6 @@ watch(
|
|||
option-label="label"
|
||||
option-value="value"
|
||||
v-model="nationality"
|
||||
lazy-rules="ondemand"
|
||||
class="col-2"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { dateFormat, parseAndFormatDate } from 'src/utils/datetime';
|
||||
import useAddressStore, {
|
||||
District,
|
||||
Province,
|
||||
SubDistrict,
|
||||
} from 'stores/address';
|
||||
import { EmployeeCheckupCreate } from 'stores/employee/types';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { checkTabBeforeAdd, selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { QSelect } from 'quasar';
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
import {
|
||||
|
|
@ -19,7 +17,6 @@ import {
|
|||
UndoButton,
|
||||
} from 'components/button';
|
||||
|
||||
const { locale } = useI18n();
|
||||
const adrressStore = useAddressStore();
|
||||
|
||||
const addrOptions = reactive<{
|
||||
|
|
@ -58,8 +55,10 @@ withDefaults(
|
|||
typeCustomer?: string;
|
||||
prefixId: string;
|
||||
showBtnSave?: boolean;
|
||||
hideAction?: boolean;
|
||||
}>(),
|
||||
{
|
||||
hideAction: false,
|
||||
showBtnSave: false,
|
||||
},
|
||||
);
|
||||
|
|
@ -98,8 +97,10 @@ function addCheckup() {
|
|||
checkupResult: '',
|
||||
checkupType: '',
|
||||
});
|
||||
if (employeeCheckup.value)
|
||||
if (employeeCheckup.value) {
|
||||
tab.value = `tab${employeeCheckup.value.length - 1}`;
|
||||
currentIndex.value = employeeCheckup.value.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +161,7 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
/>
|
||||
{{ $t(`customerEmployee.formHealthCheck.title`) }}
|
||||
<AddButton
|
||||
v-if="!readonly"
|
||||
v-if="currentIndex === -1 && !hideAction"
|
||||
id="btn-add-bank"
|
||||
icon-only
|
||||
class="q-ml-sm"
|
||||
|
|
@ -185,7 +186,7 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
/>
|
||||
<span class="col-12 flex justify-between items-center">
|
||||
{{ $t('general.times', { number: index + 1 }) }}
|
||||
<div class="row items-center">
|
||||
<div class="row items-center" v-if="!hideAction">
|
||||
<UndoButton
|
||||
v-if="!readonly && !!checkup.id && !checkup.statusSave"
|
||||
id="btn-info-health-undo"
|
||||
|
|
@ -222,7 +223,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
</span>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly || checkup.statusSave"
|
||||
|
|
@ -244,7 +244,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
v-model="checkup.checkupType"
|
||||
:dense="dense"
|
||||
:readonly="readonly || checkup.statusSave"
|
||||
|
|
@ -274,7 +273,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
option-value="id"
|
||||
input-debounce="0"
|
||||
option-label="name"
|
||||
lazy-rules="ondemand"
|
||||
class="col-2"
|
||||
v-model="checkup.provinceId"
|
||||
:dense="dense"
|
||||
|
|
@ -295,7 +293,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
</q-select>
|
||||
<!-- @filter="provinceFilter" -->
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly || checkup.statusSave"
|
||||
|
|
@ -318,7 +315,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
v-model="checkup.medicalBenefitScheme"
|
||||
:dense="dense"
|
||||
:readonly="readonly || checkup.statusSave"
|
||||
|
|
@ -337,7 +333,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:label="$t('general.remark')"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -378,7 +373,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
v-model="checkup.insuranceCompany"
|
||||
:dense="dense"
|
||||
:readonly="readonly || checkup.statusSave"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defineProps<{
|
|||
separator?: boolean;
|
||||
employee?: boolean;
|
||||
prefixId: string;
|
||||
hideAction?: boolean;
|
||||
}>();
|
||||
|
||||
const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
||||
|
|
@ -31,7 +32,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
{{ $t('customerEmployee.form.group.family') }}
|
||||
<div class="row q-ml-auto">
|
||||
<div class="row q-ml-auto" v-if="!hideAction">
|
||||
<UndoButton
|
||||
v-if="!readonly && !!employeeOther.id && !employeeOther.statusSave"
|
||||
id="btn-info-health-undo"
|
||||
|
|
@ -66,7 +67,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
<div class="col-12 row q-col-gutter-y-sm">
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-citizen-id`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -83,7 +83,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
</div>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-father-first-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -94,7 +93,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.fatherFirstName"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-father-last-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -105,7 +103,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.fatherLastName"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-father-first-name-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -116,7 +113,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.fatherFirstNameEN"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-father-last-name-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -127,7 +123,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.fatherLastNameEN"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-father-birthplace`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -145,7 +140,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
</div>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mother-first-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -156,7 +150,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.motherFirstName"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mother-last-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -167,7 +160,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.motherLastName"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mother-first-name-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -178,7 +170,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.motherFirstNameEN"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mother-last-name-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -189,7 +180,6 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
|
|||
v-model="employeeOther.motherLastNameEN"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mother-birthplace`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ watch(
|
|||
name="mdi-passport"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
{{ $t(`${title}`) }}
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
|
|
@ -111,7 +111,6 @@ watch(
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
v-model="passportType"
|
||||
class="col-12"
|
||||
:class="{ 'col-md-3': !ocr }"
|
||||
|
|
@ -122,8 +121,11 @@ watch(
|
|||
:for="`${prefixId}-select-passport-type`"
|
||||
:label="$t('customerEmployee.form.passportType')"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val || $t('selectValidate') + $t('formDialogInputPassportType'),
|
||||
(val) =>
|
||||
(val && val.length > 0) ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('customerEmployee.form.passportType'),
|
||||
}),
|
||||
]"
|
||||
@filter="passportTypeFilter"
|
||||
>
|
||||
|
|
@ -136,7 +138,6 @@ watch(
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-passport-no`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -145,13 +146,9 @@ watch(
|
|||
:class="{ 'col-12': ocr, 'col-6': !ocr, 'col-md-3': !ocr }"
|
||||
:label="$t('customerEmployee.form.passportNo')"
|
||||
v-model="passportNumber"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val || $t('inputValidate') + $t('formDialogInputPassportNo'),
|
||||
]"
|
||||
:rules="[(val) => (val && val.length > 0) || $t('form.error.required')]"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-passport-ref`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -169,7 +166,6 @@ watch(
|
|||
"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-passport-place`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -178,10 +174,7 @@ watch(
|
|||
:class="{ 'col-12': ocr, 'col-6': !ocr, 'col-md-3': !ocr }"
|
||||
:label="$t('customerEmployee.form.passportPlace')"
|
||||
v-model="passportIssuingPlace"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val || $t('inputValidate') + $t('formDialogInputWPassportPlace'),
|
||||
]"
|
||||
:rules="[(val) => (val && val.length > 0) || $t('form.error.required')]"
|
||||
/>
|
||||
<q-select
|
||||
outlined
|
||||
|
|
@ -195,7 +188,6 @@ watch(
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
:class="{ 'col-12': ocr, 'col-6': !ocr, 'col-md-3': !ocr }"
|
||||
v-model="passportIssuingCountry"
|
||||
:dense="dense"
|
||||
|
|
@ -205,9 +197,11 @@ watch(
|
|||
:for="`${prefixId}-select-passport-country`"
|
||||
:label="$t('customerEmployee.form.passportIssuer')"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('selectValidate') + $t('formDialogInputPassportCountry'),
|
||||
(val) =>
|
||||
(val && val.length > 0) ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('customerEmployee.form.passportIssuer'),
|
||||
}),
|
||||
]"
|
||||
@filter="passportIssuingCountryFilter"
|
||||
>
|
||||
|
|
@ -227,9 +221,11 @@ watch(
|
|||
:class="{ 'col-md-3': !ocr }"
|
||||
:readonly="readonly"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('selectValidate') + $t('formDialogInputPassportIssuance'),
|
||||
(val) =>
|
||||
(val && val.length > 0) ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('customerEmployee.form.passportIssueDate'),
|
||||
}),
|
||||
]"
|
||||
/>
|
||||
<DatePicker
|
||||
|
|
@ -240,8 +236,11 @@ watch(
|
|||
:class="{ 'col-md-3': !ocr }"
|
||||
:readonly="readonly"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val || $t('selectValidate') + $t('formDialogInputPassportExpire'),
|
||||
(val) =>
|
||||
(val && val.length > 0) ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('customerEmployee.form.passportExpireDate'),
|
||||
}),
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -112,7 +112,6 @@ watch(
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
:class="{ 'col-4': !ocr, 'col-6': ocr }"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
|
|
@ -140,7 +139,6 @@ watch(
|
|||
!!val || $t('selectValidate') + $t('formDialogInputVisaType'),
|
||||
]" -->
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-visa-no`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -175,7 +173,6 @@ watch(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-visa-place`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -202,7 +199,6 @@ watch(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-tm6`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { dateFormat, parseAndFormatDate } from 'src/utils/datetime';
|
||||
import { EmployeeWorkCreate } from 'stores/employee/types';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { checkTabBeforeAdd, selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
|
||||
import {
|
||||
|
|
@ -13,7 +11,6 @@ import {
|
|||
SaveButton,
|
||||
UndoButton,
|
||||
} from 'components/button';
|
||||
const { locale } = useI18n();
|
||||
|
||||
const currentIndex = defineModel<number>('currentIndex');
|
||||
const employeeWork = defineModel<EmployeeWorkCreate[]>('employeeWork');
|
||||
|
|
@ -38,6 +35,7 @@ defineProps<{
|
|||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
prefixId: string;
|
||||
hideAction?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -62,7 +60,10 @@ function addData() {
|
|||
ownerName: '',
|
||||
remark: '',
|
||||
});
|
||||
if (employeeWork.value) tab.value = `tab${employeeWork.value.length - 1}`;
|
||||
if (employeeWork.value) {
|
||||
tab.value = `tab${employeeWork.value.length - 1}`;
|
||||
currentIndex.value = employeeWork.value.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +117,7 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
/>
|
||||
{{ $t(`customerEmployee.formWorkHistory.title`) }}
|
||||
<AddButton
|
||||
v-if="!readonly"
|
||||
v-if="currentIndex === -1 && !hideAction"
|
||||
id="btn-add-bank"
|
||||
icon-only
|
||||
class="q-ml-sm"
|
||||
|
|
@ -142,7 +143,7 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
<span class="col-12 flex justify-between items-center">
|
||||
{{ $t('general.times', { number: index + 1 }) }}
|
||||
|
||||
<div class="row items-center">
|
||||
<div class="row items-center" v-if="!hideAction">
|
||||
<UndoButton
|
||||
v-if="!readonly && !!work.id && !work.statusSave"
|
||||
id="btn-info-health-undo"
|
||||
|
|
@ -189,7 +190,6 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
v-model="work.jobType"
|
||||
:dense="dense"
|
||||
:readonly="readonly || work.statusSave"
|
||||
|
|
@ -218,7 +218,6 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
hide-bottom-space
|
||||
class="col-6"
|
||||
input-debounce="0"
|
||||
lazy-rules="ondemand"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
v-model="work.workplace"
|
||||
|
|
@ -239,7 +238,6 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-work-end-date`"
|
||||
:label="$t('general.remark')"
|
||||
:dense="dense"
|
||||
|
|
@ -260,7 +258,6 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-work-permit-no`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -288,7 +285,6 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-owner-name`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -311,7 +307,6 @@ const workplaceFilter = selectFilterOptionRefMod(
|
|||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
v-model="work.positionName"
|
||||
:dense="dense"
|
||||
:readonly="readonly || work.statusSave"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { calculateAge, dateFormat } from 'src/utils/datetime';
|
||||
import { baseUrl } from 'src/stores/utils';
|
||||
|
||||
import PersonCard from 'components/shared/PersonCard.vue';
|
||||
import KebabAction from '../shared/KebabAction.vue';
|
||||
|
|
@ -121,7 +122,7 @@ defineEmits<{
|
|||
<q-avatar size="md">
|
||||
<q-img
|
||||
:src="
|
||||
props.row.profileImageUrl ||
|
||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
||||
'/images/employee-avatar.png'
|
||||
"
|
||||
class="text-center"
|
||||
|
|
@ -248,7 +249,10 @@ defineEmits<{
|
|||
$i18n.locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName} `.trim(),
|
||||
img: props.row.profileImageUrl || '/images/employee-avatar.png',
|
||||
img:
|
||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
||||
'/images/employee-avatar.png',
|
||||
fallbackImg: '/images/employee-avatar.png',
|
||||
male: props.row.gender === 'male',
|
||||
female: props.row.gender === 'female',
|
||||
detail: [
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@ onMounted(() => {
|
|||
|
||||
<div class="col-12 row" style="gap: var(--size-2)">
|
||||
<q-select
|
||||
lazy-rules="ondemand"
|
||||
:id="`${prefixId}-select-employer-branch`"
|
||||
:for="`${prefixId}-select-employer-branch`"
|
||||
:use-input="!customerBranch"
|
||||
|
|
@ -156,7 +155,7 @@ onMounted(() => {
|
|||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('customer.form.employerBranch'),
|
||||
field: $t('customerEmployee.branch'),
|
||||
}),
|
||||
]"
|
||||
>
|
||||
|
|
@ -274,7 +273,7 @@ onMounted(() => {
|
|||
'-' + ' ' + scope.opt.customer.lastName
|
||||
}}
|
||||
|
||||
{{ $t('address') }}
|
||||
{{ $t('general.address') }}
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
? `${scope.opt.addressEN || ''} ${scope.opt.subDistrict.nameEN || ''} ${scope.opt.district.nameEN || ''} ${scope.opt.province.nameEN || ''}`
|
||||
|
|
@ -297,7 +296,6 @@ onMounted(() => {
|
|||
</q-select>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-code`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
@ -310,7 +308,6 @@ onMounted(() => {
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-nrc-no`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import { QSelect } from 'quasar';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
import useOptionStore from 'src/stores/options';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { locale } = useI18n({ useScope: 'global' });
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
||||
const remark = defineModel<string>('remark', { default: '' });
|
||||
const detail = defineModel<string>('detail', { default: '' });
|
||||
const process = defineModel<number>('process');
|
||||
const name = defineModel<string>('name');
|
||||
const code = defineModel<string>('code');
|
||||
const expenseType = defineModel<string>('expenseType');
|
||||
|
||||
const registeredBranchId = defineModel<string>('registeredBranchId');
|
||||
const codeOption = ref<{ id: string; name: string }[]>([]);
|
||||
const optionsBranch = defineModel<{ id: string; name: string }[]>(
|
||||
'optionsBranch',
|
||||
{ default: [] },
|
||||
);
|
||||
|
||||
defineProps<{
|
||||
dense?: boolean;
|
||||
|
|
@ -42,11 +40,31 @@ onMounted(async () => {
|
|||
const codeOptions = ref<Record<string, unknown>[]>([]);
|
||||
const codeFilter = selectFilterOptionRefMod(codeOption, codeOptions, 'label');
|
||||
|
||||
const branchOptions = ref<Record<string, unknown>[]>([]);
|
||||
const branchFilter = selectFilterOptionRefMod(
|
||||
optionsBranch,
|
||||
branchOptions,
|
||||
'name',
|
||||
const expenseTypeOptions = ref<Record<string, unknown>[]>([]);
|
||||
let expenseTypeFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
onMounted(() => {
|
||||
if (optionStore.globalOption) {
|
||||
expenseTypeFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.expenseType),
|
||||
expenseTypeOptions,
|
||||
'label',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
expenseTypeFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.expenseType),
|
||||
expenseTypeOptions,
|
||||
'label',
|
||||
);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
|
|
@ -67,7 +85,6 @@ const branchFilter = selectFilterOptionRefMod(
|
|||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
|
|
@ -76,12 +93,12 @@ const branchFilter = selectFilterOptionRefMod(
|
|||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
:disable="!readonly && disableCode"
|
||||
class="col-md-3 col-6"
|
||||
class="col-md-4 col-12"
|
||||
v-model="code"
|
||||
id="select-br-id"
|
||||
for="select-br-id"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="codeOptions"
|
||||
|
|
@ -98,6 +115,42 @@ const branchFilter = selectFilterOptionRefMod(
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
for="input-name"
|
||||
id="input-name"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:borderless="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-8 col-12"
|
||||
:label="$t('productService.product.name')"
|
||||
v-model="name"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-input
|
||||
for="input-process"
|
||||
id="input-process"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:borderless="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('productService.product.processingTime')"
|
||||
v-model="process"
|
||||
type="number"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
color="primary"
|
||||
name="mdi-clock-outline"
|
||||
size="xs"
|
||||
class="q-mr-xs"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -109,32 +162,18 @@ const branchFilter = selectFilterOptionRefMod(
|
|||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
class="col-md-3 col-6"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
v-model="expenseType"
|
||||
id="select-br-id"
|
||||
for="select-br-id"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
id="input-source-nationality"
|
||||
v-model="registeredBranchId"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="branchOptions"
|
||||
:options="expenseTypeOptions"
|
||||
:label="$t('productService.product.expenseType')"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:label="$t('productService.product.registeredBranch')"
|
||||
:rules="[
|
||||
(val) => {
|
||||
const roles = getRole() || [];
|
||||
const isSpecialRole = ['admin', 'system', 'head_of_admin'].some(
|
||||
(role) => roles.includes(role),
|
||||
);
|
||||
return (
|
||||
isSpecialRole ||
|
||||
!!val ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('productService.product.registeredBranch'),
|
||||
})
|
||||
);
|
||||
},
|
||||
]"
|
||||
@filter="branchFilter"
|
||||
@filter="expenseTypeFilter"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
|
|
@ -145,50 +184,12 @@ const branchFilter = selectFilterOptionRefMod(
|
|||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-name"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:borderless="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('productService.product.name')"
|
||||
v-model="name"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-process"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:borderless="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('productService.product.processingTime')"
|
||||
v-model="process"
|
||||
type="number"
|
||||
/>
|
||||
<!-- <q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-detail"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:borderless="readonly"
|
||||
hide-bottom-space
|
||||
type="textarea"
|
||||
class="col-12"
|
||||
:label="$t('serviceDetail')"
|
||||
v-model="detail"
|
||||
/> -->
|
||||
|
||||
<div class="col-12">
|
||||
<q-field
|
||||
class="full-width"
|
||||
outlined
|
||||
for="input-detail"
|
||||
id="input-detail"
|
||||
:readonly="readonly"
|
||||
:borderless="readonly"
|
||||
:label="$t('general.detail')"
|
||||
|
|
@ -219,8 +220,8 @@ const branchFilter = selectFilterOptionRefMod(
|
|||
</q-field>
|
||||
</div>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
for="input-remark"
|
||||
id="input-remark"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { QSelect } from 'quasar';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const remark = defineModel<string>('remark');
|
||||
|
|
@ -12,8 +13,7 @@ const code = defineModel<string>('code');
|
|||
const serviceCode = defineModel<string>('serviceCode');
|
||||
const serviceName = defineModel<string>('serviceNameTh');
|
||||
const serviceDescription = defineModel<string>('serviceDescription');
|
||||
|
||||
const registeredBranchId = defineModel<string | null>('registeredBranchId');
|
||||
const registeredBranchId = defineModel<string>('registeredBranchId');
|
||||
|
||||
const optionsBranch = defineModel<{ id: string; name: string }[]>(
|
||||
'optionsBranch',
|
||||
|
|
@ -31,18 +31,21 @@ defineProps<{
|
|||
}>();
|
||||
|
||||
const branchOptions = ref<Record<string, unknown>[]>([]);
|
||||
const branchFilter = selectFilterOptionRefMod(
|
||||
let branchFilter = selectFilterOptionRefMod(
|
||||
optionsBranch,
|
||||
branchOptions,
|
||||
'name',
|
||||
);
|
||||
|
||||
const serviceCodeOpts = ref([{ label: 'mou', value: 'mou' }]);
|
||||
const serviceCodeOptions = ref<Record<string, unknown>[]>([]);
|
||||
const serviceCodeFilter = selectFilterOptionRefMod(
|
||||
serviceCodeOpts,
|
||||
serviceCodeOptions,
|
||||
'label',
|
||||
watch(
|
||||
() => optionsBranch.value,
|
||||
() => {
|
||||
branchFilter = selectFilterOptionRefMod(
|
||||
optionsBranch,
|
||||
branchOptions,
|
||||
'name',
|
||||
);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
|
|
@ -60,122 +63,17 @@ const serviceCodeFilter = selectFilterOptionRefMod(
|
|||
{{ $t(`form.field.basicInformation`) }}
|
||||
</div>
|
||||
<div v-if="!service" class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
readonly
|
||||
:disable="!readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="
|
||||
$t(isType ? 'productService.type.code' : 'productService.group.code')
|
||||
"
|
||||
v-model="code"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="
|
||||
$t(isType ? 'productService.type.name' : 'productService.group.name')
|
||||
"
|
||||
v-model="name"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
type="textarea"
|
||||
class="col-12"
|
||||
:label="$t('general.detail')"
|
||||
:model-value="readonly ? detail || '-' : detail"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (detail = v) : '')"
|
||||
:for="`input-detail`"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
type="textarea"
|
||||
class="col-12"
|
||||
:label="$t('general.remark')"
|
||||
:model-value="readonly ? remark || '-' : remark"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (remark = v) : '')"
|
||||
:for="`input-remark`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="service" class="col-12 row q-col-gutter-sm">
|
||||
<!-- <q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
:disable="!readonly && disableCode"
|
||||
class="col-3"
|
||||
v-model="serviceCode"
|
||||
id="select-br-id"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="serviceCodeOptions"
|
||||
:label="$t('serviceCode')"
|
||||
:hide-dropdown-icon="readonly || disableCode"
|
||||
:rules="[(val: string) => !!val]"
|
||||
@filter="serviceCodeFilter"
|
||||
>
|
||||
<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
|
||||
lazy-rules="ondemand"
|
||||
id="input-service-code"
|
||||
for="input-service-code"
|
||||
:disable="!readonly && disableCode"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-4"
|
||||
:label="$t('productService.service.code')"
|
||||
v-model="serviceCode"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-8"
|
||||
class="col-md-12 col-12"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
lazy-rules="ondemand"
|
||||
v-model="registeredBranchId"
|
||||
id="input-source-nationality"
|
||||
for="input-source-nationality"
|
||||
|
|
@ -209,22 +107,87 @@ const serviceCodeFilter = selectFilterOptionRefMod(
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
for="input-code"
|
||||
:dense="dense"
|
||||
outlined
|
||||
readonly
|
||||
:disable="!readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="
|
||||
$t(isType ? 'productService.type.code' : 'productService.group.code')
|
||||
"
|
||||
v-model="code"
|
||||
/>
|
||||
<q-input
|
||||
for="input-name"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="
|
||||
$t(isType ? 'productService.type.name' : 'productService.group.name')
|
||||
"
|
||||
v-model="name"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-input
|
||||
for="input-detail"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
type="textarea"
|
||||
class="col-12"
|
||||
:label="$t('general.detail')"
|
||||
:model-value="readonly ? detail || '-' : detail"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (detail = v) : '')"
|
||||
/>
|
||||
<q-input
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
type="textarea"
|
||||
class="col-12"
|
||||
:label="$t('general.remark')"
|
||||
:model-value="readonly ? remark || '-' : remark"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (remark = v) : '')"
|
||||
:for="`input-remark`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="service" class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
id="input-service-code"
|
||||
for="input-service-code"
|
||||
:disable="!readonly && disableCode"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-4 col-12"
|
||||
:label="$t('productService.service.code')"
|
||||
v-model="serviceCode"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
id="input-service-name"
|
||||
for="input-service-name"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-6 col"
|
||||
class="col-md-8 col-12"
|
||||
:label="$t('productService.service.name')"
|
||||
v-model="serviceName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
id="input-service-description"
|
||||
for="input-service-description"
|
||||
:dense="dense"
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ defineEmits<{
|
|||
style="background: hsla(var(--info-bg) / 0.1); min-height: 50px"
|
||||
>
|
||||
{{ $t(`productService.service.serviceProperties`) }}
|
||||
|
||||
<q-btn
|
||||
id="btn-capitalize"
|
||||
v-if="!readonly"
|
||||
:disable="readonly"
|
||||
dense
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
const serviceCharge = defineModel<number>('serviceCharge');
|
||||
const agentPrice = defineModel<number>('agentPrice');
|
||||
const price = defineModel<number>('price');
|
||||
const vatIncluded = defineModel<boolean>('vatIncluded');
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
|
|
@ -38,11 +39,44 @@ withDefaults(
|
|||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
{{ $t('productService.product.priceInformation') }}
|
||||
|
||||
<div
|
||||
class="surface-3 q-px-sm q-py-xs row text-caption q-ml-md app-text-muted"
|
||||
style="border-radius: var(--radius-3)"
|
||||
>
|
||||
<span
|
||||
id="btn-include-vat"
|
||||
for="btn-include-vat"
|
||||
class="q-px-sm q-mr-lg rounded cursor-pointer"
|
||||
:class="{
|
||||
dark: $q.dark.isActive,
|
||||
'active-addr': vatIncluded,
|
||||
'cursor-not-allowed': readonly,
|
||||
}"
|
||||
@click="readonly ? '' : (vatIncluded = true)"
|
||||
>
|
||||
{{ $t('productService.product.vatIncluded') }}
|
||||
</span>
|
||||
<span
|
||||
id="btn-no-include-vat"
|
||||
for="btn-no-include-vat"
|
||||
class="q-px-sm rounded cursor-pointer"
|
||||
:class="{
|
||||
dark: $q.dark.isActive,
|
||||
'active-addr': !vatIncluded,
|
||||
'cursor-not-allowed': readonly,
|
||||
}"
|
||||
@click="readonly ? '' : (vatIncluded = false)"
|
||||
>
|
||||
{{ $t('productService.product.vatExcluded') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
id="input-price"
|
||||
for="input-price"
|
||||
v-if="priceDisplay?.price"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -55,8 +89,9 @@ withDefaults(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
id="input-agent-price"
|
||||
for="input-agent-price"
|
||||
v-if="priceDisplay?.agentPrice"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -69,8 +104,9 @@ withDefaults(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
id="input-service-charge"
|
||||
for="input-service-charge"
|
||||
v-if="priceDisplay?.serviceCharge"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -85,4 +121,14 @@ withDefaults(
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.active-addr {
|
||||
color: hsl(var(--info-bg));
|
||||
background-color: hsla(var(--info-bg) / 0.1);
|
||||
border-radius: var(--radius-3);
|
||||
|
||||
&.dark {
|
||||
background-color: var(--surface-1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -17,25 +17,25 @@ const formServiceProperties = defineModel<Attributes>('formServiceProperties');
|
|||
|
||||
const typeOption = ref([
|
||||
{
|
||||
label: t('text'),
|
||||
label: 'Text',
|
||||
value: 'string',
|
||||
color: 'var(--pink-6-hsl)',
|
||||
icon: 'mdi-alpha-t',
|
||||
},
|
||||
{
|
||||
label: t('number'),
|
||||
label: 'Number',
|
||||
value: 'number',
|
||||
color: 'var(--purple-11-hsl)',
|
||||
icon: 'mdi-numeric',
|
||||
},
|
||||
{
|
||||
label: t('date'),
|
||||
label: 'Date',
|
||||
value: 'date',
|
||||
color: 'var(--green-9-hsl)',
|
||||
icon: 'mdi-calendar-blank-outline',
|
||||
},
|
||||
{
|
||||
label: t('selection'),
|
||||
label: 'Selection',
|
||||
value: 'array',
|
||||
color: 'var(--indigo-7-hsl)',
|
||||
icon: 'mdi-code-array',
|
||||
|
|
@ -223,7 +223,8 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
<div class="full-width full-height column no-wrap">
|
||||
<div class="row">
|
||||
<q-btn-dropdown
|
||||
icon="mdi-plus"
|
||||
id="btn-dropdow-properties"
|
||||
for="btn-dropdow-properties"
|
||||
dense
|
||||
unelevated
|
||||
color="primary"
|
||||
|
|
@ -232,7 +233,12 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
menu-anchor="bottom end"
|
||||
>
|
||||
<q-list dense v-if="formServiceProperties && propertiesOption">
|
||||
<q-item clickable @click="manageProperties('all')">
|
||||
<q-item
|
||||
for="list-all"
|
||||
id="list-all"
|
||||
clickable
|
||||
@click="manageProperties('all')"
|
||||
>
|
||||
<div class="full-width flex items-center">
|
||||
<q-icon
|
||||
v-if="
|
||||
|
|
@ -260,6 +266,8 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
clickable
|
||||
:key="index"
|
||||
@click="manageProperties(ops.value, ops.type)"
|
||||
:for="`list-${ops.value}`"
|
||||
:id="`list-${ops.value}`"
|
||||
>
|
||||
<div class="full-width flex items-center no-wrap">
|
||||
<q-icon
|
||||
|
|
@ -344,12 +352,12 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
|
||||
<!-- field name -->
|
||||
<q-select
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
emit-value
|
||||
map-options
|
||||
hide-bottom-space
|
||||
for="input-properties-name"
|
||||
class="col-md col-12 q-mr-md"
|
||||
:class="{ 'q-my-sm': $q.screen.lt.md }"
|
||||
:label="$t('productService.service.propertiesName')"
|
||||
|
|
@ -374,12 +382,13 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
<!-- type -->
|
||||
<div class="col-md col-12">
|
||||
<q-select
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
emit-value
|
||||
map-options
|
||||
hide-bottom-space
|
||||
for="input-properties-type"
|
||||
id="input-properties-type"
|
||||
:label="$t('general.type')"
|
||||
option-value="value"
|
||||
@update:model-value="
|
||||
|
|
@ -428,6 +437,7 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
v-if="scope.opt"
|
||||
v-bind="scope.itemProps"
|
||||
class="row items-center col-12"
|
||||
:id="`type-${scope.itemProps}`"
|
||||
>
|
||||
<q-avatar
|
||||
size="sm"
|
||||
|
|
@ -476,6 +486,7 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
<div class="row items-center">
|
||||
<div class="col-7 surface-3 rounded q-mr-sm q-py-xs">
|
||||
<q-checkbox
|
||||
:for="`checkbox-is-phone-number-${p.fieldName}`"
|
||||
v-if="p.type === 'string'"
|
||||
v-model="p.isPhoneNumber"
|
||||
size="xs"
|
||||
|
|
@ -483,8 +494,8 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
{{ $t('general.telephone') }}
|
||||
</div>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
v-if="p.type === 'string'"
|
||||
:for="`input-max-length-${p.fieldName}`"
|
||||
v-model="p.phoneNumberLength"
|
||||
input-class="text-caption"
|
||||
class="col additional-label"
|
||||
|
|
@ -498,7 +509,12 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
<div v-if="p.type === 'number'" class="q-gutter-y-sm">
|
||||
<div class="row items-center">
|
||||
<div class="col-md-4 col-12 surface-3 rounded">
|
||||
<q-checkbox v-model="p.comma" size="xs" class="q-py-xs" />
|
||||
<q-checkbox
|
||||
v-model="p.comma"
|
||||
size="xs"
|
||||
class="q-py-xs"
|
||||
:for="`checkbox-is-comma-${p.fieldName}`"
|
||||
/>
|
||||
{{ $t('form.useComma') }}
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -512,11 +528,12 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
v-model="p.decimal"
|
||||
size="xs"
|
||||
class="q-py-xs"
|
||||
:for="`checkbox-is-decimal-${p.fieldName}`"
|
||||
/>
|
||||
{{ $t('form.decimal') }}
|
||||
</div>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`input-decimal-place-${p.fieldName}`"
|
||||
v-model="p.decimalPlace"
|
||||
class="col additional-label"
|
||||
:class="{ 'q-mt-xs': $q.screen.lt.md }"
|
||||
|
|
@ -536,8 +553,8 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
>
|
||||
<div class="col rounded">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
v-model="p.options[i]"
|
||||
:for="`input-selection-${p.fieldName}-${i}`"
|
||||
class="col additional-label"
|
||||
dense
|
||||
outlined
|
||||
|
|
@ -549,6 +566,8 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
</div>
|
||||
<div class="col-1 q-pl-sm">
|
||||
<q-btn
|
||||
:id="`btn-delete-selection-${p.fieldName}-${i}`"
|
||||
:for="`btn-delete-selection-${p.fieldName}-${i}`"
|
||||
@click="
|
||||
() => {
|
||||
p.options.splice(i, 1);
|
||||
|
|
@ -565,6 +584,8 @@ function confirmDelete(items: unknown[], index: number) {
|
|||
</div>
|
||||
<div class="row">
|
||||
<q-btn
|
||||
:for="`btn-add-selection-${p.fieldName}`"
|
||||
:id="`btn-add-selection-${p.fieldName}`"
|
||||
@click="
|
||||
() => {
|
||||
p.options.push('');
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ withDefaults(
|
|||
style="left: 0px; bottom: 0px"
|
||||
>
|
||||
<KebabAction
|
||||
:id-name="title"
|
||||
:status="data?.status"
|
||||
class="absolute-top-right"
|
||||
@view="$emit('menuViewDetail')"
|
||||
@edit="$emit('menuEdit')"
|
||||
|
|
@ -144,7 +146,7 @@ withDefaults(
|
|||
style="background-color: transparent"
|
||||
loading="lazy"
|
||||
:src="
|
||||
`${baseUrl}/${data?.type === 'service' ? 'service' : 'product'}/${data?.id}/image`.concat(
|
||||
`${baseUrl}/${data?.type}/${data?.id}/image/${data?.selectedImage}`.concat(
|
||||
noTimeImg ? '' : `?ts=${Date.now()}`,
|
||||
)
|
||||
"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import useProductServiceStore from 'stores/product-service';
|
|||
import useOptionStore from 'stores/options';
|
||||
import { Attributes, ProductList } from 'stores/product-service/types';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const baseUrl = ref<string>(import.meta.env.VITE_API_BASE_URL);
|
||||
const productServiceStore = useProductServiceStore();
|
||||
|
|
@ -58,10 +58,26 @@ defineEmits<{
|
|||
(e: 'manageWorkName'): void;
|
||||
(e: 'workProperties'): void;
|
||||
}>();
|
||||
|
||||
watch(
|
||||
() => workNameItems.value,
|
||||
(c, o) => {
|
||||
const list = c.map((v: { name: string }) => v.name);
|
||||
const oldList = o.map((v: { name: string }) => v.name);
|
||||
const index = oldList.indexOf(workName.value);
|
||||
|
||||
if (list[index] !== oldList[index] && !list.includes(workName.value)) {
|
||||
if (list.length - 1 === index - 1) workName.value = list[index - 1];
|
||||
else workName.value = list[index];
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="bordered rounded">
|
||||
<q-expansion-item
|
||||
for="item-up"
|
||||
id="item-up"
|
||||
dense
|
||||
switch-toggle-side
|
||||
default-opened
|
||||
|
|
@ -75,6 +91,7 @@ defineEmits<{
|
|||
<q-btn
|
||||
v-if="!readonly"
|
||||
id="btn-work-up-product"
|
||||
for="btn-work-up-product"
|
||||
icon="mdi-arrow-up"
|
||||
dense
|
||||
flat
|
||||
|
|
@ -86,6 +103,7 @@ defineEmits<{
|
|||
<q-btn
|
||||
v-if="!readonly"
|
||||
id="btn-work-down-product"
|
||||
for="btn-work-down-product"
|
||||
icon="mdi-arrow-down"
|
||||
dense
|
||||
flat
|
||||
|
|
@ -96,7 +114,7 @@ defineEmits<{
|
|||
@click.stop="$emit('moveWorkDown')"
|
||||
/>
|
||||
<div
|
||||
for="select-work-name"
|
||||
:for="`select-work-name-${index + 1}`"
|
||||
class="col q-py-sm q-px-md"
|
||||
style="background-color: var(--surface-1); z-index: 2"
|
||||
@click="() => (readonly ? '' : fetchListOfWork())"
|
||||
|
|
@ -109,7 +127,6 @@ defineEmits<{
|
|||
}}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<q-menu v-if="!readonly" fit anchor="bottom left" self="top left">
|
||||
<q-item>
|
||||
<div class="full-width flex items-center justify-between">
|
||||
|
|
@ -249,6 +266,7 @@ defineEmits<{
|
|||
<q-btn
|
||||
v-if="!readonly"
|
||||
id="btn-add-work-product"
|
||||
for="btn-add-work-product"
|
||||
flat
|
||||
dense
|
||||
icon="mdi-plus"
|
||||
|
|
@ -292,6 +310,7 @@ defineEmits<{
|
|||
<q-btn
|
||||
v-if="!readonly && $q.screen.gt.xs"
|
||||
id="btn-product-down"
|
||||
for="btn-product-down"
|
||||
icon="mdi-arrow-down"
|
||||
dense
|
||||
flat
|
||||
|
|
@ -420,6 +439,7 @@ defineEmits<{
|
|||
v-if="!readonly"
|
||||
class="q-ml-md"
|
||||
id="btn-delete-work-product"
|
||||
for="btn-delete-work-product"
|
||||
icon="mdi-trash-can-outline"
|
||||
padding="0"
|
||||
dense
|
||||
|
|
|
|||
142
src/components/05_quotation/FormAbout.vue
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useBranchStore from 'src/stores/branch';
|
||||
import useCustomerStore from 'src/stores/customer';
|
||||
import SelectInput from '../shared/SelectInput.vue';
|
||||
import { QSelect } from 'quasar';
|
||||
|
||||
const { locale } = useI18n({ useScope: 'global' });
|
||||
const branchStore = useBranchStore();
|
||||
const customerStore = useCustomerStore();
|
||||
|
||||
const branch = defineModel<string>('branch');
|
||||
const customer = defineModel<string>('customer');
|
||||
const agentPrice = defineModel<boolean>('agentPrice');
|
||||
|
||||
const branchOption = ref();
|
||||
const customerOption = ref();
|
||||
|
||||
defineProps<{
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
separator?: boolean;
|
||||
employee?: boolean;
|
||||
title?: string;
|
||||
prefixId: string;
|
||||
}>();
|
||||
|
||||
async function filter(
|
||||
val: string,
|
||||
update: (...args: unknown[]) => void,
|
||||
type: 'branch' | 'customer',
|
||||
) {
|
||||
update(
|
||||
async () => {
|
||||
const res =
|
||||
type === 'branch'
|
||||
? await branchStore.fetchList({
|
||||
query: val,
|
||||
pageSize: 30,
|
||||
})
|
||||
: await customerStore.fetchList({
|
||||
query: val,
|
||||
pageSize: 30,
|
||||
});
|
||||
if (res) {
|
||||
if (type === 'branch') {
|
||||
branchOption.value = res.result.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.name,
|
||||
labelEN: v.nameEN,
|
||||
}));
|
||||
} else if (type === 'customer') {
|
||||
customerOption.value = res.result.map((v) => ({
|
||||
value: v.id,
|
||||
label:
|
||||
v.customerType === 'CORP'
|
||||
? v.branch[0].registerName
|
||||
: `${v.branch[0].firstName} ${v.branch[0].lastName}`,
|
||||
labelEN:
|
||||
v.customerType === 'CORP'
|
||||
? v.branch[0].registerNameEN
|
||||
: `${v.branch[0].firstNameEN} ${v.branch[0].lastNameEN}`,
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
(ref: QSelect) => {
|
||||
if (val !== '' && ref.options && ref.options?.length > 0) {
|
||||
ref.setOptionIndex(-1);
|
||||
ref.moveOptionSelection(1, true);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
<div class="col-12 row items-center q-pb-sm text-weight-bold text-body1">
|
||||
<q-icon
|
||||
flat
|
||||
size="xs"
|
||||
class="q-pa-sm rounded q-mr-sm"
|
||||
color="info"
|
||||
name="mdi-file-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
{{ $t(`general.about`) }}
|
||||
<div class="q-ml-md text-weight-regular">
|
||||
<q-checkbox
|
||||
:label="$t('productService.product.agentPrice')"
|
||||
size="xs"
|
||||
v-model="agentPrice"
|
||||
style="font-size: 14px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<SelectInput
|
||||
:readonly
|
||||
incremental
|
||||
v-model="branch"
|
||||
id="quotation-branch"
|
||||
class="col-md col-12"
|
||||
:option="branchOption"
|
||||
:label="$t('quotation.branch')"
|
||||
:option-label="locale === 'eng' ? 'labelEN' : 'label'"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
@filter="(val, update) => filter(val, update, 'branch')"
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
:readonly
|
||||
incremental
|
||||
v-model="customer"
|
||||
class="col-md col-12"
|
||||
id="quotation-customer"
|
||||
:option="customerOption"
|
||||
:label="$t('quotation.customer')"
|
||||
:option-label="locale === 'eng' ? 'labelEN' : 'label'"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
@filter="(val, update) => filter(val, update, 'customer')"
|
||||
>
|
||||
<template #option="{ scope }">
|
||||
<q-item clickable v-if="scope.index === 0">
|
||||
<q-item-section>
|
||||
{{ $t('general.add', { text: $t('quotation.newCustomer') }) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator v-if="scope.index === 0" />
|
||||
|
||||
<q-item clickable v-bind="scope.itemProps">
|
||||
<q-item-section>
|
||||
{{ locale === 'eng' ? scope.opt.labelEN : scope.opt.label }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</SelectInput>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -11,7 +11,7 @@ const dialogState = defineModel('state', { default: true });
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<q-dialog v-model="dialogState">
|
||||
<q-dialog v-model="dialogState" full-width full-height>
|
||||
<AppBox
|
||||
style="
|
||||
padding: 0;
|
||||
|
|
@ -19,10 +19,6 @@ const dialogState = defineModel('state', { default: true });
|
|||
max-height: 100%;
|
||||
flex-wrap: nowrap;
|
||||
"
|
||||
:style="{
|
||||
width: width ?? '100%',
|
||||
maxWidth: maxWidth ?? '98%',
|
||||
}"
|
||||
class="column"
|
||||
>
|
||||
<div class="row items-center q-py-sm q-px-md bordered-b">
|
||||
|
|
@ -43,9 +39,11 @@ const dialogState = defineModel('state', { default: true });
|
|||
class="dialog-body"
|
||||
style="
|
||||
flex: 1;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
|
|
|
|||
155
src/components/05_quotation/QuotationCard.vue
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { formatNumberDecimal } from 'src/stores/utils';
|
||||
import KebabAction from '../shared/KebabAction.vue';
|
||||
|
||||
defineProps<{
|
||||
type?:
|
||||
| 'fullAmountCash'
|
||||
| 'installmentsCash'
|
||||
| 'fullAmountBill'
|
||||
| 'installmentsBill'
|
||||
| string;
|
||||
title?: string;
|
||||
code?: string;
|
||||
amount?: number;
|
||||
date?: string;
|
||||
customerName?: string;
|
||||
reporter?: string;
|
||||
totalPrice?: number;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
(e: 'view'): void;
|
||||
(e: 'edit'): void;
|
||||
(e: 'link'): void;
|
||||
(e: 'upload'): void;
|
||||
(e: 'delete'): void;
|
||||
(e: 'changeStatus'): void;
|
||||
(e: 'example'): void;
|
||||
}>();
|
||||
</script>
|
||||
<template>
|
||||
<div class="surface-1 rounded bordered q-pa-sm quo-card">
|
||||
<!-- SEC: header -->
|
||||
<header class="row items-center no-wrap">
|
||||
<div
|
||||
class="badge-card rounded q-pa-xs"
|
||||
:class="{ [`badge-card__${type}`]: true }"
|
||||
>
|
||||
{{ $t(`quotation.type.${type}`) }}
|
||||
</div>
|
||||
|
||||
<div class="column q-ml-md relative-position" style="font-size: 12px">
|
||||
<span>
|
||||
{{ $t('general.itemNo', { msg: $t('quotation.title') }) }}
|
||||
</span>
|
||||
<span class="text-caption app-text-muted" style="top: 10px">
|
||||
{{ code }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav class="col text-right">
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
rounded
|
||||
icon="mdi-eye-outline"
|
||||
size="12px"
|
||||
@click.stop="$emit('view')"
|
||||
/>
|
||||
<KebabAction
|
||||
:idName="code"
|
||||
status="ACTIVE"
|
||||
use-link
|
||||
use-upload
|
||||
@view="$emit('view')"
|
||||
@edit="$emit('edit')"
|
||||
@link="$emit('link')"
|
||||
@upload="$emit('upload')"
|
||||
@delete="$emit('delete')"
|
||||
@change-status="$emit('changeStatus')"
|
||||
/>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- SEC: body -->
|
||||
<section class="row no-wrap q-py-md">
|
||||
<q-img src="/images/quotation-avatar.png" width="4rem" class="q-mr-lg" />
|
||||
<div class="column">
|
||||
<span class="col q-pt-sm">{{ title || '-' }}</span>
|
||||
<span class="app-text-muted">x {{ amount || '0' }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="col text-right app-text-muted q-mr-md self-end"
|
||||
style="font-size: 12px"
|
||||
>
|
||||
{{ date || '-' }}
|
||||
</div>
|
||||
</section>
|
||||
<q-separator />
|
||||
<section class="row q-py-sm">
|
||||
<div class="col-3 app-text-muted">{{ $t('quotation.customerName') }}</div>
|
||||
<div class="col-9">{{ customerName || '-' }}</div>
|
||||
<div class="col-3 app-text-muted">{{ $t('quotation.actor') }}</div>
|
||||
<div class="col-9">{{ reporter || '-' }}</div>
|
||||
</section>
|
||||
<q-separator />
|
||||
<footer class="row no-wrap items-center q-mt-sm">
|
||||
<Icon
|
||||
class="q-mr-md"
|
||||
icon="ph:money-fill"
|
||||
style="font-size: 24px; color: var(--green-9)"
|
||||
/>
|
||||
{{ $t('quotation.totalPrice') }} :
|
||||
<div class="q-pl-xs" style="color: var(--orange-5)">
|
||||
{{ formatNumberDecimal(totalPrice || 0, 2) }}
|
||||
</div>
|
||||
<q-btn
|
||||
dense
|
||||
outline
|
||||
color="primary"
|
||||
class="rounded q-ml-auto"
|
||||
padding="2px 8px"
|
||||
>
|
||||
<q-icon name="mdi-play-box-outline" size="xs" class="q-mr-xs" />
|
||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||
</q-btn>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.badge-card {
|
||||
font-size: 12px;
|
||||
color: hsla(var(--gray-0-hsl) / 1);
|
||||
background: hsla(var(--_color) / 1);
|
||||
}
|
||||
|
||||
.badge-card__fullAmountCash {
|
||||
--_color: var(--red-6-hsl);
|
||||
}
|
||||
|
||||
.badge-card__installmentsCash {
|
||||
--_color: var(--blue-6-hsl);
|
||||
}
|
||||
|
||||
.badge-card__fullAmountBill {
|
||||
--_color: var(--jungle-8-hsl);
|
||||
}
|
||||
|
||||
.badge-card__installmentsBill {
|
||||
--_color: var(--purple-7-hsl);
|
||||
}
|
||||
|
||||
.dark .badge-card__installmentsCash {
|
||||
--_color: var(--blue-10-hsl);
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
/*new:*/
|
||||
line-height: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -19,13 +19,14 @@ defineProps<{
|
|||
height?: string;
|
||||
employee?: boolean;
|
||||
edit?: boolean;
|
||||
hideDelete?: boolean;
|
||||
|
||||
saveAmount?: number;
|
||||
submitLabel?: string;
|
||||
|
||||
submitIcon?: string;
|
||||
isEdit?: boolean;
|
||||
tabsList?: { name: string; label: string }[];
|
||||
|
||||
hideCloseEvent?: boolean;
|
||||
editData?: (...args: unknown[]) => void;
|
||||
deleteData?: (...args: unknown[]) => void;
|
||||
show?: (...args: unknown[]) => void;
|
||||
|
|
@ -43,7 +44,7 @@ const currentTab = defineModel<string>('currentTab');
|
|||
:model-value="modal"
|
||||
@update:model-value="(v) => (modal = beforeClose ? beforeClose() : v)"
|
||||
@before-show="show"
|
||||
@hide="close"
|
||||
@hide="hideCloseEvent !== undefined && hideCloseEvent ? '' : close"
|
||||
>
|
||||
<div
|
||||
class="surface-1"
|
||||
|
|
@ -86,7 +87,7 @@ const currentTab = defineModel<string>('currentTab');
|
|||
@click="editData"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="edit"
|
||||
v-if="edit && !hideDelete"
|
||||
round
|
||||
flat
|
||||
id="deleteDialog"
|
||||
|
|
@ -217,6 +218,7 @@ const currentTab = defineModel<string>('currentTab');
|
|||
id="btn-form-submit"
|
||||
type="submit"
|
||||
solid
|
||||
:icon="submitIcon"
|
||||
:label="submitLabel"
|
||||
:amount="saveAmount"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ withDefaults(
|
|||
statusBranch?: string;
|
||||
badgeLabel?: string;
|
||||
badgeClass?: string;
|
||||
badgeStyle?: string;
|
||||
bgColor?: string;
|
||||
hideAction?: boolean;
|
||||
editData?: (...args: unknown[]) => void;
|
||||
|
|
@ -25,6 +26,7 @@ withDefaults(
|
|||
close?: (...args: unknown[]) => void;
|
||||
undo?: (...args: unknown[]) => void;
|
||||
beforeClose?: (...args: unknown[]) => boolean;
|
||||
show?: (...args: unknown[]) => void;
|
||||
}>(),
|
||||
{
|
||||
hideAction: false,
|
||||
|
|
@ -48,6 +50,7 @@ function reset() {
|
|||
<template>
|
||||
<q-drawer
|
||||
no-swipe-open
|
||||
@show="show"
|
||||
@before-hide="reset"
|
||||
@hide="close"
|
||||
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
|
||||
|
|
@ -90,29 +93,32 @@ function reset() {
|
|||
v-if="badgeLabel"
|
||||
class="badge-label badge text-caption q-px-sm q-mr-sm"
|
||||
:class="badgeClass"
|
||||
:style="badgeStyle"
|
||||
>
|
||||
{{ badgeLabel }}
|
||||
</text>
|
||||
<text v-if="category" class="app-text-muted q-mr-sm">
|
||||
{{ category }}
|
||||
</text>
|
||||
<span>
|
||||
{{ title }}
|
||||
</span>
|
||||
<slot name="badgeList">
|
||||
<span>
|
||||
{{ title }}
|
||||
</span>
|
||||
|
||||
<text
|
||||
v-if="!!statusBranch"
|
||||
class="branch-badge branch-card__badge q-ml-sm"
|
||||
:class="{
|
||||
'branch-card__inactive ': statusBranch === 'INACTIVE',
|
||||
}"
|
||||
>
|
||||
{{
|
||||
statusBranch === 'INACTIVE'
|
||||
? $t('status.INACTIVE')
|
||||
: $t('status.ACTIVE')
|
||||
}}
|
||||
</text>
|
||||
<text
|
||||
v-if="!!statusBranch"
|
||||
class="branch-badge branch-card__badge q-ml-sm"
|
||||
:class="{
|
||||
'branch-card__inactive ': statusBranch === 'INACTIVE',
|
||||
}"
|
||||
>
|
||||
{{
|
||||
statusBranch === 'INACTIVE'
|
||||
? $t('status.INACTIVE')
|
||||
: $t('status.ACTIVE')
|
||||
}}
|
||||
</text>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div v-if="!hideAction" class="q-mr-md" style="width: 38.8px"></div>
|
||||
|
|
@ -141,7 +147,16 @@ function reset() {
|
|||
|
||||
<!-- footer -->
|
||||
<div class="bordered-t q-pr-lg row items-center justify-end q-py-md">
|
||||
<CancelButton id="btn-info-cancel" outlined @click="close" />
|
||||
<CancelButton
|
||||
id="btn-info-cancel"
|
||||
outlined
|
||||
@click="
|
||||
() => {
|
||||
drawerOpen = beforeClose ? beforeClose() : !drawerOpen;
|
||||
close?.();
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SaveButton
|
||||
class="q-ml-md"
|
||||
id="btn-info-save"
|
||||
|
|
|
|||
136
src/components/ImageHover.vue
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
img?: string | null;
|
||||
icon?: string;
|
||||
title?: string;
|
||||
caption?: string;
|
||||
color?: string;
|
||||
bgColor?: string;
|
||||
toggleTitle?: string;
|
||||
fallbackImg?: string;
|
||||
fallbackCover?: string;
|
||||
|
||||
hideFade?: boolean;
|
||||
hideActive?: boolean;
|
||||
active?: boolean;
|
||||
readonly?: boolean;
|
||||
useToggle?: boolean;
|
||||
labelAction?: string;
|
||||
|
||||
menu?: { icon: string; color: string; bgColor: string }[];
|
||||
tabsList?: { name: string | number; label: string }[];
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
const showOverlay = ref(false);
|
||||
|
||||
defineEmits<{
|
||||
(e: 'view'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="surface-1" style="border: 4px solid var(--surface-1)">
|
||||
<q-avatar
|
||||
square
|
||||
size="10rem"
|
||||
font-size="8rem"
|
||||
class="relative-position"
|
||||
style="z-index: 1; cursor: pointer"
|
||||
@mouseover="showOverlay = true"
|
||||
@mouseleave="showOverlay = false"
|
||||
@click.stop="$emit('view')"
|
||||
>
|
||||
<div
|
||||
v-if="img"
|
||||
class="full-width full-height"
|
||||
:style="{
|
||||
background: `${bgColor || 'var(--brand-1)'}`,
|
||||
color: `${color || 'white'}`,
|
||||
}"
|
||||
>
|
||||
<q-img id="profile-view" :src="img" :ratio="1">
|
||||
<template #error>
|
||||
<q-img
|
||||
v-if="fallbackImg"
|
||||
:src="fallbackImg"
|
||||
:ratio="1"
|
||||
style="background-color: transparent"
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
class="full-width full-height flex items-center justify-center"
|
||||
:style="{
|
||||
background: `${bgColor || 'var(--brand-1)'}`,
|
||||
color: `${color || 'white'}`,
|
||||
}"
|
||||
>
|
||||
<q-icon :name="icon || 'mdi-account'" />
|
||||
</div>
|
||||
</template>
|
||||
</q-img>
|
||||
<div
|
||||
v-else
|
||||
class="full-width full-height flex items-center justify-center"
|
||||
:style="{
|
||||
background: `${bgColor || 'var(--brand-1)'}`,
|
||||
color: `${color || 'white'}`,
|
||||
}"
|
||||
>
|
||||
<q-icon :name="icon || 'mdi-account'" />
|
||||
</div>
|
||||
</template>
|
||||
</q-img>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="full-width full-height flex items-center justify-center"
|
||||
:style="{
|
||||
background: `${bgColor || 'var(--brand-1)'}`,
|
||||
color: `${color || 'white'}`,
|
||||
}"
|
||||
>
|
||||
<q-icon :name="icon || 'mdi-account'" />
|
||||
</div>
|
||||
|
||||
<Transition name="slide-fade">
|
||||
<div
|
||||
v-if="showOverlay && !readonly"
|
||||
class="absolute text-caption full-width full-height"
|
||||
style="overflow: hidden"
|
||||
:class="{ dark: $q.dark.isActive }"
|
||||
>
|
||||
<div
|
||||
class="upload-overlay absolute-bottom flex items-center justify-center"
|
||||
@click.stop="$emit('edit')"
|
||||
>
|
||||
{{
|
||||
labelAction === undefined
|
||||
? $t('general.editImage')
|
||||
: labelAction
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</q-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.upload-overlay {
|
||||
top: 60%;
|
||||
background-color: hsla(var(--gray-10-hsl) / 0.5);
|
||||
color: white;
|
||||
|
||||
&.dark {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,17 +1,23 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import AppBox from './app/AppBox.vue';
|
||||
import { CancelButton, ClearButton, SaveButton } from './button';
|
||||
import { dialog } from 'src/stores/utils';
|
||||
|
||||
defineExpose({ browse });
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
changeDisabled?: boolean;
|
||||
clearButtonDisabled?: boolean;
|
||||
clearButton?: boolean;
|
||||
hiddenFooter?: boolean;
|
||||
defaultUrl?: string;
|
||||
onCreate?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', file: File | null, url: string | null): void;
|
||||
(e: 'addImage', file: File | null): void;
|
||||
(e: 'removeImage', name: string): void;
|
||||
(e: 'submit', name: string): void;
|
||||
}>();
|
||||
|
||||
const imageUrl = defineModel<string>('imageUrl', {
|
||||
|
|
@ -29,7 +35,22 @@ const dialogState = defineModel<boolean>('dialogState', {
|
|||
const file = defineModel<File | null>('file', {
|
||||
required: true,
|
||||
});
|
||||
const dataList = defineModel<{ selectedImage: string; list: string[] }>(
|
||||
'dataList',
|
||||
{
|
||||
required: false,
|
||||
default: { selectedImage: '', list: [] },
|
||||
},
|
||||
);
|
||||
const onCreateData = defineModel<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>('onCreateDataList', {
|
||||
required: false,
|
||||
default: { selectedImage: '', list: [] },
|
||||
});
|
||||
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
|
||||
const reader = new FileReader();
|
||||
const inputFile = (() => {
|
||||
const _element = document.createElement('input');
|
||||
|
|
@ -39,8 +60,26 @@ const inputFile = (() => {
|
|||
return _element;
|
||||
})();
|
||||
|
||||
const selectedImg = ref('');
|
||||
const currentImag = ref('');
|
||||
const tempImage = ref<string | null>('');
|
||||
|
||||
reader.addEventListener('load', () => {
|
||||
if (typeof reader.result === 'string') imageUrl.value = reader.result;
|
||||
if (typeof reader.result === 'string') {
|
||||
tempImage.value = reader.result;
|
||||
if (props.onCreate) {
|
||||
onCreateData.value.list.push({
|
||||
url: reader.result,
|
||||
imgFile: file.value,
|
||||
name: Date.now().toString(),
|
||||
});
|
||||
onCreateData.value.selectedImage =
|
||||
onCreateData.value.list[onCreateData.value.list.length - 1].name;
|
||||
selectedImg.value = reader.result;
|
||||
} else {
|
||||
emit('addImage', file.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function browse() {
|
||||
|
|
@ -55,12 +94,13 @@ function change(e: Event) {
|
|||
file.value = _file;
|
||||
reader.readAsDataURL(_file);
|
||||
if (!dialogState.value) {
|
||||
emit('save', _file, imageUrl.value);
|
||||
emit('save', _file, tempImage.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadImage(url: string) {
|
||||
async function downloadImage(url: string | null) {
|
||||
if (!url) return;
|
||||
const res = await fetch(url);
|
||||
const blob = await res.blob();
|
||||
|
||||
|
|
@ -76,57 +116,234 @@ async function downloadImage(url: string) {
|
|||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
|
||||
function selectImg(name: string) {
|
||||
if (props.onCreate) {
|
||||
selectedImg.value = name;
|
||||
tempImage.value = name;
|
||||
onCreateData.value.selectedImage =
|
||||
onCreateData.value.list.find((v) => v.url === name)?.name || '';
|
||||
} else {
|
||||
selectedImg.value = name.split('/').pop() || '';
|
||||
tempImage.value = `${apiBaseUrl}/${name}`;
|
||||
}
|
||||
}
|
||||
|
||||
function closeCheckToDefault() {
|
||||
let imgNameList: string[];
|
||||
let inList: boolean;
|
||||
|
||||
if (props.onCreate) {
|
||||
imgNameList = onCreateData.value.list.map((v) => v.url || '');
|
||||
} else {
|
||||
imgNameList = dataList.value.list.map((v) => v.split('/').pop() || '');
|
||||
}
|
||||
|
||||
inList = imgNameList.includes(currentImag.value);
|
||||
|
||||
if (!inList && currentImag.value !== '') {
|
||||
selectImg('');
|
||||
emit('submit', selectedImg.value);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dialogState.value,
|
||||
() => {
|
||||
if (dialogState.value) {
|
||||
if (props.onCreate) {
|
||||
tempImage.value = imageUrl.value;
|
||||
selectedImg.value = imageUrl.value;
|
||||
currentImag.value = imageUrl.value;
|
||||
} else {
|
||||
tempImage.value = `${imageUrl.value}?ts=${Date.now()}`;
|
||||
selectedImg.value = dataList.value.selectedImage;
|
||||
currentImag.value = dataList.value.selectedImage;
|
||||
}
|
||||
} else {
|
||||
tempImage.value = '';
|
||||
selectedImg.value = '';
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => dataList.value.list,
|
||||
(n, o) => {
|
||||
if (n.length > o.length) {
|
||||
selectImg(n[n.length - 1]);
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<q-dialog v-model="dialogState">
|
||||
<AppBox class="image-dialog-content">
|
||||
<q-dialog v-model="dialogState" @before-hide="closeCheckToDefault">
|
||||
<AppBox class="image-dialog-content column">
|
||||
<!-- header -->
|
||||
<div class="row items-center q-py-sm q-px-md bordered-b">
|
||||
<div style="width: 38.61px" />
|
||||
<div style="flex: 1"><slot name="title" /></div>
|
||||
<div>
|
||||
<q-btn
|
||||
round
|
||||
flat
|
||||
icon="mdi-close"
|
||||
padding="xs"
|
||||
class="close-btn"
|
||||
:class="{ dark: $q.dark.isActive }"
|
||||
v-close-popup
|
||||
<CancelButton icon-only v-close-popup @click="closeCheckToDefault" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- body -->
|
||||
<div class="q-px-lg surface-2 col row full-width">
|
||||
<div
|
||||
class="image-dialog-body q-my-lg relative-position rounded surface-1 flex items-center"
|
||||
>
|
||||
<img
|
||||
:src="tempImage || fallbackUrl"
|
||||
v-if="tempImage || fallbackUrl"
|
||||
style="object-fit: contain; width: 100%"
|
||||
@error="
|
||||
() => {
|
||||
tempImage = '';
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
style="object-fit: contain; height: 100%; width: 100%"
|
||||
v-if="!tempImage && !fallbackUrl"
|
||||
>
|
||||
<slot name="error"></slot>
|
||||
</div>
|
||||
<div class="absolute-top-left q-pa-md">
|
||||
<q-btn
|
||||
class="upload-image-btn q-mr-md"
|
||||
icon="mdi-camera-plus-outline"
|
||||
id="btn-add-img"
|
||||
size="md"
|
||||
unelevated
|
||||
round
|
||||
v-if="!changeDisabled"
|
||||
@click="
|
||||
() => {
|
||||
inputFile?.click();
|
||||
}
|
||||
"
|
||||
style="color: hsla(var(--stone-0-hsl) / 0.7)"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-if="!onCreate"
|
||||
class="upload-image-btn"
|
||||
icon="mdi-download-outline"
|
||||
id="btn-download-img"
|
||||
size="md"
|
||||
unelevated
|
||||
round
|
||||
@click="downloadImage(tempImage)"
|
||||
style="color: hsla(var(--stone-0-hsl) / 0.7)"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!hiddenFooter"
|
||||
class="col q-ml-md q-pt-sm q-my-md"
|
||||
style="width: 40em; height: 30em; overflow: auto"
|
||||
>
|
||||
<div class="row items-center q-gutter-sm">
|
||||
<div
|
||||
class="rounded surface-1 relative-position"
|
||||
:class="{
|
||||
'selected-img': selectedImg === '',
|
||||
}"
|
||||
style="
|
||||
object-fit: cover;
|
||||
height: 5vw;
|
||||
width: 5vw;
|
||||
overflow: hidden;
|
||||
"
|
||||
@click="selectImg('')"
|
||||
>
|
||||
<slot name="error"></slot>
|
||||
</div>
|
||||
|
||||
<template v-if="dataList">
|
||||
<div
|
||||
v-for="(img, n) in onCreate
|
||||
? onCreateData.list.map((item) => item.url)
|
||||
: dataList.list"
|
||||
:key="n"
|
||||
class="rounded surface-1 relative-position"
|
||||
:class="{
|
||||
'selected-img':
|
||||
selectedImg && onCreate
|
||||
? selectedImg === img
|
||||
: selectedImg === img.split('/').pop(),
|
||||
}"
|
||||
style="height: 5vw; width: 5vw"
|
||||
@click="selectImg(img)"
|
||||
>
|
||||
<q-btn
|
||||
icon="mdi-close"
|
||||
class="absolute-top-right"
|
||||
rounded
|
||||
padding="0"
|
||||
size="sm"
|
||||
style="
|
||||
z-index: 2;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: var(--surface-1);
|
||||
color: hsl(var(--negative-bg));
|
||||
"
|
||||
@click.stop="
|
||||
() => {
|
||||
if (onCreate) {
|
||||
const v = onCreateData.list.splice(n, 1);
|
||||
if (v[0].url === selectedImg) {
|
||||
if (onCreateData.list.length === 0 || n === 0) {
|
||||
selectImg('');
|
||||
} else {
|
||||
selectImg(onCreateData.list[n - 1].url);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: $t('dialog.title.confirmDelete'),
|
||||
actionText: $t('general.delete'),
|
||||
message: $t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
$emit('removeImage', img);
|
||||
const index = dataList.list.indexOf(img);
|
||||
if (img.split('/').pop() === selectedImg) {
|
||||
if (dataList.list.length === 0 || index === 0) {
|
||||
selectImg('');
|
||||
} else {
|
||||
selectImg(dataList.list[index - 1]);
|
||||
}
|
||||
}
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
}
|
||||
"
|
||||
></q-btn>
|
||||
<div
|
||||
class="rounded full-width full-height surface-1"
|
||||
style="overflow: hidden"
|
||||
>
|
||||
<img
|
||||
v-if="img"
|
||||
:src="onCreate ? img : `${apiBaseUrl}/${img}`"
|
||||
class=""
|
||||
style="object-fit: cover; height: 100%; width: 100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-dialog-body">
|
||||
<img
|
||||
:src="imageUrl || fallbackUrl"
|
||||
v-if="imageUrl || fallbackUrl"
|
||||
class="image-container"
|
||||
style="object-fit: contain"
|
||||
@error="imageUrl = ''"
|
||||
/>
|
||||
<div class="image-container" v-if="!imageUrl && !fallbackUrl">
|
||||
<slot name="error"></slot>
|
||||
</div>
|
||||
<div style="position: fixed; padding: var(--size-2)">
|
||||
<q-btn
|
||||
class="upload-image-btn q-mr-md"
|
||||
icon="mdi-camera-outline"
|
||||
size="md"
|
||||
unelevated
|
||||
round
|
||||
v-if="!changeDisabled"
|
||||
@click="inputFile?.click()"
|
||||
style="color: hsla(var(--stone-0-hsl) / 0.7)"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
class="upload-image-btn"
|
||||
icon="mdi-download-outline"
|
||||
size="md"
|
||||
unelevated
|
||||
round
|
||||
@click="downloadImage(imageUrl)"
|
||||
style="color: hsla(var(--stone-0-hsl) / 0.7)"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- footer -->
|
||||
<div
|
||||
class="row items-center justify-end q-py-sm q-px-md bordered-t"
|
||||
v-if="!hiddenFooter"
|
||||
|
|
@ -147,20 +364,25 @@ async function downloadImage(url: string) {
|
|||
"
|
||||
v-if="clearButton"
|
||||
/>
|
||||
<CancelButton
|
||||
<!-- <CancelButton
|
||||
id="btn-cancel-img"
|
||||
outlined
|
||||
class="q-px-md q-mr-sm"
|
||||
@click="
|
||||
inputFile && (inputFile.value = ''),
|
||||
(imageUrl = defaultUrl || fallbackUrl || ''),
|
||||
(tempImage = defaultUrl || fallbackUrl || ''),
|
||||
(file = null)
|
||||
"
|
||||
v-close-popup
|
||||
/>
|
||||
/> -->
|
||||
<SaveButton
|
||||
id="btn-save-img"
|
||||
outlined
|
||||
@click="$emit('save', inputFile?.files?.[0] || null, imageUrl)"
|
||||
:disabled="currentImag === selectedImg"
|
||||
:label="$t('general.apply')"
|
||||
@click="$emit('submit', selectedImg)"
|
||||
/>
|
||||
<!-- @click="$emit('save', inputFile?.files?.[0] || null, tempImage)" -->
|
||||
</div>
|
||||
</AppBox>
|
||||
</q-dialog>
|
||||
|
|
@ -171,24 +393,24 @@ async function downloadImage(url: string) {
|
|||
padding: 0;
|
||||
border-radius: var(--radius-2);
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.image-dialog-content {
|
||||
max-width: 60%;
|
||||
max-width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.image-dialog-body {
|
||||
overflow-y: auto;
|
||||
background-color: var(--surface-2) !important;
|
||||
/* overflow-y: auto;
|
||||
background-color: var(--surface-1) !important;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: calc(100vh - 200px);
|
||||
flex: 1; */
|
||||
overflow: hidden;
|
||||
width: 30em;
|
||||
height: 30em;
|
||||
}
|
||||
|
||||
.upload-image-btn {
|
||||
|
|
@ -227,4 +449,21 @@ async function downloadImage(url: string) {
|
|||
border: 1px solid hsl(var(--negative-bg));
|
||||
}
|
||||
}
|
||||
|
||||
.selected-img {
|
||||
position: relative;
|
||||
border: 2px solid hsl(var(--info-bg));
|
||||
}
|
||||
|
||||
.selected-img:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
border: 2px solid var(--surface-1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,51 @@
|
|||
<script setup lang="ts">
|
||||
import AppBox from './app/AppBox.vue';
|
||||
import { ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
defineProps<{
|
||||
import { onMounted } from 'vue';
|
||||
const props = defineProps<{
|
||||
icon: string;
|
||||
text: string;
|
||||
color: string;
|
||||
iconColor: string;
|
||||
bgColor: string;
|
||||
changeColor?: boolean;
|
||||
index?: number;
|
||||
}>();
|
||||
|
||||
const _self = ref<InstanceType<typeof HTMLDivElement>>();
|
||||
|
||||
onMounted(() => {
|
||||
if (props.index === 0) {
|
||||
_self.value?.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppBox
|
||||
style="
|
||||
padding: 0;
|
||||
border-radius: var(--radius-2);
|
||||
width: 320px;
|
||||
height: 195px;
|
||||
"
|
||||
bordered
|
||||
class="cursor-pointer"
|
||||
<button
|
||||
ref="_self"
|
||||
class="wrapper surface-1 shadow-2"
|
||||
type="submit"
|
||||
@click="$emit('trigger')"
|
||||
style="padding: 0; border-radius: var(--radius-2); width: 320px"
|
||||
>
|
||||
<div class="column" style="height: 100%">
|
||||
<div class="col-9 flex justify-center items-center">
|
||||
<Icon
|
||||
:icon="icon"
|
||||
width="64px"
|
||||
:class="`${$q.dark.isActive ? '' : 'app-text-muted'}`"
|
||||
:color="changeColor ? color : ''"
|
||||
/>
|
||||
<div class="column justify-center" style="height: 100%">
|
||||
<div class="col-6 flex justify-center items-center">
|
||||
<div
|
||||
class="q-pa-md row items-center"
|
||||
:style="`border-radius: 50%; background-color: hsla(${bgColor} / 0.1)`"
|
||||
>
|
||||
<Icon :icon="icon" width="53px" :color="`var(${iconColor})`" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-3 flex text-bold text-h6 text-center justify-center items-center variable-item-card"
|
||||
class="col-2 flex text-bold text-h6 text-center justify-center items-center variable-item-card"
|
||||
:class="{ dark: $q.dark.isActive }"
|
||||
:style="`background-color: ${color}`"
|
||||
:style="`background-color: ${bgColor}`"
|
||||
>
|
||||
<div style="color: white">{{ $t(text) }}</div>
|
||||
<div style="font-size: 14px">{{ $t(text) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppBox>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -59,4 +66,15 @@ defineProps<{
|
|||
--_var-filter: color;
|
||||
}
|
||||
}
|
||||
.wrapper {
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.wrapper:focus {
|
||||
border: 2px solid var(--blue-6);
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ const showOverlay = ref(false);
|
|||
size="6rem"
|
||||
font-size="3rem"
|
||||
class="relative-position"
|
||||
style="z-index: 1"
|
||||
style="z-index: 1; box-shadow: var(--shadow-2)"
|
||||
:style="{
|
||||
color: `${color || 'white'}`,
|
||||
cursor: `${noImageAction ? 'default' : 'pointer'}`,
|
||||
|
|
|
|||
231
src/components/QrCodeUploadDialog.vue
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
<script lang="ts" setup>
|
||||
import AppBox from './app/AppBox.vue';
|
||||
import { CancelButton, ClearButton, SaveButton } from './button';
|
||||
|
||||
defineExpose({ browse });
|
||||
defineProps<{
|
||||
changeDisabled?: boolean;
|
||||
clearButtonDisabled?: boolean;
|
||||
clearButton?: boolean;
|
||||
hiddenFooter?: boolean;
|
||||
defaultUrl?: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', file: File | null, url: string | null): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
const imageUrl = defineModel<string>('imageUrl', {
|
||||
required: false,
|
||||
default: '',
|
||||
});
|
||||
const fallbackUrl = defineModel<string>('fallbackUrl', {
|
||||
required: false,
|
||||
default: '',
|
||||
});
|
||||
const dialogState = defineModel<boolean>('dialogState', {
|
||||
required: false,
|
||||
default: true,
|
||||
});
|
||||
const file = defineModel<File | null>('file', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const reader = new FileReader();
|
||||
const inputFile = (() => {
|
||||
const _element = document.createElement('input');
|
||||
_element.type = 'file';
|
||||
_element.accept = 'image/*';
|
||||
_element.addEventListener('change', change);
|
||||
return _element;
|
||||
})();
|
||||
|
||||
reader.addEventListener('load', () => {
|
||||
if (typeof reader.result === 'string') {
|
||||
imageUrl.value = reader.result;
|
||||
if (!dialogState.value) emit('save', file.value, imageUrl.value);
|
||||
}
|
||||
});
|
||||
|
||||
function browse() {
|
||||
inputFile?.click();
|
||||
}
|
||||
|
||||
function change(e: Event) {
|
||||
const _element = e.target as HTMLInputElement | null;
|
||||
const _file = _element?.files?.[0];
|
||||
if (_file) {
|
||||
file.value = _file;
|
||||
reader.readAsDataURL(_file);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadImage(url: string) {
|
||||
const res = await fetch(url);
|
||||
const blob = await res.blob();
|
||||
|
||||
let extension = '';
|
||||
|
||||
if (blob.type === 'image/jpeg') extension = '.jpg';
|
||||
else if (blob.type === 'image/png') extension = '.png';
|
||||
else return;
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.download = `download${extension}`;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-dialog v-model="dialogState">
|
||||
<AppBox class="image-dialog-content">
|
||||
<div class="row items-center q-py-sm q-px-md bordered-b">
|
||||
<div style="flex: 1"><slot name="title" /></div>
|
||||
<div>
|
||||
<q-btn
|
||||
round
|
||||
flat
|
||||
icon="mdi-close"
|
||||
padding="xs"
|
||||
class="close-btn"
|
||||
:class="{ dark: $q.dark.isActive }"
|
||||
v-close-popup
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-dialog-body">
|
||||
<img
|
||||
:src="imageUrl || fallbackUrl"
|
||||
v-if="imageUrl || fallbackUrl"
|
||||
class="image-container"
|
||||
style="object-fit: contain"
|
||||
@error="imageUrl = ''"
|
||||
/>
|
||||
<div class="image-container" v-if="!imageUrl && !fallbackUrl">
|
||||
<slot name="error"></slot>
|
||||
</div>
|
||||
<div style="position: fixed; padding: var(--size-2)">
|
||||
<q-btn
|
||||
class="upload-image-btn q-mr-md"
|
||||
icon="mdi-camera-outline"
|
||||
size="md"
|
||||
unelevated
|
||||
round
|
||||
v-if="!changeDisabled"
|
||||
@click="inputFile?.click()"
|
||||
style="color: hsla(var(--stone-0-hsl) / 0.7)"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
class="upload-image-btn"
|
||||
icon="mdi-download-outline"
|
||||
size="md"
|
||||
unelevated
|
||||
round
|
||||
@click="downloadImage(imageUrl)"
|
||||
style="color: hsla(var(--stone-0-hsl) / 0.7)"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row items-center justify-end q-py-sm q-px-md bordered-t"
|
||||
v-if="!hiddenFooter"
|
||||
>
|
||||
<ClearButton
|
||||
outlined
|
||||
@click="
|
||||
inputFile && (inputFile.value = ''),
|
||||
(imageUrl = defaultUrl || fallbackUrl || ''),
|
||||
(file = null),
|
||||
$emit('clear')
|
||||
"
|
||||
class="q-px-md q-mr-auto"
|
||||
:disabled="
|
||||
clearButtonDisabled ||
|
||||
imageUrl === fallbackUrl ||
|
||||
imageUrl === defaultUrl ||
|
||||
!imageUrl
|
||||
"
|
||||
v-if="clearButton"
|
||||
/>
|
||||
<CancelButton
|
||||
outlined
|
||||
class="q-px-md q-mr-sm"
|
||||
@click="
|
||||
inputFile && (inputFile.value = ''),
|
||||
(imageUrl = defaultUrl || fallbackUrl || ''),
|
||||
(file = null)
|
||||
"
|
||||
v-close-popup
|
||||
/>
|
||||
<SaveButton
|
||||
outlined
|
||||
@click="$emit('save', inputFile?.files?.[0] || null, imageUrl)"
|
||||
/>
|
||||
</div>
|
||||
</AppBox>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.image-dialog-content {
|
||||
padding: 0;
|
||||
border-radius: var(--radius-2);
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.image-dialog-content {
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.image-dialog-body {
|
||||
overflow-y: auto;
|
||||
background-color: var(--surface-2) !important;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.upload-image-btn {
|
||||
transition: 0.3s background-color ease-in-out;
|
||||
background-color: hsla(0 0% 0% / 0.2);
|
||||
|
||||
backdrop-filter: blur(1px);
|
||||
|
||||
&:not(:hover) {
|
||||
color: hsla(0 0% 40% / 1);
|
||||
background-color: hsla(0 0% 0% / 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.image-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 200px);
|
||||
min-height: 480px;
|
||||
|
||||
& > :deep(*) {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.image-container > :deep(*:not(:first-child)) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
color: hsl(var(--negative-bg));
|
||||
background-color: hsla(var(--negative-bg) / 0.1);
|
||||
|
||||
&.dark {
|
||||
background-color: transparent;
|
||||
border: 1px solid hsl(var(--negative-bg));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
59
src/components/RemarkComponent.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
const remark = defineModel<string>('remark', { default: '' });
|
||||
|
||||
defineProps<{
|
||||
readonly: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-12 row">
|
||||
<div class="col-12 q-mb-md text-weight-bold text-body1">
|
||||
<q-icon
|
||||
flat
|
||||
size="xs"
|
||||
class="q-pa-sm rounded q-mr-xs"
|
||||
color="info"
|
||||
name="mdi-asterisk-circle-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
{{ $t('general.remark') }}
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<q-field
|
||||
class="full-width"
|
||||
outlined
|
||||
for="input-detail"
|
||||
id="input-detail"
|
||||
:readonly="readonly"
|
||||
:borderless="readonly"
|
||||
:label="$t('general.remark')"
|
||||
stack-label
|
||||
dense
|
||||
>
|
||||
<q-editor
|
||||
dense
|
||||
:model-value="readonly ? remark || '-' : remark"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (remark = v) : '')
|
||||
"
|
||||
min-height="5rem"
|
||||
class="q-mt-sm q-mb-xs"
|
||||
:flat="!readonly"
|
||||
:readonly="readonly"
|
||||
:toolbar-color="
|
||||
readonly ? 'disabled' : $q.dark.isActive ? 'white' : ''
|
||||
"
|
||||
:toolbar-toggle-color="readonly ? 'disabled' : 'primary'"
|
||||
style="
|
||||
cursor: auto;
|
||||
color: var(--foreground);
|
||||
border-color: var(--surface-3);
|
||||
"
|
||||
:style="`width: ${$q.screen.gt.xs ? '100%' : '63vw'}`"
|
||||
/>
|
||||
</q-field>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
84
src/components/ShowAttachent.vue
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { VuePDF, usePDF } from '@tato30/vue-pdf';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ url: string; file: File | undefined }>(),
|
||||
{},
|
||||
);
|
||||
|
||||
const scale = ref(1);
|
||||
const page = ref(1);
|
||||
const { pdf, pages } = usePDF(computed(() => props.url));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col full-height column no-wrap">
|
||||
<div
|
||||
class="surface-0 bordered row items-center justify-evenly q-pa-sm no-wrap"
|
||||
style="height: 50px"
|
||||
>
|
||||
<q-btn
|
||||
@click="page = page > 1 ? page - 1 : page"
|
||||
class="btn-next"
|
||||
icon="mdi-chevron-left"
|
||||
unelevated
|
||||
dense
|
||||
id="btn-prev-page-top"
|
||||
/>
|
||||
|
||||
<div class="ellipsis">Page {{ page }} of {{ pages }}</div>
|
||||
|
||||
<q-btn
|
||||
@click="scale = scale > 0.25 ? scale - 0.25 : scale"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
size="12px"
|
||||
icon="mdi-magnify-minus-outline"
|
||||
class="app-text-dark"
|
||||
>
|
||||
<q-tooltip>{{ $t('zoomOut') }}</q-tooltip>
|
||||
</q-btn>
|
||||
<div>{{ scale * 100 }}%</div>
|
||||
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
round
|
||||
size="12px"
|
||||
class="app-text-dark"
|
||||
icon="mdi-magnify-plus-outline"
|
||||
@click="scale = scale < 2 ? scale + 0.25 : scale"
|
||||
>
|
||||
<q-tooltip>{{ $t('general.zoomIn') }}</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
@click="page = page < pages ? page + 1 : page"
|
||||
class="btn-next"
|
||||
icon="mdi-chevron-right"
|
||||
unelevated
|
||||
dense
|
||||
id="btn-prev-page-top"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-center surface-2 bordered-l bordered-r bordered-b full-height scroll"
|
||||
>
|
||||
<VuePDF
|
||||
v-if="
|
||||
url?.split('?').at(0)?.endsWith('.pdf') ||
|
||||
file?.type === 'application/pdf'
|
||||
"
|
||||
class="q-py-md"
|
||||
:pdf="pdf"
|
||||
:page="page"
|
||||
:scale="scale"
|
||||
/>
|
||||
|
||||
<q-img v-else class="q-py-md full-width" :src="url" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -9,6 +9,7 @@ type Menu = {
|
|||
name: string;
|
||||
sub?: boolean;
|
||||
tab?: string;
|
||||
useBtn?: boolean;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -86,14 +87,27 @@ onUnmounted(() => {
|
|||
'--side-menu__fg-active': active?.foreground,
|
||||
'--side-menu__bg-active': active?.background,
|
||||
}"
|
||||
class="side-menu__item"
|
||||
class="side-menu__item row items-center justify-between no-wrap"
|
||||
:class="{
|
||||
'side-menu__active': activeMenu === v.anchor,
|
||||
'side-menu__sub': v.sub || false,
|
||||
}"
|
||||
@click="handleClick(v)"
|
||||
>
|
||||
{{ v.name }}
|
||||
<span
|
||||
class="row no-wrap items-center"
|
||||
:class="{ 'app-text-muted': v.sub && activeMenu !== v.anchor }"
|
||||
>
|
||||
<div v-if="v.sub" class="circle-2"></div>
|
||||
<div
|
||||
v-if="v.sub"
|
||||
class="surface-tab circle flex justify-center q-mx-md"
|
||||
>
|
||||
{{ menu.filter((v) => v.sub === true).indexOf(v) + 1 }}
|
||||
</div>
|
||||
{{ v.name }}
|
||||
</span>
|
||||
<slot v-if="v.useBtn" :name="`btn-${v.anchor}`"></slot>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
|
@ -115,7 +129,7 @@ onUnmounted(() => {
|
|||
cursor: pointer;
|
||||
|
||||
&.side-menu__sub {
|
||||
margin-left: 1rem;
|
||||
/* margin-left: 1rem; */
|
||||
}
|
||||
|
||||
&.side-menu__active {
|
||||
|
|
@ -125,4 +139,18 @@ onUnmounted(() => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
font-size: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.circle-2 {
|
||||
background: var(--surface-tab);
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -13,13 +13,18 @@ const props = withDefaults(
|
|||
| 'cyan'
|
||||
| 'yellow'
|
||||
| 'red'
|
||||
| 'magenta';
|
||||
| 'magenta'
|
||||
| 'blue'
|
||||
| 'lime'
|
||||
| 'light-purple';
|
||||
}[];
|
||||
dark?: boolean;
|
||||
textSize?: string;
|
||||
labelI18n?: boolean;
|
||||
}>(),
|
||||
{
|
||||
labelI18n: false,
|
||||
textSize: '12px',
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
|
@ -42,8 +47,13 @@ const props = withDefaults(
|
|||
/>
|
||||
</div>
|
||||
<div class="col-6 justify-center column">
|
||||
<div class="col-6 ellipsis text-bold" style="width: 100%">
|
||||
{{ labelI18n ? $t(v.label) : v.label }}
|
||||
<div
|
||||
class="col-6 ellipsis text-bold text-caption"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span :style="`font-size: ${textSize}`">
|
||||
{{ labelI18n ? $t(v.label) : v.label }}
|
||||
</span>
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
|
|
@ -107,6 +117,18 @@ const props = withDefaults(
|
|||
--_color: var(--pink-8-hsl);
|
||||
}
|
||||
|
||||
.stat-card__lime {
|
||||
--_color: var(--jungle-8-hsl);
|
||||
}
|
||||
|
||||
.stat-card__light-purple {
|
||||
--_color: var(--purple-7-hsl);
|
||||
}
|
||||
|
||||
.stat-card__blue {
|
||||
--_color: var(--blue-6-hsl);
|
||||
}
|
||||
|
||||
.dark .stat-card__purple {
|
||||
--_color: var(--violet-10-hsl);
|
||||
}
|
||||
|
|
@ -122,4 +144,8 @@ const props = withDefaults(
|
|||
.dark .stat-card__magenta {
|
||||
--_color: var(--pink-7-hsl);
|
||||
}
|
||||
|
||||
.dark .stat-card__blue {
|
||||
--_color: var(--blue-10-hsl);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
125
src/components/TableComponents.vue
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<script setup lang="ts">
|
||||
import { QTableProps } from 'quasar';
|
||||
import KebabAction from 'components/shared/KebabAction.vue';
|
||||
import DeleteButton from './button/DeleteButton.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
rows: QTableProps['rows'];
|
||||
columns: QTableProps['columns'];
|
||||
flat?: boolean;
|
||||
bordered?: boolean;
|
||||
grid?: boolean;
|
||||
hideHeader?: boolean;
|
||||
buttomDownload?: boolean;
|
||||
buttonDelete?: boolean;
|
||||
hidePagination?: boolean;
|
||||
|
||||
imgColumn?: string;
|
||||
}>(),
|
||||
{
|
||||
row: () => [],
|
||||
column: () => [],
|
||||
flat: false,
|
||||
bordered: false,
|
||||
grid: false,
|
||||
hideHeader: false,
|
||||
buttomDownload: false,
|
||||
imgColumn: '',
|
||||
},
|
||||
);
|
||||
|
||||
defineEmits<{
|
||||
(e: 'view', index: number): void;
|
||||
(e: 'edit', index: number): void;
|
||||
(e: 'delete', index: number): void;
|
||||
(e: 'toggleStatus', row: typeof props.rows): void;
|
||||
(e: 'download', index: number): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-table
|
||||
v-bind="props"
|
||||
class="full-height"
|
||||
:no-data-label="$t('general.noDataTable')"
|
||||
:hide-pagination
|
||||
>
|
||||
<slot name="zxc"></slot>
|
||||
|
||||
<template v-slot:header="props">
|
||||
<q-tr
|
||||
style="background-color: hsla(var(--info-bg) / 0.07)"
|
||||
:props="props"
|
||||
>
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span v-if="col.label === 'nameEmployee'">
|
||||
{{ $t('fullname') }}
|
||||
</span>
|
||||
|
||||
<span v-if="col.label !== ''">
|
||||
{{ $t(col.label) }}
|
||||
</span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-order="props">
|
||||
<q-td class="text-center">
|
||||
{{ props.rowIndex + 1 }}
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template v-slot:[`body-cell-${imgColumn}`]="props">
|
||||
<q-td>
|
||||
<slot name="img-column" :props="props"></slot>
|
||||
{{ props.row[imgColumn] }}
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-action="props">
|
||||
<q-td class="text-center">
|
||||
<DeleteButton iconOnly v-if="buttonDelete" />
|
||||
|
||||
<q-btn
|
||||
v-if="!buttonDelete"
|
||||
icon="mdi-eye-outline"
|
||||
size="sm"
|
||||
dense
|
||||
round
|
||||
flat
|
||||
@click.stop="$emit('view', props.rowIndex)"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
v-if="buttomDownload && props.row.id !== undefined"
|
||||
icon="mdi-download-outline"
|
||||
size="sm"
|
||||
dense
|
||||
round
|
||||
flat
|
||||
@click.stop="$emit('download', props.rowIndex)"
|
||||
/>
|
||||
|
||||
<KebabAction
|
||||
v-if="!buttonDelete"
|
||||
hide-toggle
|
||||
:id-name="props.row.code"
|
||||
:status="props.row.status"
|
||||
@view="$emit('view', props.rowIndex)"
|
||||
@edit="$emit('edit', props.rowIndex)"
|
||||
@delete="$emit('delete', props.rowIndex)"
|
||||
@change-status="$emit('toggleStatus', props.row)"
|
||||
/>
|
||||
</q-td>
|
||||
|
||||
<slot name="dialog" :index="props.rowIndex" :row="props.row"></slot>
|
||||
</template>
|
||||
</q-table>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(i.q-icon.mdi.mdi-alert.q-table__bottom-nodata-icon) {
|
||||
color: #ffc224 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -43,6 +43,7 @@ defineEmits<{
|
|||
:node-key="nodeKey"
|
||||
:label-key="labelKey"
|
||||
:children-key="childrenKey"
|
||||
:no-nodes-label="$t('general.noData')"
|
||||
v-model:expanded="expandedTree"
|
||||
style="color: var(--foreground)"
|
||||
>
|
||||
|
|
@ -51,7 +52,10 @@ defineEmits<{
|
|||
class="full-width q-py-xs"
|
||||
:class="{
|
||||
'clickable-node': typeTree === 'product' || node.isHeadOffice,
|
||||
'cursor-pointer': node.type === 'group' || node.type === 'type',
|
||||
'cursor-pointer':
|
||||
node.type === 'group' ||
|
||||
node.type === 'type' ||
|
||||
node.type === 'productService',
|
||||
'active-node': expandedTree[expandedTree.length - 1] === node.id,
|
||||
}"
|
||||
v-touch-hold.mouse="() => $emit('handleHold', node)"
|
||||
|
|
@ -60,12 +64,12 @@ defineEmits<{
|
|||
$emit('select', node);
|
||||
}
|
||||
"
|
||||
:id="`tree-enter-${node.name}`"
|
||||
:id="`tree-enter-${node.name}${node.for ? `-${node.for}` : ''}`"
|
||||
>
|
||||
<div class="row col items-center justify-between full-width no-wrap">
|
||||
<q-icon
|
||||
v-if="
|
||||
(node.type === 'group' && node._count.type > 0) ||
|
||||
node.type === 'group' ||
|
||||
(node.isHeadOffice && node._count.branch !== 0)
|
||||
"
|
||||
name="mdi-triangle-down"
|
||||
|
|
@ -75,7 +79,10 @@ defineEmits<{
|
|||
/>
|
||||
<div
|
||||
class="col row"
|
||||
:class="{ 'q-pl-sm': node.type === 'type' }"
|
||||
:class="{
|
||||
'q-pl-sm q-py-xs':
|
||||
node.type === 'type' || node.type === 'productService',
|
||||
}"
|
||||
:style="`padding-left:${(node.type === 'group' && node._count.type === 0) || (node.isHeadOffice && node._count.branch === 0) ? '36px' : ''}`"
|
||||
>
|
||||
<span
|
||||
|
|
@ -101,7 +108,7 @@ defineEmits<{
|
|||
>
|
||||
<slot></slot>
|
||||
<q-btn
|
||||
v-if="typeTree === 'product'"
|
||||
v-if="typeTree === 'product' && !node.actionDisabled"
|
||||
icon="mdi-eye-outline"
|
||||
:id="`btn-tree-eye-${node.name}`"
|
||||
size="sm"
|
||||
|
|
@ -125,8 +132,9 @@ defineEmits<{
|
|||
|
||||
<KebabAction
|
||||
v-if="action && !node.actionDisabled"
|
||||
:disable-delete="node.status !== 'CREATED'"
|
||||
:id-name="node.name"
|
||||
status="ACTIVE"
|
||||
:status="node.status"
|
||||
@view="$emit('view', node)"
|
||||
@edit="$emit('edit', node)"
|
||||
@delete="$emit('delete', node)"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ defineProps<{
|
|||
dark?: boolean;
|
||||
|
||||
label?: string;
|
||||
icon?: string;
|
||||
|
||||
amount?: number;
|
||||
}>();
|
||||
|
|
@ -21,7 +22,7 @@ defineProps<{
|
|||
<MainButton
|
||||
@click="(e) => $emit('click', e)"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
icon="mdi-content-save-outline"
|
||||
:icon="icon || 'mdi-content-save-outline'"
|
||||
color="207 96% 32%"
|
||||
:title="iconOnly ? $t('general.save') : undefined"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ input:checked + .slider {
|
|||
background-color: hsl(var(--positive-bg));
|
||||
|
||||
&.disable {
|
||||
background-color: var(--stone-4);
|
||||
// background-color: var(--stone-4);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ export { default as UndoButton } from './UndoButton.vue';
|
|||
export { default as ToggleButton } from './ToggleButton.vue';
|
||||
export { default as ClearButton } from './ClearButton.vue';
|
||||
export { default as CloseButton } from './CloseButton.vue';
|
||||
export { default as ViewButton } from './viewButton.vue';
|
||||
|
|
|
|||
27
src/components/button/viewButton.vue
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<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;
|
||||
size?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainButton
|
||||
@click="(e) => $emit('click', e)"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
icon="mdi-eye-outline"
|
||||
color="var(--gray-8-hsl)"
|
||||
:title="iconOnly ? $t('general.viewDetail') : undefined"
|
||||
>
|
||||
{{ $t('general.viewDetail') }}
|
||||
</MainButton>
|
||||
</template>
|
||||
|
|
@ -15,7 +15,7 @@ defineProps<{
|
|||
<slot name="title-after" />
|
||||
</div>
|
||||
<slot name="after">
|
||||
<CancelButton icon-only v-close-popup />
|
||||
<CancelButton id="btn-form-close" icon-only v-close-popup />
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, watch, reactive, ref } from 'vue';
|
||||
import { onMounted, watch, reactive, ref, computed } from 'vue';
|
||||
import useAddressStore, {
|
||||
District,
|
||||
Province,
|
||||
|
|
@ -7,6 +7,7 @@ import useAddressStore, {
|
|||
} from 'stores/address';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { QSelect } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
|
|
@ -21,7 +22,10 @@ defineProps<{
|
|||
indexId?: number;
|
||||
prefixId: string;
|
||||
hideTitle?: boolean;
|
||||
hideInputEn?: boolean;
|
||||
hideIcon?: boolean;
|
||||
|
||||
useEmployment?: boolean;
|
||||
useWorkPlace?: boolean;
|
||||
}>();
|
||||
|
||||
|
|
@ -30,12 +34,26 @@ const workplace = defineModel<string>('workplace', { default: '' });
|
|||
const workplaceEN = defineModel<string>('workplaceEn', { default: '' });
|
||||
const address = defineModel('address', { default: '' });
|
||||
const addressEN = defineModel('addressEN', { default: '' });
|
||||
const street = defineModel('street', { default: '' });
|
||||
const streetEN = defineModel('streetEN', { default: '' });
|
||||
const moo = defineModel('moo', { default: '' });
|
||||
const mooEN = defineModel('mooEN', { default: '' });
|
||||
const soi = defineModel('soi', { default: '' });
|
||||
const soiEN = defineModel('soiEN', { default: '' });
|
||||
const provinceId = defineModel<string | null | undefined>('provinceId');
|
||||
const districtId = defineModel<string | null | undefined>('districtId');
|
||||
const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
|
||||
const zipCode = defineModel<string | null | undefined>('zipCode');
|
||||
const sameWithEmployer = defineModel<boolean>('sameWithEmployer');
|
||||
|
||||
const homeCode = defineModel<string | null | undefined>('homeCode');
|
||||
const employmentOffice = defineModel<string | null | undefined>(
|
||||
'employmentOffice',
|
||||
);
|
||||
const employmentOfficeEN = defineModel<string | null | undefined>(
|
||||
'employmentOfficeEN',
|
||||
);
|
||||
|
||||
const addrOptions = reactive<{
|
||||
provinceOps: Province[];
|
||||
districtOps: District[];
|
||||
|
|
@ -46,6 +64,80 @@ const addrOptions = reactive<{
|
|||
subDistrictOps: [],
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const fullAddress = computed(() => {
|
||||
const addressParts = [`${address.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 (moo.value) addressParts.push(`${t('form.moo')} ${moo.value},`);
|
||||
if (soi.value) addressParts.push(`${t('form.soi')} ${soi.value},`);
|
||||
if (street.value) addressParts.push(`${t('form.road')} ${street.value},`);
|
||||
|
||||
if (subDistrictId.value && sDistrict) {
|
||||
addressParts.push(
|
||||
typeof sDistrict.name === 'string' ? `${sDistrict.name},` : '',
|
||||
);
|
||||
}
|
||||
|
||||
if (districtId.value && district)
|
||||
addressParts.push(
|
||||
typeof district.name === 'string' ? `${district.name},` : '',
|
||||
);
|
||||
|
||||
if (provinceId.value && province) {
|
||||
addressParts.push(
|
||||
typeof province.name === 'string' ? `${province.name}` : '',
|
||||
);
|
||||
sDistrict &&
|
||||
addressParts.push(
|
||||
typeof sDistrict.zipCode === 'string' ? `${sDistrict.zipCode}` : '',
|
||||
);
|
||||
}
|
||||
|
||||
return addressParts.join(' ');
|
||||
});
|
||||
|
||||
const fullAddressEN = computed(() => {
|
||||
const addressParts = [`${addressEN.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 (mooEN.value) addressParts.push(`Moo ${mooEN.value},`);
|
||||
if (soiEN.value) addressParts.push(`Soi ${soiEN.value},`);
|
||||
if (streetEN.value) addressParts.push(`${streetEN.value} Rd.`);
|
||||
|
||||
if (subDistrictId.value && sDistrict) {
|
||||
addressParts.push(
|
||||
typeof sDistrict.nameEN === 'string' ? `${sDistrict.nameEN},` : '',
|
||||
);
|
||||
}
|
||||
|
||||
if (districtId.value && district)
|
||||
addressParts.push(
|
||||
typeof district.nameEN === 'string' ? `${district.nameEN},` : '',
|
||||
);
|
||||
|
||||
if (provinceId.value && province) {
|
||||
addressParts.push(
|
||||
typeof province.nameEN === 'string' ? `${province.nameEN}` : '',
|
||||
);
|
||||
sDistrict &&
|
||||
addressParts.push(
|
||||
typeof sDistrict.zipCode === 'string' ? `${sDistrict.zipCode}` : '',
|
||||
);
|
||||
}
|
||||
|
||||
return addressParts.join(' ');
|
||||
});
|
||||
|
||||
async function fetchProvince() {
|
||||
const result = await adrressStore.fetchProvince();
|
||||
|
||||
|
|
@ -199,10 +291,51 @@ watch(districtId, fetchSubDistrict);
|
|||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-y-md">
|
||||
<div class="col-12 app-text-muted-2">
|
||||
<q-icon size="xs" class="q-mr-xs" name="mdi-map-marker-outline" />
|
||||
{{ addressTitle || $t('form.address') }}
|
||||
<div v-if="useEmployment" class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-3"
|
||||
v-model="homeCode"
|
||||
mask="###########"
|
||||
:dense="dense"
|
||||
:label="$t('customer.form.homeCode')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-address-${indexId}` : 'input-address'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
v-model="employmentOffice"
|
||||
:dense="dense"
|
||||
:label="$t('customer.form.employmentOffice')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-address-${indexId}` : 'input-address'}`"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
v-model="employmentOfficeEN"
|
||||
:dense="dense"
|
||||
:label="`${$t('customer.form.employmentOffice')} (EN)`"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-address-${indexId}` : 'input-address'}`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template v-if="!hideIcon">
|
||||
<div class="col-12 app-text-muted-2">
|
||||
<q-icon size="xs" class="q-mr-xs" name="mdi-map-marker-outline" />
|
||||
{{ addressTitle || $t('form.address') }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<div class="row col-12" v-if="useWorkPlace">
|
||||
<q-input
|
||||
|
|
@ -210,7 +343,6 @@ watch(districtId, fetchSubDistrict);
|
|||
hide-bottom-space
|
||||
class="col-6"
|
||||
v-model="workplace"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
:label="$t('customer.form.workplace')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
|
|
@ -229,19 +361,53 @@ watch(districtId, fetchSubDistrict);
|
|||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
class="col-md-5 col-8"
|
||||
v-model="address"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
:label="$t('form.address')"
|
||||
:label="$t('form.addressNo')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-address-${indexId}` : 'input-address'}`"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-address-no-${indexId}` : 'input-address-no'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-1 col-4"
|
||||
:model-value="readonly ? moo || '-' : moo"
|
||||
:dense="dense"
|
||||
:label="$t('form.moo')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-moo-${indexId}` : 'input-moo'}`"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (moo = v) : '')"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:model-value="readonly ? soi || '-' : soi"
|
||||
:dense="dense"
|
||||
:label="$t('form.soi')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-soi-${indexId}` : 'input-soi'}`"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (soi = v) : '')"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:model-value="readonly ? street || '-' : street"
|
||||
:dense="dense"
|
||||
:label="$t('form.road')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-street-${indexId}` : 'input-street'}`"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (street = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
autocomplete="off"
|
||||
outlined
|
||||
|
|
@ -256,7 +422,6 @@ watch(districtId, fetchSubDistrict);
|
|||
input-debounce="0"
|
||||
option-label="name"
|
||||
v-model="provinceId"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-3 col-6"
|
||||
:dense="dense"
|
||||
:label="$t('form.province')"
|
||||
|
|
@ -301,7 +466,6 @@ watch(districtId, fetchSubDistrict);
|
|||
input-debounce="0"
|
||||
option-label="name"
|
||||
v-model="districtId"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-3 col-6"
|
||||
:dense="dense"
|
||||
:label="$t('form.district')"
|
||||
|
|
@ -344,7 +508,6 @@ watch(districtId, fetchSubDistrict);
|
|||
option-value="id"
|
||||
input-debounce="0"
|
||||
option-label="name"
|
||||
lazy-rules="ondemand"
|
||||
class="col-md-3 col-6"
|
||||
v-model="subDistrictId"
|
||||
:dense="dense"
|
||||
|
|
@ -376,34 +539,46 @@ watch(districtId, fetchSubDistrict);
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
readonly
|
||||
:label="$t('form.zipCode')"
|
||||
class="col-md-2 col-6"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="
|
||||
addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? ''
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
:model-value="
|
||||
address ? ($i18n.locale === 'eng' ? fullAddress : fullAddress) : ''
|
||||
"
|
||||
:dense="dense"
|
||||
:label="$t('form.fullAddress')"
|
||||
readonly
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-full-address-${indexId}` : 'input-full-address'}`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 app-text-muted-2">
|
||||
<q-icon size="xs" class="q-mr-xs" name="mdi-map-marker-outline" />
|
||||
{{ addressTitleEN || $t('form.address', { suffix: '(EN)' }) }}
|
||||
</div>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<template v-if="!hideIcon">
|
||||
<div class="col-12 app-text-muted-2" v-if="!hideInputEn">
|
||||
<q-icon size="xs" class="q-mr-xs" name="mdi-map-marker-outline" />
|
||||
{{ addressTitleEN || $t('form.address', { suffix: '(EN)' }) }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="col-12 row q-col-gutter-sm" v-if="!hideInputEn">
|
||||
<div class="row col-12" v-if="useWorkPlace">
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
v-model="workplaceEN"
|
||||
lazy-rules="ondemand"
|
||||
:dense="dense"
|
||||
:label="$t('customer.form.workplaceEN')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
|
|
@ -420,14 +595,13 @@ watch(districtId, fetchSubDistrict);
|
|||
</div>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-address-en-${indexId}` : 'input-address-en'}`"
|
||||
:dense="dense"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
:label="$t('form.address', { suffix: '(EN)' })"
|
||||
class="col-12"
|
||||
label="Address No."
|
||||
class="col-md-5 col-8"
|
||||
v-model="addressEN"
|
||||
:rules="
|
||||
disabledRule
|
||||
|
|
@ -435,6 +609,45 @@ watch(districtId, fetchSubDistrict);
|
|||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-1 col-4"
|
||||
:model-value="readonly ? mooEN || '-' : mooEN"
|
||||
:dense="dense"
|
||||
label="Moo"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-moo-${indexId}` : 'input-moo'}`"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (mooEN = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:model-value="readonly ? soiEN || '-' : soiEN"
|
||||
:dense="dense"
|
||||
label="Soi"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-soi-${indexId}` : 'input-soi'}`"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (soiEN = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:model-value="readonly ? streetEN || '-' : streetEN"
|
||||
:dense="dense"
|
||||
label="Road"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-street-${indexId}` : 'input-street'}`"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (streetEN = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
autocomplete="off"
|
||||
outlined
|
||||
|
|
@ -448,11 +661,10 @@ watch(districtId, fetchSubDistrict);
|
|||
option-value="id"
|
||||
input-debounce="0"
|
||||
v-model="provinceId"
|
||||
lazy-rules="ondemand"
|
||||
option-label="nameEN"
|
||||
class="col-md-3 col-6"
|
||||
:dense="dense"
|
||||
:label="$t('form.province')"
|
||||
label="Province"
|
||||
:options="provinceOptions"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:hide-dropdown-icon="readonly || sameWithEmployer"
|
||||
|
|
@ -492,11 +704,10 @@ watch(districtId, fetchSubDistrict);
|
|||
option-value="id"
|
||||
input-debounce="0"
|
||||
v-model="districtId"
|
||||
lazy-rules="ondemand"
|
||||
option-label="nameEN"
|
||||
class="col-md-3 col-6"
|
||||
:dense="dense"
|
||||
:label="$t('form.district')"
|
||||
label="District"
|
||||
:options="districtOptions"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:hide-dropdown-icon="readonly || sameWithEmployer"
|
||||
|
|
@ -535,12 +746,11 @@ watch(districtId, fetchSubDistrict);
|
|||
hide-bottom-space
|
||||
option-value="id"
|
||||
input-debounce="0"
|
||||
lazy-rules="ondemand"
|
||||
option-label="nameEN"
|
||||
class="col-md-3 col-6"
|
||||
v-model="subDistrictId"
|
||||
:dense="dense"
|
||||
:label="$t('form.subDistrict')"
|
||||
label="Sub-District"
|
||||
:options="subDistrictOptions"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:hide-dropdown-icon="readonly || sameWithEmployer"
|
||||
|
|
@ -568,7 +778,6 @@ watch(districtId, fetchSubDistrict);
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
hide-bottom-space
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
|
||||
:dense="dense"
|
||||
|
|
@ -576,14 +785,25 @@ watch(districtId, fetchSubDistrict);
|
|||
readonly
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
zip="zip-en"
|
||||
:label="$t('form.zipCode')"
|
||||
class="col-md-2 col-6"
|
||||
label="Zip Code"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="
|
||||
addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? ''
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
:model-value="addressEN ? fullAddressEN : ''"
|
||||
:dense="dense"
|
||||
label="Full Address"
|
||||
readonly
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-full-address-en-${indexId}` : 'input-full-address-en'}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const props = defineProps<{
|
|||
readonly?: boolean;
|
||||
clearable?: boolean;
|
||||
label?: string;
|
||||
bgColor?: string;
|
||||
rules?: ((value: string) => string | true)[];
|
||||
disabledDates?: string[] | Date[] | ((date: Date) => boolean);
|
||||
}>();
|
||||
|
|
@ -75,11 +76,11 @@ function valueUpdate(value: string) {
|
|||
</template>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
placeholder="DD/MM/YYYY"
|
||||
hide-bottom-space
|
||||
dense
|
||||
outlined
|
||||
:bg-color="bgColor"
|
||||
:rules
|
||||
:label
|
||||
:for="id"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import ToggleButton from '../button/ToggleButton.vue';
|
||||
import { QMenu } from 'quasar';
|
||||
import { watch } from 'vue';
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
idName: string;
|
||||
status: string;
|
||||
hideToggle?: boolean;
|
||||
useLink?: boolean;
|
||||
useUpload?: boolean;
|
||||
|
||||
disableDelete?: boolean;
|
||||
}>(),
|
||||
|
|
@ -17,9 +23,22 @@ withDefaults(
|
|||
defineEmits<{
|
||||
(e: 'view'): void;
|
||||
(e: 'edit'): void;
|
||||
(e: 'link'): void;
|
||||
(e: 'upload'): void;
|
||||
(e: 'delete'): void;
|
||||
(e: 'changeStatus'): void;
|
||||
}>();
|
||||
|
||||
const refMenu = ref<InstanceType<typeof QMenu>>();
|
||||
|
||||
watch(
|
||||
() => props.status,
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
refMenu.value?.hide();
|
||||
}, 100);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<q-btn
|
||||
|
|
@ -31,7 +50,7 @@ defineEmits<{
|
|||
:id="`btn-kebab-action-${idName}`"
|
||||
@click.stop
|
||||
>
|
||||
<q-menu class="bordered">
|
||||
<q-menu class="bordered" ref="refMenu" :key="idName">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-close-popup
|
||||
|
|
@ -73,6 +92,49 @@ defineEmits<{
|
|||
{{ $t('general.edit') }}
|
||||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-item
|
||||
v-if="status !== 'INACTIVE' && useLink"
|
||||
v-close-popup
|
||||
dense
|
||||
clickable
|
||||
class="row q-py-sm"
|
||||
style="white-space: nowrap"
|
||||
:id="`btn-kebab-edit-${idName}`"
|
||||
@click.stop="() => $emit('link')"
|
||||
>
|
||||
<q-icon
|
||||
size="xs"
|
||||
class="col-3"
|
||||
name="mdi-link-variant"
|
||||
style="color: hsl(var(--yellow-6-hsl))"
|
||||
/>
|
||||
<span class="col-9 q-px-md flex items-center">
|
||||
{{ $t('general.add', { text: $t('quotation.receipt') }) }}
|
||||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-item
|
||||
v-if="status !== 'INACTIVE' && useUpload"
|
||||
v-close-popup
|
||||
dense
|
||||
clickable
|
||||
class="row q-py-sm"
|
||||
style="white-space: nowrap"
|
||||
:id="`btn-kebab-edit-${idName}`"
|
||||
@click.stop="() => $emit('upload')"
|
||||
>
|
||||
<q-icon
|
||||
size="xs"
|
||||
class="col-3"
|
||||
name="mdi-upload"
|
||||
style="color: hsl(var(--blue-10-hsl))"
|
||||
/>
|
||||
<span class="col-9 q-px-md flex items-center">
|
||||
{{ $t('general.upload', { msg: $t('general.attachment') }) }}
|
||||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-item
|
||||
v-if="status !== 'INACTIVE'"
|
||||
v-close-popup
|
||||
|
|
@ -100,14 +162,18 @@ defineEmits<{
|
|||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-item dense>
|
||||
<q-item v-if="!hideToggle" dense>
|
||||
<q-item-section class="q-py-sm">
|
||||
<div class="q-pa-sm surface-2 rounded flex items-center">
|
||||
<ToggleButton
|
||||
two-way
|
||||
:id="`btn-kebab-status-${idName}`"
|
||||
:model-value="status !== 'INACTIVE'"
|
||||
@click="() => $emit('changeStatus')"
|
||||
@click="
|
||||
() => {
|
||||
$emit('changeStatus');
|
||||
}
|
||||
"
|
||||
/>
|
||||
<span class="q-pl-md">
|
||||
{{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ defineProps<{
|
|||
male?: boolean;
|
||||
female?: boolean;
|
||||
img?: string;
|
||||
fallbackImg?: string;
|
||||
detail?: { icon: string; value: string }[];
|
||||
};
|
||||
tag?: [{ color: string; value: string }];
|
||||
|
|
@ -48,7 +49,7 @@ defineEmits<{
|
|||
}"
|
||||
@click="$emit('enterCard', 'INFO')"
|
||||
>
|
||||
<div class="column items-center">
|
||||
<div class="column items-center" :class="{ 'q-pt-sm': noAction }">
|
||||
<!-- kebab menu -->
|
||||
<div class="full-width flex no-wrap" v-if="!noAction">
|
||||
<div style="margin-right: auto">
|
||||
|
|
@ -98,19 +99,15 @@ defineEmits<{
|
|||
<q-img
|
||||
v-if="!$slots.img"
|
||||
:src="data.img ?? '/no-profile.png'"
|
||||
style="
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
"
|
||||
fit="cover"
|
||||
style="width: 100%; height: 100%; border-radius: 50%"
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
style="background: none"
|
||||
class="full-width full-height items-center justify-center flex"
|
||||
class="no-padding full-width full-height flex items-center justify-center"
|
||||
>
|
||||
<q-img src="/no-profile.png" width="5rem" />
|
||||
<q-img :src="data.fallbackImg || '/no-profile.png'" fit="cover" />
|
||||
</div>
|
||||
</template>
|
||||
</q-img>
|
||||
|
|
@ -139,22 +136,26 @@ defineEmits<{
|
|||
</AppCircle>
|
||||
|
||||
<!-- name symbol -->
|
||||
<span class="items-center text-center row full-width">
|
||||
<span class="col ellipsis" style="width: 0">
|
||||
{{ data.name }}
|
||||
<span class="full-width">
|
||||
<span
|
||||
class="items-center justify-center row text-center ellipsis col-6"
|
||||
>
|
||||
<div class="items-center ellipsis" style="max-width: 140px">
|
||||
{{ data.name }}
|
||||
</div>
|
||||
<Icon
|
||||
v-if="data.male || data.female"
|
||||
class="q-pl-xs"
|
||||
:class="{
|
||||
'symbol-gender': data.male || data.female,
|
||||
'symbol-gender__male': !disabled && data.male,
|
||||
'symbol-gender__female': !disabled && data.female,
|
||||
'symbol-gender__disable': disabled,
|
||||
}"
|
||||
:icon="`material-symbols:${data.male ? 'male' : 'female'}`"
|
||||
width="24px"
|
||||
/>
|
||||
</span>
|
||||
<Icon
|
||||
v-if="data.male || data.female"
|
||||
class="q-pl-xs"
|
||||
:class="{
|
||||
'symbol-gender': data.male || data.female,
|
||||
'symbol-gender__male': !disabled && data.male,
|
||||
'symbol-gender__female': !disabled && data.female,
|
||||
'symbol-gender__disable': disabled,
|
||||
}"
|
||||
:icon="`material-symbols:${data.male ? 'male' : 'female'}`"
|
||||
width="24px"
|
||||
/>
|
||||
</span>
|
||||
<span style="color: hsl(var(--text-mute)); scale: 0.9">
|
||||
{{ data.code || '-' }}
|
||||
|
|
|
|||
97
src/components/shared/SelectInput.vue
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { selectFilterOptionRefMod } from 'src/stores/utils';
|
||||
import { QSelect } from 'quasar';
|
||||
|
||||
const model = defineModel<string | Date | null>();
|
||||
|
||||
const options = ref<Record<string, unknown>[]>([]);
|
||||
let defaultFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
id?: string;
|
||||
label?: string;
|
||||
option: Record<string, unknown>[];
|
||||
optionLabel?: string;
|
||||
optionValue?: string;
|
||||
|
||||
readonly?: boolean;
|
||||
clearable?: boolean;
|
||||
incremental?: boolean;
|
||||
|
||||
rules?: ((value: string) => string | true)[];
|
||||
}>(),
|
||||
{ option: () => [], optionLabel: 'label', optionValue: 'value' },
|
||||
);
|
||||
|
||||
defineEmits<{
|
||||
(e: 'filter', val: string, update: void): void;
|
||||
}>();
|
||||
|
||||
onMounted(() => {
|
||||
defaultFilter = selectFilterOptionRefMod(
|
||||
ref(props.option),
|
||||
options,
|
||||
props.optionLabel,
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.option,
|
||||
() => {
|
||||
defaultFilter = selectFilterOptionRefMod(
|
||||
ref(props.option),
|
||||
options,
|
||||
props.optionLabel,
|
||||
);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<q-select
|
||||
outlined
|
||||
:clearable
|
||||
use-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
:fill-input="!!model"
|
||||
:hide-dropdown-icon="readonly"
|
||||
input-debounce="0"
|
||||
:option-value="optionValue"
|
||||
:option-label="optionLabel"
|
||||
v-model="model"
|
||||
dense
|
||||
:readonly
|
||||
:label="label"
|
||||
:options="incremental ? option : options"
|
||||
:for="`select-${id}`"
|
||||
@filter="
|
||||
(val, update) => {
|
||||
incremental ? $emit('filter', val, update) : defaultFilter(val, update);
|
||||
}
|
||||
"
|
||||
:rules
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<template v-if="$slots.name" v-slot:selected-item="scope">
|
||||
<slot name="selected-item" :scope="scope"></slot>
|
||||
</template>
|
||||
|
||||
<template v-if="$slots.option" v-slot:option="scope">
|
||||
<slot name="option" :scope="scope"></slot>
|
||||
</template>
|
||||
</q-select>
|
||||
</template>
|
||||
77
src/components/shared/SelectZone.vue
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<script setup lang="ts">
|
||||
const search = defineModel<string>('search');
|
||||
const selectedItem = defineModel<unknown[]>('selectedItem', { default: [] });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
items: any;
|
||||
color?: string;
|
||||
}>(),
|
||||
{
|
||||
items: () => [],
|
||||
color: 'var(--brand-1)',
|
||||
},
|
||||
);
|
||||
|
||||
function select(item: unknown) {
|
||||
if (selectedItem.value.includes(item)) {
|
||||
let index = selectedItem.value.indexOf(item);
|
||||
selectedItem.value.splice(index, 1);
|
||||
} else selectedItem.value.push(item);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<section class="full-width column q-pa-md">
|
||||
<header class="row items-center no-wrap q-mb-md">
|
||||
<div class="col"><slot name="top"></slot></div>
|
||||
<q-input
|
||||
for="input-search"
|
||||
outlined
|
||||
dense
|
||||
:label="$t('general.search')"
|
||||
class="q-ml-auto"
|
||||
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
|
||||
v-model="search"
|
||||
debounce="200"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="mdi-magnify" />
|
||||
</template>
|
||||
</q-input>
|
||||
</header>
|
||||
|
||||
<section class="col">
|
||||
<div class="row q-col-gutter-md">
|
||||
<div
|
||||
v-for="(item, i) in items"
|
||||
:key="i"
|
||||
class="col-md-2 col-sm-6 col-12"
|
||||
>
|
||||
<div
|
||||
class="rounded cursor-pointer relative-position"
|
||||
:style="`border: 1px solid ${selectedItem.includes(item) ? color : 'transparent'}`"
|
||||
@click="() => select(item)"
|
||||
>
|
||||
<div
|
||||
v-if="selectedItem.includes(item)"
|
||||
class="badge absolute-top-right flex justify-center q-ma-sm"
|
||||
:style="`background-color: ${color}`"
|
||||
>
|
||||
{{ selectedItem.indexOf(item) + 1 }}
|
||||
</div>
|
||||
<slot name="data" :item="item"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.badge {
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--surface-1);
|
||||
}
|
||||
</style>
|
||||
169
src/components/shared/TreeView.vue
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue';
|
||||
|
||||
type Node = {
|
||||
[key: string]: any;
|
||||
opened?: boolean;
|
||||
selected?: boolean;
|
||||
children?: Node[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
level?: number;
|
||||
keyTitle?: string;
|
||||
keySubtitle?: string;
|
||||
expandable?: boolean;
|
||||
decoration?: {
|
||||
level?: number;
|
||||
bg?: string;
|
||||
fg?: string;
|
||||
icon?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const nodes = defineModel<Node[]>('nodes', { required: true });
|
||||
|
||||
const emits = defineEmits<{ (e: 'checked'): void }>();
|
||||
|
||||
const dec = props.decoration?.find((v) => v.level === (props.level || 0));
|
||||
|
||||
function recursiveDeselect(node: Node) {
|
||||
if (node.children) {
|
||||
node.children.forEach((v) => {
|
||||
v.selected = false;
|
||||
recursiveDeselect(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
function toggleCheck(node: Node) {
|
||||
node.selected = !node.selected;
|
||||
if (node.selected === false) recursiveDeselect(node);
|
||||
if (node.selected === true) emits('checked');
|
||||
}
|
||||
|
||||
function toggleExpand(node: Node) {
|
||||
node.opened = !node.opened;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tree-container">
|
||||
<div v-for="(node, i) in nodes" class="tree-item" :key="i">
|
||||
<slot
|
||||
v-if="$slots['item']"
|
||||
name="item"
|
||||
:data="{ node, toggleExpand, toggleCheck }"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
class="item__content row items-center no-wrap"
|
||||
@click="toggleExpand(node)"
|
||||
>
|
||||
<label class="flex items-center item__checkbox" @click.stop>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="node.selected"
|
||||
@click="toggleCheck(node)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div
|
||||
class="item__icon flex items-center justify-center"
|
||||
:style="`background: ${dec?.bg}; color: ${dec?.fg}`"
|
||||
>
|
||||
<Icon v-if="dec && dec.icon" :icon="dec.icon" />
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<span class="item__title">
|
||||
{{ node[keyTitle || 'title'] || 'No Title' }}
|
||||
</span>
|
||||
<span class="item__subtitle">
|
||||
{{ node[keySubtitle || 'subtitle'] || 'No Subtitle' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<q-separator v-if="!level"></q-separator>
|
||||
|
||||
<transition name="slide">
|
||||
<div
|
||||
class="q-pl-lg q-pt-sm"
|
||||
v-if="node.opened && node.children && node.children.length > 0"
|
||||
>
|
||||
<TreeView
|
||||
class="item__children"
|
||||
v-if="node.children"
|
||||
v-model:nodes="node.children"
|
||||
@checked="
|
||||
() => {
|
||||
node.selected = true;
|
||||
$emit('checked');
|
||||
}
|
||||
"
|
||||
:level="(level || 0) + 1"
|
||||
:expandable
|
||||
:decoration
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.tree-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
gap: 8px;
|
||||
|
||||
& .tree-item {
|
||||
& .item__content {
|
||||
padding: 0.1rem 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background: hsla(0 0% 0% / 0.1);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
}
|
||||
|
||||
& .item__checkbox {
|
||||
padding: 0.1rem 0.5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
& .item__icon {
|
||||
margin-right: 1rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
& .item__title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
& .item__subtitle {
|
||||
font-size: 80%;
|
||||
color: hsla(var(--text-mute-2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slide-enter-active {
|
||||
transition: all 0.1s ease-out;
|
||||
}
|
||||
|
||||
.slide-leave-active {
|
||||
transition: all 0.1s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
|
||||
.slide-enter-from,
|
||||
.slide-leave-to {
|
||||
transform: translateY(-20px);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
76
src/components/upload-file/CorpFormBusinessRegistration.vue
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<script setup lang="ts">
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
|
||||
const registrationNumber = defineModel<string>('registrationNumber', {
|
||||
default: '',
|
||||
});
|
||||
const name = defineModel<string>('name', {
|
||||
default: '',
|
||||
});
|
||||
|
||||
const businessRegistrationDate = defineModel<string>(
|
||||
'businessRegistrationDate',
|
||||
{ default: '' },
|
||||
);
|
||||
const visaPlace = defineModel<string>('visaPlace', { default: '' });
|
||||
defineProps<{
|
||||
prefixId?: string;
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
customerType?: 'CORP' | 'PERS';
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row q-mb-sm" style="gap: 10px">
|
||||
<div class="col-12 text-subtitle1 text-weight-bold">
|
||||
<p>Document Properties</p>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.businessRegistration.registrationNumber')"
|
||||
for="input-citizen-id"
|
||||
v-model="registrationNumber"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.businessRegistration.registrationNumber')"
|
||||
for="input-citizen-id"
|
||||
v-model="name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<DatePicker
|
||||
:label="$t('form.businessRegistration.businessRegistration')"
|
||||
v-model="businessRegistrationDate"
|
||||
class="col-4"
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
:label="$t('customerEmployee.form.visaPlace')"
|
||||
v-model="visaPlace"
|
||||
class="col-4"
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
import { ref, watch } from 'vue';
|
||||
import { QSelect } from 'quasar';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { AddressForm } from 'components/form';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { onMounted } from 'vue';
|
||||
|
|
@ -38,23 +39,48 @@ defineEmits<{
|
|||
(e: 'cancel'): void;
|
||||
}>();
|
||||
|
||||
const middleName = defineModel<string>('middleName', { default: '' });
|
||||
const middleNameEn = defineModel<string>('middleNameEn', { default: '' });
|
||||
|
||||
const workplaceEN = defineModel<string>('workplaceEn', { default: '' });
|
||||
const addressEN = defineModel('addressEN', { default: '' });
|
||||
const street = defineModel('street', { default: '' });
|
||||
const streetEN = defineModel('streetEN', { default: '' });
|
||||
const moo = defineModel('moo', { default: '' });
|
||||
const mooEN = defineModel('mooEN', { default: '' });
|
||||
const soi = defineModel('soi', { default: '' });
|
||||
const soiEN = defineModel('soiEN', { default: '' });
|
||||
const provinceId = defineModel<string | null | undefined>('provinceId');
|
||||
const districtId = defineModel<string | null | undefined>('districtId');
|
||||
const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
|
||||
const zipCode = defineModel<string | null | undefined>('zipCode');
|
||||
const sameWithEmployer = defineModel<boolean>('sameWithEmployer');
|
||||
const homeCode = defineModel<string | null | undefined>('homeCode');
|
||||
const employmentOffice = defineModel<string | null | undefined>(
|
||||
'employmentOffice',
|
||||
);
|
||||
const employmentOfficeEN = defineModel<string | null | undefined>(
|
||||
'employmentOfficeEN',
|
||||
);
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
const namePrefix = defineModel<string | null>('namePrefix');
|
||||
const birthDate = defineModel<Date | string | null>('birthDate');
|
||||
const gender = defineModel<string>('gender');
|
||||
const gender = defineModel<string>('gender', { required: true });
|
||||
|
||||
const address = defineModel<string>('address');
|
||||
const firstName = defineModel<string>('firstName', { required: true });
|
||||
const lastName = defineModel<string>('lastName', { required: true });
|
||||
const firstNameEN = defineModel<string>('firstNameEn', { required: true });
|
||||
const lastNameEN = defineModel<string>('lastNameEn', { required: true });
|
||||
|
||||
const issueDate = defineModel<Date>('issueDate', { required: true });
|
||||
const expireDate = defineModel<Date>('expireDate', { required: true });
|
||||
const citizenId = defineModel<string | undefined>('citizenId', {
|
||||
required: true,
|
||||
});
|
||||
const nationality = defineModel<string>('nationality');
|
||||
const nationality = defineModel<string>('nationality', { required: true });
|
||||
|
||||
const religion = defineModel<string>('religion');
|
||||
const religion = defineModel<string>('religion', { required: true });
|
||||
|
||||
const branchOptions = defineModel<{ id: string; name: string }[]>(
|
||||
'branchOptions',
|
||||
|
|
@ -152,7 +178,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
</div>
|
||||
<div class="col row" style="gap: 10px">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -173,7 +198,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -185,7 +209,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -207,7 +230,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
class="col"
|
||||
dense
|
||||
:readonly="readonly"
|
||||
|
|
@ -244,7 +266,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
lazy-rules="ondemand"
|
||||
hide-dropdown-icon
|
||||
class="col-2"
|
||||
dense
|
||||
|
|
@ -271,7 +292,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
</q-select>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -283,7 +303,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -295,7 +314,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:disable="!readonly"
|
||||
|
|
@ -310,7 +328,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -319,9 +336,10 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.firstNameEN')"
|
||||
for="input-first-name-en"
|
||||
v-model="firstNameEN"
|
||||
:rules="[(val) => /^[A-Za-z]+$/.test(val)]"
|
||||
:error-message="$t('form.error.letterOnly')"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
|
|
@ -330,22 +348,33 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.lastNameEN')"
|
||||
for="input-last-name-en"
|
||||
v-model="lastNameEN"
|
||||
:rules="[(val) => /^[A-Za-z]+$/.test(val)]"
|
||||
:error-message="$t('form.error.letterOnly')"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
<AddressForm
|
||||
prefixId="citizen"
|
||||
v-model:homeCode="homeCode"
|
||||
v-model:employmentOffice="employmentOffice"
|
||||
v-model:employmentOfficeEN="employmentOfficeEN"
|
||||
v-model:address="address"
|
||||
v-model:addressEN="addressEN"
|
||||
v-model:street="street"
|
||||
v-model:streetEN="streetEN"
|
||||
v-model:moo="moo"
|
||||
v-model:mooEN="mooEN"
|
||||
v-model:soi="soi"
|
||||
v-model:soiEN="soiEN"
|
||||
v-model:province-id="provinceId"
|
||||
v-model:district-id="districtId"
|
||||
v-model:sub-district-id="subDistrictId"
|
||||
hide-title
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
:label="$t('general.address')"
|
||||
for="input-address"
|
||||
v-model="address"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
:label="$t('customer.form.issueDate')"
|
||||
v-model="firstName"
|
||||
v-model="issueDate"
|
||||
class="col-6"
|
||||
:id="`${prefixId}-input-issue-date`"
|
||||
:readonly="readonly"
|
||||
|
|
@ -354,7 +383,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
|
||||
<DatePicker
|
||||
:label="$t('customer.form.passportExpiryDate')"
|
||||
v-model="firstName"
|
||||
v-model="expireDate"
|
||||
class="col-6"
|
||||
:id="`${prefixId}-input-passport-expiry-date`"
|
||||
:readonly="readonly"
|
||||
|
|
|
|||
99
src/components/upload-file/FormTm6.vue
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<script setup lang="ts">
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
import { AddressForm } from 'components/form';
|
||||
|
||||
const transportation = defineModel<string>('transportation', { default: '' });
|
||||
const travelDate = defineModel<string>('travelDate', { default: '' });
|
||||
const entryCheckpoint = defineModel<string>('entryCheckpoint', { default: '' });
|
||||
const entryCardNumber = defineModel<string>('entryCardNumber', { default: '' });
|
||||
|
||||
const address = defineModel<string>('address');
|
||||
const street = defineModel('street', { default: '' });
|
||||
const moo = defineModel('moo', { default: '' });
|
||||
const soi = defineModel('soi', { default: '' });
|
||||
const provinceId = defineModel<string | null | undefined>('provinceId');
|
||||
const districtId = defineModel<string | null | undefined>('districtId');
|
||||
const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
|
||||
const homeCode = defineModel<string | null | undefined>('homeCode');
|
||||
const employmentOffice = defineModel<string | null | undefined>(
|
||||
'employmentOffice',
|
||||
);
|
||||
|
||||
defineProps<{
|
||||
prefixId?: string;
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row q-mb-sm" style="gap: 10px">
|
||||
<div class="col-12 text-subtitle1 text-weight-bold">
|
||||
<p>Document Properties</p>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.tm6.transportation')"
|
||||
for="input-citizen-id"
|
||||
v-model="transportation"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
:label="$t('form.tm6.travelDate')"
|
||||
v-model="travelDate"
|
||||
class="col-4"
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AddressForm
|
||||
prefixId="form-tm6"
|
||||
hide-icon
|
||||
hide-title
|
||||
hide-input-en
|
||||
dense
|
||||
v-model:homeCode="homeCode"
|
||||
v-model:employmentOffice="employmentOffice"
|
||||
v-model:address="address"
|
||||
v-model:street="street"
|
||||
v-model:moo="moo"
|
||||
v-model:soi="soi"
|
||||
v-model:province-id="provinceId"
|
||||
v-model:district-id="districtId"
|
||||
v-model:sub-district-id="subDistrictId"
|
||||
/>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('form.tm6.entryCheckpoint')"
|
||||
for="input-citizen-id"
|
||||
v-model="entryCheckpoint"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('form.tm6.entryCardNumber')"
|
||||
for="input-citizen-id"
|
||||
v-model="entryCardNumber"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
233
src/components/upload-file/PersFormBusinessRegistration.vue
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
import { QSelect } from 'quasar';
|
||||
import { AddressForm } from 'components/form';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
||||
const registrationNumber = defineModel<string>('registrationNumber', {
|
||||
default: '',
|
||||
});
|
||||
|
||||
const requestAt = defineModel<string>('requestAt', { default: '' });
|
||||
const namePrefix = defineModel<string | null>('namePrefix');
|
||||
const firstName = defineModel<string>('firstName', { default: '' });
|
||||
const lastName = defineModel<string>('lastName', { default: '' });
|
||||
|
||||
const businessRegistrationDate = defineModel<string>(
|
||||
'businessRegistrationDate',
|
||||
{ default: '' },
|
||||
);
|
||||
const visaPlace = defineModel<string>('visaPlace', { default: '' });
|
||||
const businessType = defineModel<string>('businessType', { default: '' });
|
||||
const businessName = defineModel<string>('businessName', { default: '' });
|
||||
const romanCharacters = defineModel<string>('romanCharacters', { default: '' });
|
||||
|
||||
const address = defineModel<string>('address');
|
||||
const street = defineModel('street', { default: '' });
|
||||
const moo = defineModel('moo', { default: '' });
|
||||
const soi = defineModel('soi', { default: '' });
|
||||
const provinceId = defineModel<string | null | undefined>('provinceId');
|
||||
const districtId = defineModel<string | null | undefined>('districtId');
|
||||
const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
|
||||
const homeCode = defineModel<string | null | undefined>('homeCode');
|
||||
const employmentOffice = defineModel<string | null | undefined>(
|
||||
'employmentOffice',
|
||||
);
|
||||
|
||||
const prefixNameOptions = ref<Record<string, unknown>[]>([]);
|
||||
let prefixNameFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
defineProps<{
|
||||
prefixId?: string;
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
customerType?: 'CORP' | 'PERS';
|
||||
}>();
|
||||
|
||||
onMounted(() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row q-mb-sm" style="gap: 10px">
|
||||
<div class="col-12 text-subtitle1 text-weight-bold">
|
||||
<p>Document Properties</p>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.businessRegistration.registrationNumber')"
|
||||
for="input-citizen-id"
|
||||
v-model="registrationNumber"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-3"
|
||||
:label="$t('form.businessRegistration.requestAt')"
|
||||
for="input-citizen-id"
|
||||
v-model="requestAt"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<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
|
||||
class="col-2"
|
||||
dense
|
||||
:readonly="readonly"
|
||||
:options="prefixNameOptions"
|
||||
:for="`${prefixId}-select-prefix-name`"
|
||||
:label="$t('form.prefixName')"
|
||||
@filter="prefixNameFilter"
|
||||
:model-value="readonly ? namePrefix || '-' : namePrefix"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
typeof v === 'string' ? (namePrefix = v) : '';
|
||||
}
|
||||
"
|
||||
@clear="namePrefix = ''"
|
||||
>
|
||||
<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
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-5"
|
||||
:label="$t('customer.form.firstName')"
|
||||
for="input-first-name"
|
||||
v-model="firstName"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-5"
|
||||
:label="$t('customer.form.lastName')"
|
||||
for="input-last-name"
|
||||
v-model="lastName"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
:label="$t('form.businessRegistration.businessRegistration')"
|
||||
v-model="businessRegistrationDate"
|
||||
class="col-3"
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
:label="$t('customerEmployee.form.visaPlace')"
|
||||
v-model="visaPlace"
|
||||
class="col-3"
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.businessRegistration.businessType')"
|
||||
for="input-last-name"
|
||||
v-model="businessType"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
:label="$t('form.businessRegistration.businessName')"
|
||||
for="input-last-name"
|
||||
v-model="businessName"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
:label="$t('form.businessRegistration.romanCharacters')"
|
||||
for="input-last-name"
|
||||
v-model="romanCharacters"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AddressForm
|
||||
prefixId="form-tm6"
|
||||
hide-icon
|
||||
hide-title
|
||||
hide-input-en
|
||||
dense
|
||||
v-model:homeCode="homeCode"
|
||||
v-model:employmentOffice="employmentOffice"
|
||||
v-model:address="address"
|
||||
v-model:street="street"
|
||||
v-model:moo="moo"
|
||||
v-model:soi="soi"
|
||||
v-model:province-id="provinceId"
|
||||
v-model:district-id="districtId"
|
||||
v-model:sub-district-id="subDistrictId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,23 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { SaveButton, UndoButton, CloseButton } from 'components/button';
|
||||
import { DeleteButton } from 'components/button';
|
||||
import { dialog } from 'stores/utils';
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { VuePDF, usePDF } from '@tato30/vue-pdf';
|
||||
|
||||
const { t } = useI18n();
|
||||
const currentFileSelected = ref<string>('');
|
||||
const file = defineModel<
|
||||
const obj = defineModel<
|
||||
{
|
||||
name?: string;
|
||||
group?: string;
|
||||
url?: string;
|
||||
file?: File;
|
||||
}[]
|
||||
>('file', {
|
||||
>({
|
||||
default: [],
|
||||
});
|
||||
|
||||
const currentFile = computed(() => file.value.at(currentIndex.value));
|
||||
const currentFile = computed(() => obj.value.at(currentIndex.value));
|
||||
const statusOcr = defineModel<boolean>('statusOcr', { default: false });
|
||||
const currentMode = ref<string>('');
|
||||
const currentIndex = ref(0);
|
||||
|
|
@ -33,8 +35,10 @@ const props = withDefaults(
|
|||
readonly?: boolean;
|
||||
dropdownList?: { label: string; value: string }[];
|
||||
hideAction?: boolean;
|
||||
autoSave?: boolean;
|
||||
}>(),
|
||||
{
|
||||
autoSave: false,
|
||||
treeFile: () => [],
|
||||
},
|
||||
);
|
||||
|
|
@ -51,52 +55,82 @@ const inputFile = (() => {
|
|||
return _element;
|
||||
})();
|
||||
|
||||
function change(e: Event) {
|
||||
async function change(e: Event) {
|
||||
const _element = e.target as HTMLInputElement | null;
|
||||
const _file = _element?.files?.[0];
|
||||
currentIndex.value = file.value.length;
|
||||
|
||||
if (_file) {
|
||||
currentIndex.value = file.length + 1;
|
||||
if (!obj.value[currentIndex.value]) {
|
||||
obj.value = [
|
||||
...obj.value,
|
||||
{
|
||||
name: _file.name,
|
||||
file: _file,
|
||||
},
|
||||
];
|
||||
currentIndex.value = obj.value.length;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(_file);
|
||||
reader.onload = () => {
|
||||
if (file.value[currentIndex.value]) {
|
||||
file.value[currentIndex.value].url = reader.result as string;
|
||||
if (obj.value[currentIndex.value]) {
|
||||
obj.value[currentIndex.value].url = reader.result as string;
|
||||
currentFileSelected.value = _file.name;
|
||||
}
|
||||
};
|
||||
|
||||
if (_file && file.value[currentIndex.value]) {
|
||||
file.value[currentIndex.value].file = _file;
|
||||
file.value[currentIndex.value].group =
|
||||
props.dropdownList?.[currentIndexDropdownList.value].value;
|
||||
} else {
|
||||
const newName =
|
||||
props.dropdownList?.[currentIndexDropdownList.value].value +
|
||||
'-' +
|
||||
_file.name;
|
||||
|
||||
file.value.push({
|
||||
name: newName,
|
||||
group: props.dropdownList?.[currentIndexDropdownList.value].value,
|
||||
file: _file,
|
||||
});
|
||||
|
||||
currentFileSelected.value = newName;
|
||||
if (!!props.autoSave) {
|
||||
emit(
|
||||
'save',
|
||||
props.dropdownList?.[currentIndexDropdownList.value].value || '',
|
||||
inputFile?.files?.[0],
|
||||
);
|
||||
}
|
||||
|
||||
statusOcr.value = true;
|
||||
|
||||
emit(
|
||||
'sendOcr',
|
||||
props.dropdownList?.[currentIndexDropdownList.value].value || '',
|
||||
inputFile?.files?.[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// function change(e: Event) {
|
||||
// const _element = e.target as HTMLInputElement | null;
|
||||
// const _file = _element?.files?.[0];
|
||||
// currentIndex.value = obj.value.length;
|
||||
|
||||
// if (_file) {
|
||||
// currentIndex.value = obj.value.length + 1;
|
||||
// const reader = new FileReader();
|
||||
// reader.readAsDataURL(_file);
|
||||
// reader.onload = () => {
|
||||
// if (obj.value[currentIndex.value]) {
|
||||
// obj.value[currentIndex.value].url = reader.result as string;
|
||||
// console.log('asd');
|
||||
// }
|
||||
// };
|
||||
|
||||
// if (_file && obj.value[currentIndex.value]) {
|
||||
// obj.value[currentIndex.value].file = _file;
|
||||
// obj.value[currentIndex.value].group =
|
||||
// props.dropdownList?.[currentIndexDropdownList.value].value;
|
||||
// } else {
|
||||
// obj.value.push({
|
||||
// name: _file.name,
|
||||
// group: props.dropdownList?.[currentIndexDropdownList.value].value,
|
||||
// file: _file,
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (!!props.autoSave) {
|
||||
// emit(
|
||||
// 'save',
|
||||
// props.dropdownList?.[currentIndexDropdownList.value].value || '',
|
||||
// inputFile?.files?.[0],
|
||||
// );
|
||||
// } else {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
watch(currentFileSelected, () => {
|
||||
file.value.findIndex((v, i) => {
|
||||
obj.value.findIndex((v, i) => {
|
||||
if (v.name?.includes(currentFileSelected.value)) {
|
||||
currentIndex.value = i;
|
||||
|
||||
|
|
@ -112,45 +146,99 @@ watch(currentFileSelected, () => {
|
|||
|
||||
const emit = defineEmits<{
|
||||
(e: 'sendOcr', dropdown: string, file?: File): void;
|
||||
(e: 'save', เgroup: string, file?: File): void;
|
||||
(e: 'save', group: string, file?: File): void;
|
||||
(e: 'deleteFile', filename: string): void;
|
||||
}>();
|
||||
|
||||
const { pdf, pages } = usePDF(computed(() => currentFile.value?.url));
|
||||
|
||||
function deleteFileOfBranch(filename: string, index: number) {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
if (!!props.autoSave) {
|
||||
emit('deleteFile', filename);
|
||||
} else {
|
||||
obj.value.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="full-width row no-wrap wrapper">
|
||||
<div class="col full-height column no-wrap">
|
||||
<div class="full-width row no-wrap wrapper" style="height: 250px">
|
||||
<div class="col-3 full-height column no-wrap">
|
||||
<div class="q-pa-sm text-center bordered" style="height: 50px">
|
||||
<q-btn-dropdown
|
||||
:disable="readonly"
|
||||
icon="mdi-upload"
|
||||
color="info"
|
||||
label="อัปโหลดเอกสาร"
|
||||
>
|
||||
<q-list v-for="(v, i) in dropdownList" :key="v.value">
|
||||
<q-item
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="
|
||||
() => {
|
||||
currentIndexDropdownList = i;
|
||||
browse();
|
||||
}
|
||||
"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t(v.label) }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
:label="$t('general.uploadFile')"
|
||||
@click="
|
||||
() => {
|
||||
currentIndex = obj.length;
|
||||
browse();
|
||||
}
|
||||
"
|
||||
></q-btn-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="bordered-l bordered-b q-pa-sm col full-height scroll">
|
||||
<q-tree
|
||||
:nodes="treeFile || []"
|
||||
<div class="bordered-l bordered-b q-pa-sm full-height scroll">
|
||||
<q-item
|
||||
clickable
|
||||
v-for="(v, i) in obj"
|
||||
:key="v.name"
|
||||
dense
|
||||
type="button"
|
||||
class="no-padding items-center rounded"
|
||||
active-class="menu-active"
|
||||
:active="currentFileSelected === v.name"
|
||||
@click="
|
||||
async () => {
|
||||
currentFileSelected = v.name || '';
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="full-width row items-center justify-between">
|
||||
<div class="ellipsis col-8">
|
||||
<q-tooltip>{{ v.name }}</q-tooltip>
|
||||
{{ v.name }}
|
||||
</div>
|
||||
<DeleteButton
|
||||
iconOnly
|
||||
@click.stop="
|
||||
() => {
|
||||
deleteFileOfBranch(v.name || '', i);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</q-item>
|
||||
|
||||
<!-- <q-tree
|
||||
:nodes="
|
||||
Object.values(
|
||||
obj.reduce<
|
||||
Record<string, { label: string; file: { label: string }[] }>
|
||||
>((a, b) => {
|
||||
if (b.name && !a[b.name]) {
|
||||
a[b.name] = {
|
||||
label: b.name,
|
||||
file: [],
|
||||
};
|
||||
}
|
||||
return a;
|
||||
}, {}) || {},
|
||||
) || []
|
||||
"
|
||||
node-key="label"
|
||||
label-key="label"
|
||||
children-key="file"
|
||||
|
|
@ -159,15 +247,24 @@ const { pdf, pages } = usePDF(computed(() => currentFile.value?.url));
|
|||
default-expand-all
|
||||
>
|
||||
<template v-slot:default-header="prop">
|
||||
<div class="ellipsis">
|
||||
<q-tooltip>{{ prop.node.label }}</q-tooltip>
|
||||
{{ prop.node.label }}
|
||||
<div class="full-width row items-center justify-between">
|
||||
<div class="col-8 ellipsis">
|
||||
<q-tooltip>{{ prop.node.label }}</q-tooltip>
|
||||
{{ prop.node.label }}
|
||||
</div>
|
||||
<DeleteButton
|
||||
iconOnly
|
||||
@click.stop="
|
||||
() => {
|
||||
deleteFileOfBranch(prop.node.label);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</q-tree>
|
||||
</q-tree> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col full-height column no-wrap">
|
||||
<div
|
||||
class="bordered row items-center justify-evenly q-pa-sm no-wrap"
|
||||
|
|
@ -222,90 +319,23 @@ const { pdf, pages } = usePDF(computed(() => currentFile.value?.url));
|
|||
<div
|
||||
class="flex flex-center surface-2 bordered-l bordered-r bordered-b full-height scroll"
|
||||
>
|
||||
<template v-if="statusOcr">
|
||||
<q-spinner color="primary" size="3em" :thickness="2" />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<VuePDF
|
||||
v-if="
|
||||
currentFile?.url?.split('?').at(0)?.endsWith('.pdf') ||
|
||||
currentFile?.file?.type === 'application/pdf'
|
||||
"
|
||||
class="q-py-md"
|
||||
:pdf="pdf"
|
||||
:page="page"
|
||||
:scale="scale"
|
||||
/>
|
||||
|
||||
<q-img v-else class="q-py-md full-width" :src="currentFile?.url" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-5 full-height column no-wrap">
|
||||
<div
|
||||
class="bordered row items-center justify-between q-pa-sm"
|
||||
style="height: 50px"
|
||||
>
|
||||
{{ $t(currentMode) }}
|
||||
<div class="row" v-if="!hideAction">
|
||||
<UndoButton icon-only type="button" />
|
||||
<SaveButton
|
||||
icon-only
|
||||
type="button"
|
||||
@click="
|
||||
$emit(
|
||||
'save',
|
||||
dropdownList?.[currentIndexDropdownList].value || '',
|
||||
inputFile?.files?.[0],
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="q-pa-sm bordered-r bordered-b full-height col scroll">
|
||||
<slot name="form" :mode="currentMode.split('.').at(-1)" />
|
||||
|
||||
<div class="row items-center">
|
||||
{{ currentFileSelected }}
|
||||
<CloseButton
|
||||
icon-only
|
||||
v-if="!readonly && !!currentFileSelected"
|
||||
type="button"
|
||||
class="q-ml-sm"
|
||||
@click="
|
||||
() => {
|
||||
const tempValue = treeFile.find(
|
||||
(v: any) => v.label === $t(currentMode),
|
||||
);
|
||||
|
||||
if (!tempValue) return;
|
||||
|
||||
const idx = tempValue.file?.findIndex(
|
||||
(v: any) => v.label === currentFileSelected,
|
||||
);
|
||||
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: $t('dialog.title.confirmDelete'),
|
||||
actionText: $t('general.delete'),
|
||||
persistent: true,
|
||||
message: $t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
$emit('deleteFile', currentFileSelected);
|
||||
|
||||
currentFileSelected = tempValue.file?.[idx - 1].label || '';
|
||||
},
|
||||
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<VuePDF
|
||||
style="height: 100%"
|
||||
v-if="
|
||||
currentFile?.url?.split('?').at(0)?.endsWith('.pdf') ||
|
||||
currentFile?.file?.type === 'application/pdf'
|
||||
"
|
||||
class="q-py-md"
|
||||
:pdf="pdf"
|
||||
:page="page"
|
||||
:scale="scale"
|
||||
/>
|
||||
<q-img
|
||||
v-else
|
||||
class="q-py-md full-width"
|
||||
:src="currentFile?.url"
|
||||
style="height: 220px; max-width: 300px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
387
src/components/upload-file/UploadFileGroup.vue
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
<script setup lang="ts">
|
||||
import { QTableProps } from 'quasar';
|
||||
import { ref, toRaw, onMounted } from 'vue';
|
||||
import { dialog } from 'stores/utils';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import TableComponents from 'src/components/TableComponents.vue';
|
||||
import ShowAttachent from 'src/components/ShowAttachent.vue';
|
||||
import DialogForm from 'components/DialogForm.vue';
|
||||
|
||||
const isEdit = ref(false);
|
||||
const { t } = useI18n();
|
||||
const obj = defineModel<
|
||||
{
|
||||
_meta?: Record<string, any>;
|
||||
name?: string;
|
||||
group?: string;
|
||||
url?: string;
|
||||
file?: File;
|
||||
}[]
|
||||
>({
|
||||
default: [],
|
||||
});
|
||||
|
||||
const modalDialog = ref<boolean>(false);
|
||||
|
||||
const splitAttachment = ref<number>(50);
|
||||
|
||||
const currentIndex = ref<number>(-1);
|
||||
|
||||
const statusOcr = ref<boolean>(false);
|
||||
|
||||
const props = defineProps<{
|
||||
ocr?: (
|
||||
group: any,
|
||||
file: File,
|
||||
) => void | Promise<{
|
||||
status: boolean;
|
||||
group: string;
|
||||
meta: { name: string; value: string }[];
|
||||
}>;
|
||||
getFileList?: (group: any) => Promise<typeof obj.value>;
|
||||
deleteItem?: (obj: any) => void | Promise<boolean>;
|
||||
download?: (obj: any) => void;
|
||||
save?: (
|
||||
group: any,
|
||||
meta: any,
|
||||
file: File | undefined,
|
||||
) => void | Promise<boolean>;
|
||||
autoSave?: boolean;
|
||||
readonly?: boolean;
|
||||
hideAction?: boolean;
|
||||
columns: QTableProps['columns'];
|
||||
menu?: { label: string; value: string; _meta?: Record<string, any> }[];
|
||||
}>();
|
||||
|
||||
async function triggerDelete(item: any) {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-alert',
|
||||
title: t('dialog.title.confirmDelete'),
|
||||
actionText: t('general.delete'),
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
if (
|
||||
!props.autoSave ||
|
||||
!obj.value[currentIndex.value]?._meta?.hasOwnProperty('id')
|
||||
) {
|
||||
obj.value.splice(currentIndex.value, 1);
|
||||
} else {
|
||||
await props.deleteItem?.(item);
|
||||
await fileList();
|
||||
}
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
})();
|
||||
|
||||
const selectedMenu = ref<NonNullable<typeof props.menu>[number]>();
|
||||
|
||||
async function change(e: Event) {
|
||||
const _element = e.target as HTMLInputElement | null;
|
||||
const _file = _element?.files?.[0];
|
||||
|
||||
if (_file) {
|
||||
if (!obj.value[currentIndex.value] && selectedMenu.value) {
|
||||
currentIndex.value = obj.value.length;
|
||||
obj.value = [
|
||||
...obj.value,
|
||||
{
|
||||
_meta: structuredClone(toRaw(selectedMenu.value)._meta || {}),
|
||||
group: selectedMenu.value?.value,
|
||||
file: _file,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(_file);
|
||||
reader.onload = () => {
|
||||
if (obj.value[currentIndex.value]) {
|
||||
obj.value[currentIndex.value].url = reader.result as string;
|
||||
}
|
||||
};
|
||||
|
||||
statusOcr.value = true;
|
||||
const resOcr = await props.ocr?.(selectedMenu.value?.value, _file);
|
||||
|
||||
if (resOcr?.status) {
|
||||
modalDialog.value = true;
|
||||
const map = resOcr.meta.reduce<Record<string, string>>((a, c) => {
|
||||
a[c.name] = c.value;
|
||||
return a;
|
||||
}, {});
|
||||
|
||||
if (resOcr.group === 'citizen') {
|
||||
obj.value[currentIndex.value]._meta = {
|
||||
citizenId: map['citizen_id'],
|
||||
firstName: map['firstname'],
|
||||
lastName: map['lastname'],
|
||||
firstNameEN: map['firstname_en'],
|
||||
lastNameEN: map['lastname_en'],
|
||||
birthDate: map['birth_date'],
|
||||
religion: map['religion'],
|
||||
issueDate: map['issue_date'],
|
||||
expireDate: map['expire_date'],
|
||||
};
|
||||
}
|
||||
|
||||
if (resOcr.group === 'passport') {
|
||||
obj.value[currentIndex.value]._meta = {
|
||||
type: map['type'],
|
||||
number: map['passport_no'],
|
||||
issueDate: map['issue_date'],
|
||||
expireDate: map['expire_date'],
|
||||
};
|
||||
}
|
||||
|
||||
if (resOcr.group === 'visa') {
|
||||
obj.value[currentIndex.value]._meta = {
|
||||
type: map['visa_type'],
|
||||
number: map['visa_no'],
|
||||
issueDate: map['valid_until'],
|
||||
expireDate: map['expire_date'],
|
||||
issuePlace: map['issue_place'],
|
||||
};
|
||||
}
|
||||
|
||||
statusOcr.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fileList() {
|
||||
if (props.getFileList) {
|
||||
const res = await props.getFileList(selectedMenu.value?.value);
|
||||
|
||||
if (res && Array.isArray(res)) {
|
||||
obj.value = [...res];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineEmits<{
|
||||
(e: 'submit', obj: any): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative-position full-width row no-wrap wrapper"
|
||||
style="height: 250px"
|
||||
>
|
||||
<div class="col-3 full-height column no-wrap">
|
||||
<div class="q-pa-sm text-center bordered" style="height: 50px">
|
||||
<q-btn-dropdown
|
||||
:disable="readonly"
|
||||
icon="mdi-upload"
|
||||
color="info"
|
||||
:label="$t('general.uploadFile')"
|
||||
>
|
||||
<q-list v-for="(v, i) in menu" :key="v.value">
|
||||
<q-item
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="
|
||||
() => {
|
||||
isEdit = true;
|
||||
selectedMenu = menu?.[i];
|
||||
currentIndex = obj.length;
|
||||
|
||||
browse();
|
||||
}
|
||||
"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t(v.label) }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="bordered-l bordered-b q-pa-sm col full-height scroll">
|
||||
<template v-if="menu != undefined">
|
||||
<q-item
|
||||
clickable
|
||||
v-for="v in menu"
|
||||
:key="v.value"
|
||||
dense
|
||||
type="button"
|
||||
class="no-padding items-center rounded full-width"
|
||||
active-class="menu-active"
|
||||
:active="selectedMenu?.value === v.value"
|
||||
@click="
|
||||
async () => {
|
||||
selectedMenu = v;
|
||||
if (autoSave) {
|
||||
fileList();
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<span class="q-px-md ellipsis q-pa-sm" style="font-size: 16px">
|
||||
{{ $t(v.label) }}
|
||||
</span>
|
||||
</q-item>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="obj"
|
||||
class="bordered col surface-2 column justify-center items-center no-wrap scroll"
|
||||
>
|
||||
<slot name="content">
|
||||
<template v-if="columns !== undefined">
|
||||
<div class="full-height full-width q-pa-md">
|
||||
<TableComponents
|
||||
buttomDownload
|
||||
@download="
|
||||
(index) => {
|
||||
download?.(obj[index]);
|
||||
}
|
||||
"
|
||||
@delete="
|
||||
async (index) => {
|
||||
currentIndex = index;
|
||||
await triggerDelete(obj[index]);
|
||||
}
|
||||
"
|
||||
@view="
|
||||
(index) => {
|
||||
isEdit = false;
|
||||
currentIndex = index;
|
||||
modalDialog = true;
|
||||
}
|
||||
"
|
||||
@edit="
|
||||
(index) => {
|
||||
isEdit = true;
|
||||
currentIndex = index;
|
||||
modalDialog = true;
|
||||
}
|
||||
"
|
||||
:rows="
|
||||
obj
|
||||
.filter((v) => {
|
||||
if (!autoSave && v.group !== selectedMenu?.value) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((v, index) => {
|
||||
return {
|
||||
id: v._meta?.id,
|
||||
branchNo: index + 1,
|
||||
attachmentName: v.file?.name || v.name,
|
||||
uploadDate: '',
|
||||
};
|
||||
})
|
||||
"
|
||||
:columns="columns"
|
||||
></TableComponents>
|
||||
</div>
|
||||
</template>
|
||||
</slot>
|
||||
</div>
|
||||
<q-inner-loading
|
||||
:showing="statusOcr"
|
||||
label="Please wait..."
|
||||
label-class="text-teal"
|
||||
label-style="font-size: 1.1em"
|
||||
/>
|
||||
</div>
|
||||
<DialogForm
|
||||
edit
|
||||
hideDelete
|
||||
:is-edit="isEdit"
|
||||
style="position: absolute"
|
||||
height="100vh"
|
||||
weight="90%"
|
||||
v-model:modal="modalDialog"
|
||||
title="ดูตัวอย่าง"
|
||||
hideCloseEvent
|
||||
v-if="obj.length > 0"
|
||||
:undo="
|
||||
() => {
|
||||
isEdit = false;
|
||||
}
|
||||
"
|
||||
:edit-data="
|
||||
() => {
|
||||
isEdit = !isEdit;
|
||||
}
|
||||
"
|
||||
:close="
|
||||
() => {
|
||||
if (!autoSave || !obj[currentIndex]?._meta?.hasOwnProperty('id')) {
|
||||
obj.splice(currentIndex, 1);
|
||||
}
|
||||
modalDialog = false;
|
||||
}
|
||||
"
|
||||
:submit="
|
||||
async () => {
|
||||
modalDialog = false;
|
||||
if (autoSave === true) {
|
||||
const statusSave = await save?.(
|
||||
obj[currentIndex].group,
|
||||
obj[currentIndex]._meta,
|
||||
obj[currentIndex].file,
|
||||
);
|
||||
|
||||
if (statusSave) {
|
||||
fileList();
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<q-splitter class="full-height" v-model="splitAttachment">
|
||||
<template v-slot:before>
|
||||
<div class="full-height">
|
||||
<ShowAttachent
|
||||
v-if="obj[currentIndex]"
|
||||
:url="obj[currentIndex].url || ''"
|
||||
:file="obj[currentIndex].file"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:after>
|
||||
<div class="q-pa-md">
|
||||
<slot
|
||||
v-if="obj[currentIndex]"
|
||||
name="form"
|
||||
:mode="obj[currentIndex].group"
|
||||
:meta="obj[currentIndex]._meta"
|
||||
:isEdit="isEdit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</q-splitter>
|
||||
</DialogForm>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.wrapper > * {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.menu-active {
|
||||
background-color: hsla(var(--info-bg) / 0.1);
|
||||
color: hsl(var(--info-bg));
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,2 +1,7 @@
|
|||
export { default as UploadFile } from './UploadFile.vue';
|
||||
export { default as UploadFileGroup } from './UploadFileGroup.vue';
|
||||
export { default as FormCitizen } from './FormCitizen.vue';
|
||||
export { default as FormTm6 } from './FormTm6.vue';
|
||||
export { default as CorpFormBusinessRegistration } from './CorpFormBusinessRegistration.vue';
|
||||
export { default as PersFormBusinessRegistration } from './PersFormBusinessRegistration.vue';
|
||||
export { default as noticeJobEmployment } from './noticeJobEmployment.vue';
|
||||
|
|
|
|||
72
src/components/upload-file/noticeJobEmployment.vue
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<script setup lang="ts">
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
|
||||
const permitNumber = defineModel<string>('permitNumber', { default: '' });
|
||||
const jobDescription = defineModel<string>('jobDescription', { default: '' });
|
||||
const workplace = defineModel<string>('workplace', { default: '' });
|
||||
const dateOfHire = defineModel<Date>('dateOfHire');
|
||||
|
||||
defineProps<{
|
||||
prefixId?: string;
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row q-mb-sm" style="gap: 10px">
|
||||
<div class="col-12 text-subtitle1 text-weight-bold">
|
||||
<p>Document Properties</p>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.noticeJobEmployment.permitNumber')"
|
||||
for="input-citizen-id"
|
||||
v-model="permitNumber"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.noticeJobEmployment.jobDescription')"
|
||||
for="input-citizen-id"
|
||||
v-model="jobDescription"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('form.noticeJobEmployment.workplace')"
|
||||
for="input-citizen-id"
|
||||
v-model="workplace"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<DatePicker
|
||||
:label="$t('form.noticeJobEmployment.dateOfHire')"
|
||||
v-model="dateOfHire"
|
||||
class="col-4"
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -19,8 +19,8 @@ html {
|
|||
|
||||
--surface-tab: var(--gray-2);
|
||||
|
||||
--text-mute: var(--stone-5-hsl);
|
||||
--text-mute-2: var(--stone-7-hsl);
|
||||
--text-mute: var(--stone-7-hsl);
|
||||
--text-mute-2: var(--stone-8-hsl);
|
||||
|
||||
--info-fg: 0 0% 100%;
|
||||
--info-bg: var(--blue-6-hsl);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ $info: var(--blue-6);
|
|||
$warning: #ffc224;
|
||||
|
||||
$disabled: var(--stone-4);
|
||||
$input-border: var(--gray-2);
|
||||
|
||||
$separator-color: var(--border-color);
|
||||
$separator-dark-color: var(--border-color);
|
||||
|
|
@ -94,6 +95,21 @@ div.fullscreen.q-drawer__backdrop {
|
|||
color: $disabled;
|
||||
}
|
||||
|
||||
.input-border,
|
||||
.input-border *,
|
||||
[input-border],
|
||||
[input-border] * :not(:deep(.q-checkbox)) {
|
||||
color: hsl(var(--text-mute)) !important;
|
||||
}
|
||||
|
||||
.bg-input-border {
|
||||
background: $input-border;
|
||||
}
|
||||
|
||||
.text-input-border {
|
||||
color: $input-border;
|
||||
}
|
||||
|
||||
.q-field--outlined.q-field--readonly .q-field__control:before {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
|
@ -133,3 +149,11 @@ div.fullscreen.q-drawer__backdrop {
|
|||
.q-tree__node-header:before {
|
||||
top: -32px !important;
|
||||
}
|
||||
|
||||
.q-tree.q-tree--standard.text-transparent {
|
||||
color: hsl(var(--text-mute)) !important;
|
||||
}
|
||||
|
||||
.q-field__control {
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,17 +44,42 @@ export default {
|
|||
recordPerPage: 'Records per page',
|
||||
recordsPage: 'Showing {resultcurrentPage} out of {total} records',
|
||||
day: 'Days',
|
||||
select: 'Select',
|
||||
select: 'Select {msg}',
|
||||
selectAll: 'Select All',
|
||||
additional: 'Additional',
|
||||
editImage: 'Edit Image',
|
||||
upload: 'Upload',
|
||||
upload: 'Upload{msg}',
|
||||
baseOnDevice: 'Base on Device',
|
||||
clickToCreate: 'Click to create',
|
||||
age: 'Age',
|
||||
nationality: 'Nationalality',
|
||||
times: 'No. {number}',
|
||||
uploadFile: 'Upload File',
|
||||
typeBranch: 'Branch Type',
|
||||
branchStatus: 'Branch Status',
|
||||
success: 'Success',
|
||||
taxNo: 'Legal Person',
|
||||
contactName: 'Contact Name',
|
||||
image: 'Image of ',
|
||||
apply: 'Apply',
|
||||
licenseNumber: 'License number',
|
||||
dateOfIssue: 'Date of issue',
|
||||
expirationDate: 'Expiration date',
|
||||
document: 'Document',
|
||||
uploadDate: 'Upload Date',
|
||||
information: '{msg} Information',
|
||||
itemNo: '{msg} No.',
|
||||
example: 'Example',
|
||||
view: 'View {msg}',
|
||||
attachment: 'Attachment',
|
||||
about: 'About',
|
||||
total: 'Total',
|
||||
discount: 'Discount',
|
||||
totalAfterDiscount: 'Total after discount',
|
||||
totalVatExcluded: 'Tax exemption amount',
|
||||
totalVatIncluded: 'Taxable amount',
|
||||
vat: 'VAT {msg}',
|
||||
totalAmount: 'Total amount',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -102,6 +127,27 @@ export default {
|
|||
},
|
||||
|
||||
form: {
|
||||
tm6: {
|
||||
transportation: 'Flight/Vehicle',
|
||||
travelDate: 'Date of Entry',
|
||||
entryCheckpoint: 'Point of Entry',
|
||||
entryCardNumber: 'Entry Card Number',
|
||||
},
|
||||
businessRegistration: {
|
||||
registrationNumber: 'Registration Number',
|
||||
requestAt: 'Request At',
|
||||
businessRegistration: 'Business Registration',
|
||||
businessType: 'Business Type',
|
||||
businessName: 'Name Used for Business',
|
||||
romanCharacters: 'Roman Characters',
|
||||
},
|
||||
noticeJobEmployment: {
|
||||
permitNumber: 'Work Permit Number',
|
||||
jobDescription: 'Job Description',
|
||||
workplace: 'Workplace',
|
||||
dateOfHire: 'Date of Hire',
|
||||
},
|
||||
|
||||
title: {
|
||||
info: '{name}',
|
||||
create: 'Create {name}',
|
||||
|
|
@ -116,8 +162,13 @@ export default {
|
|||
telephone: 'Telephone',
|
||||
gender: 'Gender',
|
||||
address: 'Address {suffix}',
|
||||
addressNo: 'Address No.',
|
||||
moo: 'Moo',
|
||||
soi: 'Soi',
|
||||
road: 'Road',
|
||||
province: 'Province',
|
||||
district: 'District',
|
||||
fullAddress: 'Full Address',
|
||||
subDistrict: 'Sub-district',
|
||||
zipCode: 'Zip Code',
|
||||
prefixName: 'Prefix',
|
||||
|
|
@ -135,7 +186,10 @@ export default {
|
|||
please: 'Please enter {msg} correct information.',
|
||||
invalid: 'Invalid value.',
|
||||
invalidCustomeMessage: 'Invalid value. {msg}',
|
||||
letterOnly: 'Only letters are allowed',
|
||||
letterAndNumOnly: 'Only letters and number are allowed',
|
||||
numOnly: 'Only number are allowed',
|
||||
requireLength: 'Please enter {msg} character',
|
||||
},
|
||||
warning: {
|
||||
title: 'Warning {msg}',
|
||||
|
|
@ -162,14 +216,19 @@ export default {
|
|||
|
||||
branch: {
|
||||
office: 'Office',
|
||||
allBranch: 'All Branch',
|
||||
card: {
|
||||
office: 'Office',
|
||||
orderNumber: 'No.',
|
||||
branchLabelName: 'Name',
|
||||
branchLabelAddress: 'Address',
|
||||
branchLabelTel: 'Telephone',
|
||||
branchLabelType: 'Type',
|
||||
branchVirtual: 'Service Point',
|
||||
branchLabel: 'Branch',
|
||||
branchHQLabel: 'Headoffice',
|
||||
taxNo: 'Legal Person',
|
||||
contactName: 'Contact Name',
|
||||
},
|
||||
page: {
|
||||
captionManage: 'Manage',
|
||||
|
|
@ -247,6 +306,9 @@ export default {
|
|||
checkpoint: 'Checkpoint',
|
||||
checkpointEN: 'Checkpoint (EN)',
|
||||
attachment: 'Attachment Document',
|
||||
citizenId: 'Citizen ID',
|
||||
citizenIssue: 'Citizen Issue',
|
||||
citizenExpire: 'Citizen Expire',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -283,8 +345,8 @@ export default {
|
|||
issueDate: 'Issue Date',
|
||||
passportExpiryDate: 'Passport Expiry Date',
|
||||
|
||||
firstName: 'First Name in Thai',
|
||||
lastName: 'Last Name in Thai',
|
||||
firstName: 'First Name ',
|
||||
lastName: 'Last Name ',
|
||||
firstNameEN: 'First Name in English',
|
||||
lastNameEN: 'Last Name in English',
|
||||
|
||||
|
|
@ -318,6 +380,7 @@ export default {
|
|||
},
|
||||
headQuarters: {
|
||||
title: 'Headoffice',
|
||||
telephoneNo: 'Headoffice Telephone',
|
||||
},
|
||||
businessType: 'Business Type',
|
||||
businessTypeEN: 'Business Type (EN)',
|
||||
|
|
@ -327,17 +390,24 @@ export default {
|
|||
payDay: 'Pay Day',
|
||||
payRate: 'Pay Rate',
|
||||
salesPerson: 'Sales Person',
|
||||
employerName: 'Employer Name',
|
||||
employmentOffice: 'Employment Office',
|
||||
homeCode: 'Address Identification (11 characters)',
|
||||
agent: 'Agent',
|
||||
},
|
||||
|
||||
table: {
|
||||
orderNumber: 'No.',
|
||||
fullname: 'Full Name',
|
||||
titleName: 'Name',
|
||||
businessTypePure: 'Business Type',
|
||||
jobPosition: 'Job Position',
|
||||
address: 'Address',
|
||||
workPlace: 'Workplace',
|
||||
contactName: 'Contact Name',
|
||||
contactPhone: 'Contact Phone',
|
||||
totalEmployee: 'Total Employee',
|
||||
officeTel: 'Headoffice Telephone',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -437,6 +507,8 @@ export default {
|
|||
business: 'Business',
|
||||
contact: 'Contact',
|
||||
attachment: 'Upload Document',
|
||||
remark: 'remark',
|
||||
authorized: 'Authorized',
|
||||
},
|
||||
form: {
|
||||
title: 'Branch',
|
||||
|
|
@ -471,42 +543,82 @@ export default {
|
|||
name: 'Products and Services Types Name',
|
||||
},
|
||||
service: {
|
||||
title: 'Services',
|
||||
title: 'Type',
|
||||
totalWork: 'Total Work',
|
||||
code: 'Services Code',
|
||||
name: 'Services Name',
|
||||
code: 'Type Code',
|
||||
name: 'Type Name',
|
||||
work: 'Work',
|
||||
workName: 'Work Name',
|
||||
showTotalPrice: 'Show Total Price',
|
||||
addTitle: 'Add Services',
|
||||
addTitle: 'Add Type',
|
||||
registeredBranch: 'Registered Branch',
|
||||
information: 'Services Information',
|
||||
information: 'Type Information',
|
||||
workInformation: 'Work Information',
|
||||
serviceProperties: 'Services Properties',
|
||||
serviceProperties: 'Type Properties',
|
||||
propertiesName: 'Properties Name',
|
||||
properties: 'Properties',
|
||||
noProperties: 'No Properties',
|
||||
propertiesInWork: 'Properties in work',
|
||||
productInWork: 'Products in work',
|
||||
totalProductWork: 'Total products of work',
|
||||
productInWork: 'Products and Services in work',
|
||||
totalProductWork: 'Total products and services of work',
|
||||
list: 'Item',
|
||||
addWork: 'Add Work',
|
||||
workAlreadyExist: 'Work already exist',
|
||||
},
|
||||
product: {
|
||||
title: 'Products',
|
||||
code: 'Products Code',
|
||||
name: 'Products Name',
|
||||
title: 'Products and Services',
|
||||
code: 'Products and Services Code',
|
||||
name: 'Products and Services Name',
|
||||
registeredBranch: 'Registered Branch',
|
||||
noProduct: 'No Products',
|
||||
allProduct: 'All Products',
|
||||
addTitle: 'Add Products',
|
||||
noProduct: 'No Products and Services',
|
||||
allProduct: 'All Products and Services',
|
||||
addTitle: 'Add Products and Services',
|
||||
processingTime: 'Processing Time',
|
||||
processingTimeDay: 'Processing Time (Days)',
|
||||
priceInformation: 'Price Information',
|
||||
salePrice: 'Sale Price',
|
||||
agentPrice: 'Agent Price',
|
||||
processingPrice: 'Processing Price',
|
||||
expenseType: 'Expense Type',
|
||||
vatIncluded: 'Include VAT',
|
||||
vatExcluded: 'Exclude VAT',
|
||||
vat: 'VAT',
|
||||
},
|
||||
},
|
||||
|
||||
quotation: {
|
||||
title: 'Quotation',
|
||||
customerName: 'Customer Name',
|
||||
actor: 'Actor',
|
||||
totalPrice: 'Total (Baht)',
|
||||
receipt: 'Receipt/Tax Invoice',
|
||||
branch: 'Branch that issues the quotation',
|
||||
customer: 'Customer',
|
||||
newCustomer: 'New Customer',
|
||||
employeeList: 'Employee List',
|
||||
employee: 'Employee',
|
||||
workName: 'Work Name',
|
||||
contactName: 'Contact Name',
|
||||
documentReceivePoint: 'Document Drop-Off Point"',
|
||||
dueDate: 'Quotation Due Date',
|
||||
|
||||
paymentCondition: 'Payment Terms',
|
||||
payType: 'Payment Methods',
|
||||
bank: 'Select Payment Account',
|
||||
paySplitCount: 'Number of Installments',
|
||||
payTotal: 'Total {msg}',
|
||||
|
||||
summary: 'Total Summary',
|
||||
periodNo: 'Installment No."',
|
||||
amount: 'Amount',
|
||||
payDueDate: 'Pay Due Date',
|
||||
callDueDate: 'Call Due Date',
|
||||
type: {
|
||||
all: 'All',
|
||||
fullAmountCash: 'Full Amount Cash',
|
||||
installmentsCash: 'Installments Cash',
|
||||
fullAmountBill: 'Full Amount Bill',
|
||||
installmentsBill: 'Installments Bill',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -519,6 +631,7 @@ export default {
|
|||
confirmLogout: 'Confirm Logout',
|
||||
},
|
||||
message: {
|
||||
beingUse: '"{msg}" is being used.',
|
||||
incompleteDataEntry: 'Incomplete data entry on {tap} page',
|
||||
confirmChangeStatusOn: 'Do you want to open?',
|
||||
confirmChangeStatusOff: 'Do you want to close?',
|
||||
|
|
@ -594,6 +707,7 @@ export default {
|
|||
validateError: 'Validate Error',
|
||||
codeMisMatch: 'Code Mismatch',
|
||||
userExists: 'User already exits.',
|
||||
crossCompanyNotPermit: 'Cannot move between different headoffice',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,17 +44,42 @@ export default {
|
|||
recordPerPage: 'แสดงทีละ',
|
||||
recordsPage: 'แสดง {resultcurrentPage} รายการจาก {total} รายการ',
|
||||
day: 'วัน',
|
||||
select: 'เลือก',
|
||||
select: 'เลือก{msg}',
|
||||
selectAll: 'เลือกทั้งหมด',
|
||||
additional: 'เพิ่มเติม',
|
||||
editImage: 'แก้ไขรูป',
|
||||
upload: 'อัปโหลด',
|
||||
upload: 'อัปโหลด{msg}',
|
||||
baseOnDevice: 'สีตามอุปกรณ์',
|
||||
clickToCreate: 'กดเพื่อสร้าง',
|
||||
age: 'อายุ',
|
||||
nationality: 'สัญชาติ',
|
||||
times: 'ครั้งที่ {number}',
|
||||
uploadFile: 'อัปโหลดไฟล์',
|
||||
uploadFile: 'อัปโหลดเอกสาร',
|
||||
typeBranch: 'ประเภทสาขา',
|
||||
branchStatus: 'สถานะสาขา',
|
||||
success: 'สำเร็จ',
|
||||
taxNo: 'ทะเบียนนิติบุคคล',
|
||||
contactName: 'ติดต่อ',
|
||||
image: 'รูปภาพ',
|
||||
apply: 'นำไปใช้',
|
||||
licenseNumber: 'เลขที่ใบอนุญาต',
|
||||
dateOfIssue: 'วันที่อนุญาต',
|
||||
expirationDate: 'วันที่หมดอายุ',
|
||||
document: 'ชื่อเอกสาร',
|
||||
uploadDate: 'วันที่อัปโหลด',
|
||||
information: 'ข้อมูล{msg}',
|
||||
itemNo: 'เลขที่{msg}',
|
||||
example: 'ตัวอย่าง',
|
||||
view: 'ดู{msg}',
|
||||
attachment: 'เอกสาร',
|
||||
about: 'เกี่ยวกับ',
|
||||
total: 'ยอดรวม',
|
||||
discount: 'ส่วนลด',
|
||||
totalAfterDiscount: 'จำนวนเงินหลังหักส่วนลด',
|
||||
totalVatExcluded: 'จำนวนเงินยกเว้นภาษี',
|
||||
totalVatIncluded: 'จำนวนเงินที่คำนวณภาษี',
|
||||
vat: 'ภาษีมูลค่าเพิ่ม {msg}',
|
||||
totalAmount: 'จำนวนเงินรวมทั้งสิ้น',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -102,6 +127,26 @@ export default {
|
|||
},
|
||||
|
||||
form: {
|
||||
tm6: {
|
||||
transportation: 'เที่ยวบิน/พาหนะ',
|
||||
travelDate: 'วันที่เดินทางเข้ามา',
|
||||
entryCheckpoint: 'จุดผ่านแดนที่เข้าประเทศ',
|
||||
entryCardNumber: 'หมายเลขบัตรขาเข้า',
|
||||
},
|
||||
businessRegistration: {
|
||||
registrationNumber: 'ทะเบียนเลขที่',
|
||||
requestAt: 'คำขอที่',
|
||||
businessRegistration: 'จดทะเบียนพาณิชย์',
|
||||
businessType: 'ชนิดพาณิชย์',
|
||||
businessName: 'ชื่อที่ใช้ในการประกอบพาณิชย์',
|
||||
romanCharacters: 'อักษรโรมัน',
|
||||
},
|
||||
noticeJobEmployment: {
|
||||
permitNumber: 'หมายเลขใบอนุญาตทำงาน ',
|
||||
jobDescription: 'ลักษณะงาน',
|
||||
workplace: 'สถานที่ทำงาน',
|
||||
dateOfHire: 'วันที่จ้าง',
|
||||
},
|
||||
title: {
|
||||
info: '{name}',
|
||||
create: 'สร้าง {name}',
|
||||
|
|
@ -116,14 +161,19 @@ export default {
|
|||
gender: 'เพศ',
|
||||
telephone: 'เบอร์โทรศัพท์',
|
||||
address: 'ที่อยู่ {suffix}',
|
||||
addressNo: 'บ้านเลขที่',
|
||||
moo: 'หมู่',
|
||||
soi: 'ซอย',
|
||||
road: 'ถนน',
|
||||
province: 'จังหวัด',
|
||||
district: 'อำเภอ',
|
||||
fullAddress: 'ที่อยู่เต็ม',
|
||||
subDistrict: 'ตำบล',
|
||||
zipCode: 'รหัสไปรษณีย์',
|
||||
prefixName: 'คํานําหน้า',
|
||||
firstName: 'ชื่อ ภาษาไทย',
|
||||
firstName: 'ชื่อ ',
|
||||
firstNameEN: 'ชื่อ ภาษาอังกฤษ',
|
||||
lastName: 'นามสกุล ภาษาไทย',
|
||||
lastName: 'นามสกุล ',
|
||||
lastNameEN: 'นามสกุล ภาษาอังกฤษ',
|
||||
middleName: 'ชื่อกลาง',
|
||||
middleNameEN: 'ชื่อกลาง ภาษาอังกฤษ',
|
||||
|
|
@ -135,7 +185,10 @@ export default {
|
|||
please: 'โปรดใส่ข้อมูล{msg}ให้ถูกต้อง',
|
||||
invalid: 'ข้อมูลไม่ถูกต้อง',
|
||||
invalidCustomeMessage: 'ข้อมูลไม่ถูกต้อง {msg}',
|
||||
letterAndNumOnly: 'โปรดใช้เฉพาะภาษาอังกฤษและตัวเลขเท่านั้น',
|
||||
letterOnly: 'โปรดใช้เฉพาะตัวอักษรภาษาอังกฤษเท่านั้น',
|
||||
letterAndNumOnly: 'โปรดใช้เฉพาะตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
|
||||
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
|
||||
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
|
||||
},
|
||||
warning: {
|
||||
title: 'แจ้งเตือน {msg}',
|
||||
|
|
@ -162,14 +215,19 @@ export default {
|
|||
|
||||
branch: {
|
||||
office: 'สำนักงาน',
|
||||
allBranch: 'สาขาทั้งหมด',
|
||||
card: {
|
||||
orderNumber: 'เลขที่',
|
||||
orderNumber: 'ลำดับที่',
|
||||
branchLabelName: 'ชื่อ',
|
||||
office: 'สำนักงาน',
|
||||
branchLabelAddress: 'ที่อยู่',
|
||||
branchLabelTel: 'เบอร์โทรศัพท์',
|
||||
branchLabelTel: 'เบอร์โทรสำนักงาน',
|
||||
branchLabelType: 'ประเภท',
|
||||
branchLabel: 'สาขา',
|
||||
branchVirtual: 'จุดรับบริการ',
|
||||
branchHQLabel: 'สำนักงานใหญ่',
|
||||
taxNo: 'ทะเบียนนิติบุคคล',
|
||||
contactName: 'ติดต่อ',
|
||||
},
|
||||
page: {
|
||||
captionManage: 'จัดการ',
|
||||
|
|
@ -230,8 +288,8 @@ export default {
|
|||
userType: 'ประเภทผู้ใช้งาน',
|
||||
userRole: 'สิทธิ์ผู้ใช้งาน',
|
||||
prefixName: 'คํานําหน้า',
|
||||
firstName: 'ชื่อ ภาษาไทย',
|
||||
lastName: 'นามสกุล ภาษาไทย',
|
||||
firstName: 'ชื่อ ',
|
||||
lastName: 'นามสกุล ',
|
||||
firstNameEN: 'ชื่อ ภาษาอังกฤษ',
|
||||
lastNameEN: 'นามสกุล ภาษาอังกฤษ',
|
||||
middleName: 'ชื่อกลาง',
|
||||
|
|
@ -247,6 +305,9 @@ export default {
|
|||
checkpoint: 'ด่าน',
|
||||
checkpointEN: 'ด่าน ภาษาอังกฤษ',
|
||||
attachment: 'เอกสารประจำตัว',
|
||||
citizenId: 'เลขที่บัตรประชาชน',
|
||||
citizenIssue: 'วันที่ออกบัตร',
|
||||
citizenExpire: 'วันที่หมดอายุ',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -283,8 +344,8 @@ export default {
|
|||
issueDate: 'วันที่ออกหนังสือ',
|
||||
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
|
||||
|
||||
firstName: 'ชื่อ ภาษาไทย',
|
||||
lastName: 'นามสกุล ภาษาไทย',
|
||||
firstName: 'ชื่อ ',
|
||||
lastName: 'นามสกุล ',
|
||||
firstNameEN: 'ชื่อ ภาษาอังกฤษ',
|
||||
lastNameEN: 'นามสกุล ภาษาอังกฤษ',
|
||||
|
||||
|
|
@ -318,26 +379,34 @@ export default {
|
|||
},
|
||||
headQuarters: {
|
||||
title: 'สำนักงานใหญ่',
|
||||
telephoneNo: 'เบอร์โทรศัพท์สำนักงาน',
|
||||
},
|
||||
businessType: 'ประเภทธุรกิจ',
|
||||
businessTypeEN: 'ประเภทธุรกิจ (ภาษาอังกฤษ)',
|
||||
businessType: 'ประเภทกิจการ',
|
||||
businessTypeEN: 'ประเภทกิจการ (ภาษาอังกฤษ)',
|
||||
jobPosition: 'ตำแหน่งงาน',
|
||||
jobPositionEN: 'ตำแหน่งงาน (ภาษาอังกฤษ)',
|
||||
jobDescription: 'รายละเอียดงาน',
|
||||
payDay: 'วันจ่ายเงินเดือน',
|
||||
payRate: 'อัตราค่าจ้าง',
|
||||
payRate: 'อัตราค่าจ้าง/วัน',
|
||||
salesPerson: 'เจ้าหน้าที่ขาย',
|
||||
employerName: 'ชื่อนายจ้าง',
|
||||
employmentOffice: 'สำนักงานจัดหางาน',
|
||||
homeCode: 'รหัสประจำบ้าน (11 หลัก)',
|
||||
agent: 'ตัวแทน',
|
||||
},
|
||||
|
||||
table: {
|
||||
orderNumber: 'ลําดับ',
|
||||
fullname: 'ชื่อ-นามสกุล',
|
||||
titleName: 'ชื่อ บริษัท/นิติบุคคล',
|
||||
businessTypePure: 'ประเภทกิจการ',
|
||||
jobPosition: 'ตำแหน่งงาน',
|
||||
address: 'ที่อยู่',
|
||||
workPlace: 'สถานที่ทํางาน',
|
||||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
contactPhone: 'โทรศัพท์ผู้ติดต่อ',
|
||||
totalEmployee: 'ลูกจ้างทั้งหมด',
|
||||
officeTel: 'เบอร์โทรสำนักงาน',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -349,7 +418,7 @@ export default {
|
|||
passport: 'หนังสือเดินทาง',
|
||||
visa: 'วีซ่า',
|
||||
healthCheck: 'ตรวจสุขภาพ',
|
||||
workHistory: 'ประวัติการทำง่าน',
|
||||
workHistory: 'ประวัติการทำงาน',
|
||||
other: 'อื่นๆ',
|
||||
family: 'ครอบครัว',
|
||||
},
|
||||
|
|
@ -431,10 +500,12 @@ export default {
|
|||
customerBranch: {
|
||||
tab: {
|
||||
main: 'เกี่ยวกับ',
|
||||
address: 'ที่อยู่',
|
||||
business: 'ธุรกิจ',
|
||||
contact: 'ติดต่อ',
|
||||
attachment: 'อัปโหลดเอกสาร',
|
||||
address: 'ที่อยู่นายจ้าง',
|
||||
business: 'ข้อมูลธุรกิจ',
|
||||
contact: 'ข้อมูลติดต่อ',
|
||||
attachment: 'เอกสาร',
|
||||
remark: 'หมายเหตุ',
|
||||
authorized: 'ผู้มีอำนาจลงนาม',
|
||||
},
|
||||
form: {
|
||||
title: 'สาขา',
|
||||
|
|
@ -469,42 +540,82 @@ export default {
|
|||
name: 'ชื่อสินค้าและบริการ',
|
||||
},
|
||||
service: {
|
||||
title: 'บริการ',
|
||||
title: 'ประเภท',
|
||||
totalWork: 'งานทั้งหมด',
|
||||
code: 'รหัสบริการ',
|
||||
name: 'ชื่อบริการ',
|
||||
code: 'รหัสประเภท',
|
||||
name: 'ชื่อประเภท',
|
||||
work: 'งาน',
|
||||
workName: 'ชื่องาน',
|
||||
showTotalPrice: 'แสดงราคารวม',
|
||||
addTitle: 'เพิ่มบริการ',
|
||||
addTitle: 'เพิ่มประเภท',
|
||||
registeredBranch: 'สาขาที่ลงทะเบียน',
|
||||
information: 'ข้อมูลบริการ',
|
||||
information: 'ข้อมูลประเภท',
|
||||
workInformation: 'ข้อมูลงาน',
|
||||
serviceProperties: 'คุณสมบัติของบริการ',
|
||||
serviceProperties: 'คุณสมบัติของประเภท',
|
||||
propertiesName: 'ชื่อคุณสมบัติ',
|
||||
properties: 'คุณสมบัติ',
|
||||
noProperties: 'ยังไม่มีคุณสมบัติ',
|
||||
propertiesInWork: 'คุณสมบัติภายในงาน',
|
||||
productInWork: 'สินค้าภายในงาน',
|
||||
totalProductWork: 'รวมสินค้างาน',
|
||||
productInWork: 'สินค้าและบริการภายในงาน',
|
||||
totalProductWork: 'รวมสินค้าและบริการงาน',
|
||||
list: 'รายการ',
|
||||
addWork: 'เพิ่มงาน',
|
||||
workAlreadyExist: 'งานนี้มีอยู่แล้ว',
|
||||
},
|
||||
product: {
|
||||
title: 'สินค้า',
|
||||
code: 'รหัสสินค้า',
|
||||
name: 'ชื่อสินค้า',
|
||||
title: 'สินค้าและบริการ',
|
||||
code: 'รหัสสินค้าและบริการ',
|
||||
name: 'ชื่อสินค้าและบริการ',
|
||||
registeredBranch: 'สาขาที่ลงทะเบียน',
|
||||
noProduct: 'ยังไม่มีสินค้า',
|
||||
allProduct: 'สินค้าทั้งหมด',
|
||||
addTitle: 'เพิ่มสินค้า',
|
||||
noProduct: 'ยังไม่มีสินค้าและบริการ',
|
||||
allProduct: 'สินค้าและบริการทั้งหมด',
|
||||
addTitle: 'เพิ่มสินค้าและบริการ',
|
||||
processingTime: 'ระยะเวลาดำเนินการ',
|
||||
processingTimeDay: 'ระยะเวลาดำเนินการ (วัน)',
|
||||
priceInformation: 'ข้อมูลราคา',
|
||||
salePrice: 'ราคาขาย',
|
||||
agentPrice: 'ราคาตัวแทน',
|
||||
processingPrice: 'ราคาดำเนินการ',
|
||||
expenseType: 'ประเภทค่าใช้จ่าย',
|
||||
vatIncluded: 'รวม VAT',
|
||||
vatExcluded: 'ไม่รวม VAT',
|
||||
vat: 'คำนวณ VAT',
|
||||
},
|
||||
},
|
||||
|
||||
quotation: {
|
||||
title: 'ใบเสนอราคา',
|
||||
customerName: 'ชื่อลูกค้า',
|
||||
actor: 'ผู้ที่ทำรายงาน',
|
||||
totalPrice: 'ยอดรวมสุทธิ(บาท)',
|
||||
receipt: 'ใบเสร็จ/กำกับภาษี',
|
||||
branch: 'สาขาที่ออกใบเสนอราคา',
|
||||
customer: 'ลูกค้า',
|
||||
newCustomer: 'ลูกค้าใหม่',
|
||||
employeeList: 'รายชื่อแรงงาน',
|
||||
employee: 'แรงงาน',
|
||||
workName: 'ชื่องาน',
|
||||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
documentReceivePoint: 'จุดรับเอกสาร',
|
||||
dueDate: 'วันครบกำหนดใบเสนอราคา',
|
||||
|
||||
paymentCondition: 'เงื่อนไขการชำระเงิน',
|
||||
payType: 'วิธีการชำระเงิน',
|
||||
bank: 'เลือกบัญชีชำระเงิน',
|
||||
paySplitCount: 'จำนวนงวด',
|
||||
payTotal: 'ยอดชำระ {msg}',
|
||||
|
||||
summary: 'สรุปยอดทั้งหมด',
|
||||
periodNo: 'งวดที่',
|
||||
amount: 'จำนวนเงิน',
|
||||
payDueDate: 'วันที่กำหนดจ่าย',
|
||||
callDueDate: 'วันที่ครบกำหนดเรียก',
|
||||
type: {
|
||||
all: 'ทั้งหมด',
|
||||
fullAmountCash: 'เงินสดเต็มจำนวน',
|
||||
installmentsCash: 'เงินสดแบ่งจ่าย',
|
||||
fullAmountBill: 'ใบเรียกเก็บเงินเต็มจำนวน',
|
||||
installmentsBill: 'ใบเรียกเก็บเงินแบ่งจ่าย',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -517,6 +628,7 @@ export default {
|
|||
confirmLogout: 'ยืนยันการออกจากระบบ',
|
||||
},
|
||||
message: {
|
||||
beingUse: '"{msg}" มีการใช้งานอยู่',
|
||||
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
|
||||
confirmChangeStatusOn: 'คุณต้องการเปิดใช่หรือไม่',
|
||||
confirmChangeStatusOff: 'คุณต้องการปิดใช่หรือไม่',
|
||||
|
|
@ -535,16 +647,16 @@ export default {
|
|||
cancel: 'ยกเลิก',
|
||||
},
|
||||
backend: {
|
||||
productGroupNotFound: 'ไม่พบกลุ่มสินค้า',
|
||||
productGroupInUsed: 'กลุ่มสินค้าที่ใช้งานอยู่',
|
||||
productNotFound: 'ไม่พบสินค้า',
|
||||
productInUsed: 'สินค้าใช้งานอยู่',
|
||||
productTypeNotFound: 'ไม่พบประเภทสินค้า',
|
||||
productGroupAssociatedBadReq: 'ไม่พบกลุ่มสินค้าที่เกี่ยวข้อง',
|
||||
productTypeInUsed: 'ประเภทสินค้าใช้งานอยู่',
|
||||
productGroupBadReq: 'ไม่พบกลุ่มสินค้า',
|
||||
productGroupNotFound: 'ไม่พบกลุ่มสินค้าและบริการ',
|
||||
productGroupInUsed: 'กลุ่มสินค้าและบริการที่ใช้งานอยู่',
|
||||
productNotFound: 'ไม่พบสินค้าและบริการ',
|
||||
productInUsed: 'สินค้าและบริการใช้งานอยู่',
|
||||
productTypeNotFound: 'ไม่พบประเภทสินค้าและบริการ',
|
||||
productGroupAssociatedBadReq: 'ไม่พบกลุ่มสินค้าและบริการที่เกี่ยวข้อง',
|
||||
productTypeInUsed: 'ประเภทสินค้าและบริการใช้งานอยู่',
|
||||
productGroupBadReq: 'ไม่พบกลุ่มสินค้าและบริการ',
|
||||
serviceNotFound: 'ไม่พบบริการ',
|
||||
someProductBadReq: 'ไม่พบสินค้าบางส่วน',
|
||||
someProductBadReq: 'ไม่พบสินค้าและบริการบางส่วน',
|
||||
serviceInUsed: 'บริการใช้งานอยู่',
|
||||
workNotFound: 'ไม่พบงาน',
|
||||
workInUsed: 'งานที่ใช้งานอยู่',
|
||||
|
|
@ -589,6 +701,7 @@ export default {
|
|||
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
|
||||
codeMisMatch: 'รหัสไม่ตรงกัน',
|
||||
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
|
||||
crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ onMounted(async () => {
|
|||
role.value.includes('branch_admin') ||
|
||||
role.value.includes('head_of_admin') ||
|
||||
role.value.includes('system') ||
|
||||
role.value.includes('owner')
|
||||
role.value.includes('owner') ||
|
||||
role.value.includes('head_of_account')
|
||||
? false
|
||||
: true,
|
||||
},
|
||||
|
|
@ -79,8 +80,8 @@ onMounted(async () => {
|
|||
{
|
||||
label: 'menu.quotation',
|
||||
icon: 'mdi-file-document',
|
||||
route: '',
|
||||
disabled: true,
|
||||
route: '/quotation',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: 'menu.requestList',
|
||||
|
|
@ -181,10 +182,11 @@ function branchSetting() {}
|
|||
<div id="drawer-menu" class="q-pl-md q-mr-xs">
|
||||
<q-item
|
||||
v-for="v in labelMenu.filter((v) => !v.hidden)"
|
||||
:id="`drawer-${v.label}`"
|
||||
dense
|
||||
clickable
|
||||
class="row items-center q-my-xs q-px-xs q-py-sm"
|
||||
:key="v.label"
|
||||
:key="`drawer-${v.label}`"
|
||||
:disable="!!v.disabled"
|
||||
:class="{
|
||||
active: currentPath === v.route,
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ onMounted(async () => {
|
|||
|
||||
<!-- User -->
|
||||
<ProfileMenu
|
||||
id="btn-profile-menu"
|
||||
@logout="doLogout"
|
||||
@edit-personal-info="console.log('edit')"
|
||||
@signature="
|
||||
|
|
|
|||
|
|
@ -141,12 +141,17 @@ onMounted(async () => {
|
|||
filterRole.value = userRoles
|
||||
.filter(
|
||||
(role) =>
|
||||
role !== 'default-roles-' + getRealm() &&
|
||||
!role.includes('default-roles') &&
|
||||
role !== 'offline_access' &&
|
||||
role !== 'uma_authorization',
|
||||
)
|
||||
.map((role) =>
|
||||
role.replace(/_/g, ' ').replace(/^./, (match) => match.toUpperCase()),
|
||||
role
|
||||
.replace(/_/g, ' ')
|
||||
.toLowerCase()
|
||||
.split(' ')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' '),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -332,7 +337,12 @@ onMounted(async () => {
|
|||
<div class="column col-12">
|
||||
<q-separator />
|
||||
<div class="column justify-center">
|
||||
<q-list :dense="true" v-for="op in options" :key="op.label">
|
||||
<q-list
|
||||
:dense="true"
|
||||
v-for="op in options"
|
||||
:key="op.label"
|
||||
:id="op.label"
|
||||
>
|
||||
<q-item
|
||||
v-if="op.label !== 'menu.profile.mode'"
|
||||
clickable
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { storeToRefs } from 'pinia';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { QTableProps } from 'quasar';
|
||||
|
||||
import { baseUrl } from 'src/stores/utils';
|
||||
import useCustomerStore from 'stores/customer';
|
||||
import useFlowStore from 'stores/flow';
|
||||
import useOptionStore from 'stores/options';
|
||||
|
|
@ -16,6 +17,7 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
|
|||
import { columnsEmployee } from './constant';
|
||||
import { useCustomerBranchForm, useEmployeeForm } from './form';
|
||||
|
||||
import EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue';
|
||||
import ButtonAddComponent from 'components/ButtonAddCompoent.vue';
|
||||
import SideMenu from 'components/SideMenu.vue';
|
||||
import { DialogFormContainer, DialogHeader } from 'components/dialog';
|
||||
|
|
@ -75,6 +77,8 @@ const prop = withDefaults(
|
|||
currentCustomerName?: string;
|
||||
customerType: CustomerType;
|
||||
countEmployee?: number;
|
||||
gender: string;
|
||||
selectedImage: string;
|
||||
}>(),
|
||||
{
|
||||
color: 'green',
|
||||
|
|
@ -248,6 +252,17 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
style="border-radius: 0 0 40px 40px; position: relative"
|
||||
>
|
||||
<q-avatar no-padding size="50px">
|
||||
<q-img
|
||||
:src="`${baseUrl}/customer/${customerId}/image/${selectedImage}`"
|
||||
class="full-height full-width"
|
||||
>
|
||||
<template #error>
|
||||
<q-img
|
||||
:src="`${customerType === 'CORP' ? `/images/customer-CORP-avartar-male.png` : `/images/customer-PERS-avartar-${gender}.png`}`"
|
||||
/>
|
||||
</template>
|
||||
</q-img>
|
||||
|
||||
<img :src="currentCustomerUrlImage ?? '/no-profile.png'" />
|
||||
</q-avatar>
|
||||
</q-card-section>
|
||||
|
|
@ -271,7 +286,6 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
|
||||
<div class="row items-center justify-end col-12 col-md q-py-sm no-wrap">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
outlined
|
||||
dense
|
||||
class="col-6"
|
||||
|
|
@ -286,7 +300,6 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
</q-input>
|
||||
|
||||
<q-select
|
||||
lazy-rules="ondemand"
|
||||
id="select-status"
|
||||
for="select-status"
|
||||
v-model="currentStatus"
|
||||
|
|
@ -422,9 +435,16 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
<div class="col" style="min-width: fit-content">
|
||||
<div class="col">
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
? props.row.registerNameEN
|
||||
: props.row.registerName
|
||||
customerType === 'CORP'
|
||||
? $i18n.locale === 'eng'
|
||||
? props.row.registerNameEN || '-'
|
||||
: props.row.registerName || '-'
|
||||
: $i18n.locale === 'eng'
|
||||
? props.row.firstNameEN +
|
||||
' ' +
|
||||
props.row.lastNameEN || '-'
|
||||
: props.row.firstName + ' ' + props.row.lastName ||
|
||||
'-'
|
||||
}}
|
||||
</div>
|
||||
<div class="col app-text-muted">
|
||||
|
|
@ -608,6 +628,7 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
"
|
||||
@submit="
|
||||
async () => {
|
||||
console.log('asasd');
|
||||
const res = await customerBranchFormStore.submitForm();
|
||||
|
||||
if (res) {
|
||||
|
|
@ -707,54 +728,30 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
</div>
|
||||
</div>
|
||||
<EmployerFormAbout
|
||||
:index="customerBranchFormData.code?.slice(-2) || '00'"
|
||||
class="q-mb-xl"
|
||||
:customer-type="customerType"
|
||||
:customer-name="currentCustomerName"
|
||||
readonly
|
||||
v-model:citizen-id="customerBranchFormData.citizenId"
|
||||
v-model:id="customerBranchFormData.id"
|
||||
v-model:legal-person-no="customerBranchFormData.legalPersonNo"
|
||||
v-model:branch-code="customerBranchFormData.code"
|
||||
v-model:customer-code="customerBranchFormData.code"
|
||||
v-model:register-company-name="
|
||||
customerBranchFormData.registerCompanyName
|
||||
"
|
||||
v-model:register-name="customerBranchFormData.registerName"
|
||||
v-model:register-name-en="customerBranchFormData.registerNameEN"
|
||||
v-model:register-date="customerBranchFormData.registerDate"
|
||||
v-model:authorized-capital="
|
||||
customerBranchFormData.authorizedCapital
|
||||
"
|
||||
/>
|
||||
<div class="row q-col-gutter-sm q-mb-sm" id="employer-branch-address">
|
||||
<div class="col-12 text-weight-bold text-body1 row items-center">
|
||||
<q-icon
|
||||
flat
|
||||
size="xs"
|
||||
class="q-pa-sm rounded q-mr-sm"
|
||||
color="info"
|
||||
name="mdi-office-building-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
<span>{{ $t('customerBranch.tab.address') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<AddressForm
|
||||
prefix-id="employer-branch"
|
||||
class="q-mb-xl"
|
||||
hide-title
|
||||
dense
|
||||
:readonly="customerBranchFormState.dialogType === 'info'"
|
||||
outlined
|
||||
:title="$t('form.address')"
|
||||
v-model:address="customerBranchFormData.address"
|
||||
v-model:addressEN="customerBranchFormData.addressEN"
|
||||
v-model:province-id="customerBranchFormData.provinceId"
|
||||
v-model:district-id="customerBranchFormData.districtId"
|
||||
v-model:sub-district-id="customerBranchFormData.subDistrictId"
|
||||
:addressTitle="$t('form.address')"
|
||||
:addressTitleEN="$t('form.address', { suffix: '(EN)' })"
|
||||
class="q-mb-xl"
|
||||
:index="
|
||||
customerBranchFormData.code &&
|
||||
Number(customerBranchFormData.code.split('-').pop()).toString()
|
||||
"
|
||||
:customer-type="customerType"
|
||||
v-model:citizen-id="customerBranchFormData.citizenId"
|
||||
v-model:prefixName="customerBranchFormData.namePrefix"
|
||||
v-model:firstName="customerBranchFormData.firstName"
|
||||
v-model:lastName="customerBranchFormData.lastName"
|
||||
v-model:firstNameEN="customerBranchFormData.firstNameEN"
|
||||
v-model:lastNameEN="customerBranchFormData.lastNameEN"
|
||||
v-model:gender="customerBranchFormData.gender"
|
||||
v-model:birthDate="customerBranchFormData.birthDate"
|
||||
v-model:customerName="customerBranchFormData.customerName"
|
||||
v-model:legalPersonNo="customerBranchFormData.legalPersonNo"
|
||||
v-model:branchCode="customerBranchFormData.code"
|
||||
v-model:registerName="customerBranchFormData.registerName"
|
||||
v-model:registerNameEN="customerBranchFormData.registerNameEN"
|
||||
v-model:registerDate="customerBranchFormData.registerDate"
|
||||
v-model:authorizedCapital="customerBranchFormData.authorizedCapital"
|
||||
v-model:telephoneNo="customerBranchFormData.telephoneNo"
|
||||
v-model:codeCustomer="customerBranchFormData.codeCustomer"
|
||||
/>
|
||||
<div
|
||||
class="row q-col-gutter-sm q-mb-sm"
|
||||
|
|
@ -778,15 +775,76 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
outlined
|
||||
prefix-id="employer-branch"
|
||||
:readonly="customerBranchFormState.dialogType === 'info'"
|
||||
v-model:employment-office="customerBranchFormData.employmentOffice"
|
||||
v-model:bussiness-type="customerBranchFormData.businessType"
|
||||
v-model:bussiness-type-en="customerBranchFormData.businessTypeEN"
|
||||
v-model:job-position="customerBranchFormData.jobPosition"
|
||||
v-model:job-position-en="customerBranchFormData.jobPositionEN"
|
||||
v-model:job-description="customerBranchFormData.jobDescription"
|
||||
v-model:sale-employee="customerBranchFormData.saleEmployee"
|
||||
v-model:pay-date="customerBranchFormData.payDate"
|
||||
v-model:pay-date-e-n="customerBranchFormData.payDateEN"
|
||||
v-model:wage-rate="customerBranchFormData.wageRate"
|
||||
v-model:wage-rate-text="customerBranchFormData.wageRateText"
|
||||
/>
|
||||
<div class="row q-col-gutter-sm q-mb-sm" id="employer-branch-address">
|
||||
<div class="col-12 text-weight-bold text-body1 row items-center">
|
||||
<q-icon
|
||||
flat
|
||||
size="xs"
|
||||
class="q-pa-sm rounded q-mr-sm"
|
||||
color="info"
|
||||
name="mdi-office-building-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
<span>{{ $t('customerBranch.tab.authorized') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<EmployerFormAuthorized
|
||||
class="q-mb-xl"
|
||||
prefix-id="employer-branch"
|
||||
:readonly="customerBranchFormState.dialogType === 'info'"
|
||||
v-model:authorized-name="customerBranchFormData.authorizedName"
|
||||
v-model:authorized-name-e-n="
|
||||
customerBranchFormData.authorizedNameEN
|
||||
"
|
||||
/>
|
||||
<div class="row q-col-gutter-sm q-mb-sm" id="employer-branch-address">
|
||||
<div class="col-12 text-weight-bold text-body1 row items-center">
|
||||
<q-icon
|
||||
flat
|
||||
size="xs"
|
||||
class="q-pa-sm rounded q-mr-sm"
|
||||
color="info"
|
||||
name="mdi-office-building-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
<span>{{ $t('customerBranch.tab.address') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<AddressForm
|
||||
prefix-id="employer-branch"
|
||||
class="q-mb-xl"
|
||||
hide-title
|
||||
use-employment
|
||||
dense
|
||||
:readonly="customerBranchFormState.dialogType === 'info'"
|
||||
outlined
|
||||
:title="$t('form.address')"
|
||||
v-model:homeCode="customerBranchFormData.homeCode"
|
||||
v-model:employmentOffice="customerBranchFormData.employmentOffice"
|
||||
v-model:employmentOfficeEN="
|
||||
customerBranchFormData.employmentOfficeEN
|
||||
"
|
||||
v-model:address="customerBranchFormData.address"
|
||||
v-model:addressEN="customerBranchFormData.addressEN"
|
||||
v-model:province-id="customerBranchFormData.provinceId"
|
||||
v-model:district-id="customerBranchFormData.districtId"
|
||||
v-model:sub-district-id="customerBranchFormData.subDistrictId"
|
||||
v-model:street="customerBranchFormData.street"
|
||||
v-model:streetEN="customerBranchFormData.streetEN"
|
||||
v-model:moo="customerBranchFormData.moo"
|
||||
v-model:mooEN="customerBranchFormData.mooEN"
|
||||
v-model:soi="customerBranchFormData.soi"
|
||||
v-model:soiEN="customerBranchFormData.soiEN"
|
||||
:addressTitle="$t('form.address')"
|
||||
:addressTitleEN="$t('form.address', { suffix: '(EN)' })"
|
||||
/>
|
||||
<div class="row q-col-gutter-sm q-mb-sm" id="employer-branch-contact">
|
||||
<div class="col-12 text-weight-bold text-body1 row items-center">
|
||||
|
|
@ -804,8 +862,11 @@ watch([customerId, inputSearch, currentStatus], async () => {
|
|||
<EmployerFormContact
|
||||
class="q-mb-lg"
|
||||
:readonly="customerBranchFormState.dialogType === 'info'"
|
||||
v-model:contactName="customerBranchFormData.contactName"
|
||||
v-model:email="customerBranchFormData.email"
|
||||
v-model:telephone="customerBranchFormData.telephoneNo"
|
||||
v-model:contactTel="customerBranchFormData.contactTel"
|
||||
v-model:officeTel="customerBranchFormData.officeTel"
|
||||
v-model:agent="customerBranchFormData.agent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,188 +1,483 @@
|
|||
<script setup lang="ts">
|
||||
import DatePicker from 'src/components/shared/DatePicker.vue';
|
||||
import { formatNumberDecimal } from 'stores/utils';
|
||||
import { QSelect } from 'quasar';
|
||||
import { onMounted, ref, watch, capitalize } from 'vue';
|
||||
import { formatNumberDecimal, selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { calculateAge, disabledAfterToday } from 'src/utils/datetime';
|
||||
|
||||
import useOptionStore from 'src/stores/options';
|
||||
import DatePicker from 'src/components/shared/DatePicker.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
index: string;
|
||||
code?: string;
|
||||
readonly?: boolean;
|
||||
prefixId?: string;
|
||||
customerType?: 'PERS' | 'CORP';
|
||||
}>();
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
||||
// PERS
|
||||
const citizenId = defineModel<string | undefined>('citizenId', {
|
||||
required: true,
|
||||
});
|
||||
const prefixName = defineModel<string | null>('prefixName');
|
||||
const firstName = defineModel<string>('firstName');
|
||||
const lastName = defineModel<string>('lastName');
|
||||
const firstNameEN = defineModel<string>('firstNameEN');
|
||||
const lastNameEN = defineModel<string>('lastNameEN');
|
||||
const gender = defineModel<string>('gender');
|
||||
const birthDate = defineModel<Date | string | null>('birthDate');
|
||||
|
||||
// CORP
|
||||
const customerName = defineModel<string>('customerName');
|
||||
const legalPersonNo = defineModel<string | undefined>('legalPersonNo', {
|
||||
required: true,
|
||||
});
|
||||
const branchCode = defineModel<string | undefined>('branchCode', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const customerCode = defineModel<string | undefined>('customerCode', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const registerName = defineModel<string | undefined>('registerName', {
|
||||
required: true,
|
||||
});
|
||||
const registerNameEN = defineModel<string>('registerNameEn');
|
||||
|
||||
const registerNameEN = defineModel<string | undefined>('registerNameEN', {
|
||||
required: true,
|
||||
});
|
||||
const registerDate = defineModel<Date | null>('registerDate');
|
||||
const authorizedCapital = defineModel<string>('authorizedCapital', {
|
||||
default: '0',
|
||||
});
|
||||
|
||||
defineProps<{
|
||||
index: string;
|
||||
customerName?: string;
|
||||
code?: string;
|
||||
readonly?: boolean;
|
||||
prefixId?: string;
|
||||
customerType?: 'PERS' | 'CORP';
|
||||
}>();
|
||||
// both
|
||||
const telephoneNo = defineModel<string>('telephoneNo');
|
||||
const codeCustomer = defineModel<string | undefined>('codeCustomer', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
onMounted(() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => prefixName.value,
|
||||
(v) => {
|
||||
if (props.readonly) return;
|
||||
if (v === 'mr') gender.value = 'male';
|
||||
else if (v !== '') gender.value = 'female';
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<template v-if="customerType === 'CORP'">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
:label="$t('customer.form.legalPersonNo')"
|
||||
for="input-legal-person-no"
|
||||
v-model="legalPersonNo"
|
||||
/>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6 col-md-5"
|
||||
:label="$t('customer.form.employerName')"
|
||||
for="input-legal-person-no"
|
||||
v-model="customerName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
for="input-branch-code"
|
||||
:label="$t('customer.form.branchCode')"
|
||||
:disable="!readonly"
|
||||
:readonly="readonly"
|
||||
:model-value="`${branchCode?.slice(0, -2) || '-'}${index.toString().padStart(2, '0')}`"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md"
|
||||
:label="$t('customer.form.legalPersonCode')"
|
||||
for="input-legal-person-code"
|
||||
v-model="legalPersonNo"
|
||||
mask="#############"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(val) =>
|
||||
(val && val.length === 13 && /[0-9]+/.test(val)) ||
|
||||
$t('form.error.invalidCustomeMessage', {
|
||||
msg: $t('form.error.requireLength', { msg: 13 }),
|
||||
}),
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
for="input-customer-code"
|
||||
:disable="!readonly"
|
||||
:readonly="readonly"
|
||||
:label="$t('customer.form.customerCode')"
|
||||
:model-value="legalPersonNo"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12 col-md"
|
||||
for="input-customer-code"
|
||||
:disable="!readonly"
|
||||
:readonly="readonly"
|
||||
:label="$t('customer.form.customerCode')"
|
||||
:model-value="legalPersonNo"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
:label="$t('customer.form.legalPersonCode')"
|
||||
for="input-legal-person-code"
|
||||
:model-value="legalPersonNo"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12 col-md"
|
||||
for="input-branch-code"
|
||||
:label="$t('customer.form.branchCode')"
|
||||
:disable="!readonly"
|
||||
:readonly="readonly"
|
||||
:model-value="branchCode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-6"
|
||||
:label="$t('customer.form.registerName')"
|
||||
for="input-register-name"
|
||||
v-model="registerName"
|
||||
/>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-6"
|
||||
:label="$t('customer.form.registerName')"
|
||||
for="input-register-name"
|
||||
v-model="registerName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-6"
|
||||
:label="$t('customer.form.registerNameEN')"
|
||||
for="input-register-name-en"
|
||||
v-model="registerNameEN"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-6"
|
||||
label="Company name"
|
||||
for="input-register-name-en"
|
||||
v-model="registerNameEN"
|
||||
:rules="[
|
||||
(val: string) => !!val || $t('form.error.required'),
|
||||
(val: string) =>
|
||||
/^[0-9A-Za-z\s.,]+$/.test(val) || $t('form.error.letterOnly'),
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
type="text"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
:label="$t('customer.form.authorizedCapital')"
|
||||
for="input-authorized-capital"
|
||||
:model-value="
|
||||
!readonly
|
||||
? authorizedCapital
|
||||
: formatNumberDecimal(+authorizedCapital, 2)
|
||||
"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
authorizedCapital = `${v}`;
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
v-model="registerDate"
|
||||
:id="`${prefixId}-input-register-date`"
|
||||
:label="$t('customer.form.registerDate')"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<DatePicker
|
||||
v-model="registerDate"
|
||||
class="col-6 col-md-2"
|
||||
:id="`${prefixId}-input-register-date`"
|
||||
:label="$t('customer.form.registerDate')"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
type="text"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-2"
|
||||
:label="$t('customer.form.authorizedCapital')"
|
||||
for="input-authorized-capital"
|
||||
:model-value="
|
||||
!readonly
|
||||
? authorizedCapital
|
||||
: formatNumberDecimal(+authorizedCapital, 2)
|
||||
"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
authorizedCapital = `${v}`;
|
||||
}
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6 col-md-3"
|
||||
:label="$t('customer.form.headQuarters.telephoneNo')"
|
||||
for="input-first-name-en"
|
||||
:model-value="readonly ? telephoneNo || '-' : telephoneNo"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (telephoneNo = v) : '')
|
||||
"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="customerType === 'PERS'">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:disable="index !== '0'"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
:label="$t('customer.form.cardNumber')"
|
||||
for="input-legal-person-no"
|
||||
v-model="citizenId"
|
||||
/>
|
||||
<div class="col-7 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-5"
|
||||
for="input-customer-code"
|
||||
:disable="!readonly"
|
||||
:readonly="readonly"
|
||||
:label="$t('customer.form.customerCode')"
|
||||
:model-value="codeCustomer"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:disable="!readonly"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
:label="$t('customer.form.branchCode')"
|
||||
for="input-branch-code"
|
||||
:model-value="`${branchCode?.slice(0, -2) || '-'}${index.toString().padStart(2, '0')}`"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:disable="index !== '0' && !readonly"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-7"
|
||||
:label="$t('customer.form.cardNumber')"
|
||||
for="input-legal-person-no"
|
||||
v-model="citizenId"
|
||||
mask="#############"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(val) =>
|
||||
(val && val.length === 13 && /[0-9]+/.test(val)) ||
|
||||
$t('form.error.invalidCustomeMessage', {
|
||||
msg: $t('form.error.requireLength', { msg: 13 }),
|
||||
}),
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:disable="!readonly"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-3"
|
||||
:label="$t('customer.form.customerCode')"
|
||||
for="input-customer-code"
|
||||
:model-value="citizenId"
|
||||
/>
|
||||
<div class="col-9 row q-col-gutter-sm">
|
||||
<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
|
||||
class="col-md-2 col-6"
|
||||
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`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('personnel.form.firstName')"
|
||||
v-model="firstName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-input
|
||||
:for="`${prefixId}-input-last-name`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('personnel.form.lastName')"
|
||||
v-model="lastName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-9 row q-col-gutter-sm">
|
||||
<q-input
|
||||
:for="`${prefixId}-input-first-name`"
|
||||
dense
|
||||
outlined
|
||||
hide-bottom-space
|
||||
:readonly="readonly"
|
||||
:disable="!readonly"
|
||||
class="col-md-2 col-6"
|
||||
label="Title"
|
||||
:model-value="
|
||||
readonly
|
||||
? capitalize(prefixName || '') || '-'
|
||||
: capitalize(prefixName || '')
|
||||
"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
||||
"
|
||||
@clear="prefixName = ''"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
:for="`${prefixId}-input-first-name`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
label="Name"
|
||||
v-model="firstNameEN"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-input
|
||||
:for="`${prefixId}-input-last-name`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
label="Surname"
|
||||
v-model="lastNameEN"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row col-9 q-col-gutter-sm">
|
||||
<q-input
|
||||
:for="`${prefixId}-input-telephone`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
class="col-md col-6"
|
||||
:label="$t('form.telephone')"
|
||||
:mask="readonly ? '' : '##########'"
|
||||
:model-value="readonly ? telephoneNo || '-' : telephoneNo"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (telephoneNo = v) : '')
|
||||
"
|
||||
@clear="telephoneNo = ''"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<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"
|
||||
class="col-md-2 col-6"
|
||||
dense
|
||||
:readonly="readonly"
|
||||
:options="genderOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:for="`${prefixId}-select-gender`"
|
||||
:label="$t('form.gender')"
|
||||
@filter="genderFilter"
|
||||
:model-value="readonly ? gender || '-' : gender"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (gender = v) : '')
|
||||
"
|
||||
@clear="gender = ''"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<DatePicker
|
||||
v-model="birthDate"
|
||||
class="col-md col-6"
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:readonly="readonly"
|
||||
:label="$t('form.birthDate')"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', { field: $t('form.birthDate') }),
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
:for="`${prefixId}-input-age`"
|
||||
:id="`${prefixId}-input-age`"
|
||||
dense
|
||||
outlined
|
||||
readonly
|
||||
:label="$t('personnel.age')"
|
||||
class="col-md-2 col-12"
|
||||
:model-value="
|
||||
birthDate?.toString() === 'Invalid Date' ||
|
||||
birthDate?.toString() === undefined
|
||||
? ''
|
||||
: calculateAge(birthDate)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
readonly?: boolean;
|
||||
prefixId?: string;
|
||||
}>();
|
||||
const authorizedName = defineModel<string>('authorizedName');
|
||||
const authorizedNameEN = defineModel<string>('authorizedNameEN');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-md-9 col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
:for="`${prefixId}-input-contact-name`"
|
||||
:id="`${prefixId}-input-contact-name`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('customerBranch.tab.authorized')"
|
||||
:model-value="readonly ? authorizedName || '-' : authorizedName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (authorizedName = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
:for="`${prefixId}-input-contact-name`"
|
||||
:id="`${prefixId}-input-contact-name`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="`${$t('customerBranch.tab.authorized')} (EN)`"
|
||||
:model-value="readonly ? authorizedNameEN || '-' : authorizedNameEN"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (authorizedNameEN = v) : '')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,19 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, watch, capitalize } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import { QSelect } from 'quasar';
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { locale } = useI18n();
|
||||
|
||||
import {
|
||||
dateFormat,
|
||||
calculateAge,
|
||||
parseAndFormatDate,
|
||||
disabledAfterToday,
|
||||
} from 'src/utils/datetime';
|
||||
|
||||
import {
|
||||
SaveButton,
|
||||
|
|
@ -21,14 +12,21 @@ import {
|
|||
DeleteButton,
|
||||
UndoButton,
|
||||
} from 'components/button';
|
||||
defineProps<{
|
||||
prefixId?: string;
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
create?: boolean;
|
||||
actionDisabled?: boolean;
|
||||
customerType?: 'CORP' | 'PERS';
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
prefixId?: string;
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
onCreate?: boolean;
|
||||
actionDisabled?: boolean;
|
||||
customerType?: 'CORP' | 'PERS';
|
||||
hideAction?: boolean;
|
||||
}>(),
|
||||
{
|
||||
hideAction: false,
|
||||
},
|
||||
);
|
||||
defineEmits<{
|
||||
(e: 'save'): void;
|
||||
(e: 'edit'): void;
|
||||
|
|
@ -37,20 +35,20 @@ defineEmits<{
|
|||
}>();
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
const code = defineModel<string>('code', { required: true });
|
||||
const namePrefix = defineModel<string | null>('namePrefix');
|
||||
const birthDate = defineModel<Date | string | null>('birthDate');
|
||||
const gender = defineModel<string>('gender');
|
||||
|
||||
const firstName = defineModel<string>('firstName', { required: true });
|
||||
const lastName = defineModel<string>('lastName', { required: true });
|
||||
const firstNameEN = defineModel<string>('firstNameEn', { required: true });
|
||||
const lastNameEN = defineModel<string>('lastNameEn', { required: true });
|
||||
|
||||
const registeredBranchId = defineModel<string>('registeredBranchId', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const customerName = defineModel<string>('customerName', { default: '' });
|
||||
const registerName = defineModel<string>('registerName', { default: '' });
|
||||
|
||||
const citizenId = defineModel<string>('citizenId', { default: '' });
|
||||
const legalPersonNo = defineModel<string>('legalPersonNo', { default: '' });
|
||||
|
||||
const businessType = defineModel<'string'>('businessType');
|
||||
const jobPosition = defineModel<'string'>('jobPosition');
|
||||
const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
|
||||
|
||||
const branchOptions = defineModel<{ id: string; name: string }[]>(
|
||||
'branchOptions',
|
||||
{ default: [] },
|
||||
|
|
@ -80,70 +78,10 @@ watch(
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
const prefixNameOptions = ref<Record<string, unknown>[]>([]);
|
||||
let prefixNameFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
const prefixNameEnOptions = ref<Record<string, unknown>[]>([]);
|
||||
let prefixNameEnFilter: (
|
||||
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;
|
||||
|
||||
onMounted(() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption?.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
prefixNameFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.prefix),
|
||||
prefixNameOptions,
|
||||
'label',
|
||||
);
|
||||
genderFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.gender),
|
||||
genderOptions,
|
||||
'label',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => namePrefix.value,
|
||||
(v) => {
|
||||
if (v === 'mr') gender.value = 'male';
|
||||
else gender.value = 'female';
|
||||
},
|
||||
);
|
||||
|
||||
function formatCode(input: string | undefined, type: 'code' | 'number') {
|
||||
if (!input) return;
|
||||
return input.slice(...(type === 'code' ? [0, -6] : [-6]));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row q-col-gutter-sm q-mb-sm">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12 text-weight-bold text-body1 row items-center">
|
||||
<q-icon
|
||||
flat
|
||||
|
|
@ -153,8 +91,18 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
name="mdi-office-building-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
<span>{{ $t('form.field.basicInformation') }}</span>
|
||||
<EditButton
|
||||
<span>
|
||||
{{
|
||||
$t('general.information', {
|
||||
msg: `${$t('customer.employer')}${
|
||||
customerType === 'CORP'
|
||||
? $t('customer.employerLegalEntity')
|
||||
: $t('customer.employerNaturalPerson')
|
||||
}`,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<!-- <EditButton
|
||||
icon-only
|
||||
v-if="readonly && !create"
|
||||
type="button"
|
||||
|
|
@ -184,295 +132,158 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
@click="$emit('save')"
|
||||
type="submit"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
/> -->
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
dense
|
||||
class="col-12 col-md-7"
|
||||
option-value="id"
|
||||
input-debounce="0"
|
||||
option-label="name"
|
||||
lazy-rules="ondemand"
|
||||
v-model="registeredBranchId"
|
||||
:readonly="readonly"
|
||||
:options="filteredBranchOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:label="$t('customer.form.registeredBranch')"
|
||||
:for="`${prefixId}-input-source-nationality`"
|
||||
:rules="[
|
||||
(val) => {
|
||||
const roles = getRole() || [];
|
||||
return (
|
||||
['admin', 'system', 'head_of_admin'].some((v) =>
|
||||
roles.includes(v),
|
||||
) ||
|
||||
!!val ||
|
||||
$t('form.error.required')
|
||||
);
|
||||
},
|
||||
]"
|
||||
@filter="branchFilter"
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-select
|
||||
outlined
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
dense
|
||||
class="col-6"
|
||||
option-value="id"
|
||||
input-debounce="0"
|
||||
option-label="name"
|
||||
v-model="registeredBranchId"
|
||||
:readonly="readonly"
|
||||
:options="filteredBranchOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:label="$t('customer.form.registeredBranch')"
|
||||
:for="`${prefixId}-input-source-nationality`"
|
||||
:rules="[
|
||||
(val) => {
|
||||
const roles = getRole() || [];
|
||||
return (
|
||||
['admin', 'system', 'head_of_admin'].some((v) =>
|
||||
roles.includes(v),
|
||||
) ||
|
||||
!!val ||
|
||||
$t('form.error.required')
|
||||
);
|
||||
},
|
||||
]"
|
||||
@filter="branchFilter"
|
||||
>
|
||||
<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
|
||||
v-if="customerType === 'CORP' && !onCreate"
|
||||
class="row col-12 q-col-gutter-sm"
|
||||
>
|
||||
<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>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-5"
|
||||
:label="$t('customer.form.customerName')"
|
||||
for="input-first-name"
|
||||
v-model="registerName"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6 col-md-3"
|
||||
:label="$t('general.taxNo')"
|
||||
for="input-first-name-en"
|
||||
v-model="legalPersonNo"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6 col-md-3"
|
||||
:label="$t('customer.table.businessTypePure')"
|
||||
for="input-first-name-en"
|
||||
:model-value="optionStore.mapOption(businessType)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<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"
|
||||
lazy-rules="ondemand"
|
||||
hide-dropdown-icon
|
||||
class="col-12 col-md-2"
|
||||
dense
|
||||
:readonly="readonly"
|
||||
:options="prefixNameOptions"
|
||||
:for="`${prefixId}-select-prefix-name`"
|
||||
:label="$t('form.prefixName')"
|
||||
@filter="prefixNameFilter"
|
||||
:model-value="readonly ? namePrefix || '-' : namePrefix"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
typeof v === 'string' ? (namePrefix = v) : '';
|
||||
}
|
||||
"
|
||||
@clear="namePrefix = ''"
|
||||
:rules="[
|
||||
(val) => {
|
||||
const roles = getRole() || [];
|
||||
return !!val || $t('form.error.required');
|
||||
},
|
||||
]"
|
||||
<div
|
||||
v-if="customerType === 'PERS' && !onCreate"
|
||||
class="row col-12 q-col-gutter-sm"
|
||||
>
|
||||
<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
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-6"
|
||||
:label="$t('customer.form.employerName')"
|
||||
for="input-first-name"
|
||||
v-model="customerName"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
class="col-md-3 col-6"
|
||||
hide-bottom-space
|
||||
v-model="citizenId"
|
||||
mask="#############"
|
||||
:readonly="readonly"
|
||||
dense
|
||||
:label="$t('personnel.form.citizenId')"
|
||||
for="input-citizen-id"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6 col-md-3"
|
||||
:label="$t('customer.table.businessTypePure')"
|
||||
for="input-first-name-en"
|
||||
:model-value="optionStore.mapOption(businessType)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-5"
|
||||
:label="$t('customer.form.firstName')"
|
||||
for="input-first-name"
|
||||
v-model="firstName"
|
||||
:rules="[
|
||||
(val) => {
|
||||
const roles = getRole() || [];
|
||||
return !!val || $t('form.error.required');
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-5"
|
||||
:label="$t('customer.form.lastName')"
|
||||
for="input-last-name"
|
||||
v-model="lastName"
|
||||
:rules="[
|
||||
(val) => {
|
||||
const roles = getRole() || [];
|
||||
return !!val || $t('form.error.required');
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:disable="!readonly"
|
||||
readonly
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-2"
|
||||
:label="$t('customer.form.prefixName')"
|
||||
for="input-prefix-name"
|
||||
:model-value="
|
||||
readonly
|
||||
? capitalize(namePrefix || '') || '-'
|
||||
: capitalize(namePrefix || '')
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-5"
|
||||
:label="$t('customer.form.firstNameEN')"
|
||||
for="input-first-name-en"
|
||||
v-model="firstNameEN"
|
||||
/>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-5"
|
||||
:label="$t('customer.form.lastNameEN')"
|
||||
for="input-last-name-en"
|
||||
v-model="lastNameEN"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<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"
|
||||
lazy-rules="ondemand"
|
||||
class="col-12 col-md-2"
|
||||
dense
|
||||
:readonly="readonly"
|
||||
:options="genderOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:for="`${prefixId}-select-gender`"
|
||||
:label="$t('form.gender')"
|
||||
@filter="genderFilter"
|
||||
:model-value="readonly ? gender || '-' : gender"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (gender = v) : '')"
|
||||
@clear="gender = ''"
|
||||
:rules="[
|
||||
(val) => {
|
||||
const roles = getRole() || [];
|
||||
return !!val || $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>
|
||||
|
||||
<VueDatePicker
|
||||
:id="`${prefixId}-input-birth-date`"
|
||||
:for="`${prefixId}-input-birth-date`"
|
||||
utc
|
||||
autoApply
|
||||
v-model="birthDate"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
:teleport="true"
|
||||
:dark="$q.dark.isActive"
|
||||
:locale="$i18n.locale === 'tha' ? 'th' : 'en'"
|
||||
:enableTimePicker="false"
|
||||
:disabled="readonly"
|
||||
class="col-12 col-md-3"
|
||||
>
|
||||
<template #year="{ value }">
|
||||
{{ $i18n.locale === 'tha' ? value + 543 : value }}
|
||||
</template>
|
||||
<template #year-overlay-value="{ value }">
|
||||
{{ $i18n.locale === 'tha' ? value + 543 : value }}
|
||||
</template>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-birth-date`"
|
||||
hide-bottom-space
|
||||
placeholder="DD/MM/YYYY"
|
||||
:label="$t('form.birthDate')"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val || $t('selectValidate') + $t('form.birthDate'),
|
||||
]"
|
||||
:mask="readonly ? '' : '##/##/####'"
|
||||
:model-value="
|
||||
birthDate && readonly
|
||||
? dateFormat(birthDate)
|
||||
: dateFormat(birthDate, false, false, true)
|
||||
"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
if (v && v.toString().length === 10) {
|
||||
const today = new Date();
|
||||
const date = parseAndFormatDate(v, locale);
|
||||
birthDate = date && date > today ? today : date;
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-calendar-blank-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
</VueDatePicker>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-age`"
|
||||
:id="`${prefixId}-input-age`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
:label="$t('general.age')"
|
||||
class="col-12 col-md-2"
|
||||
:model-value="
|
||||
birthDate?.toString() === 'Invalid Date' ||
|
||||
birthDate?.toString() === undefined
|
||||
? ''
|
||||
: calculateAge(birthDate)
|
||||
"
|
||||
/>
|
||||
<div v-if="!onCreate" class="row col-12 q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6 col-md-5"
|
||||
:label="$t('customer.form.jobPosition')"
|
||||
for="input-first-name-en"
|
||||
:model-value="optionStore.mapOption(jobPosition)"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6 col-md-3"
|
||||
:label="$t('customer.form.headQuarters.telephoneNo')"
|
||||
for="input-first-name-en"
|
||||
v-model="telephoneNo"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,27 +5,30 @@ import EmployerFormBusiness from './EmployerFormBusiness.vue';
|
|||
import EmployerFormContact from './EmployerFormContact.vue';
|
||||
import { CustomerCreate } from 'stores/customer/types';
|
||||
import EmployerFormAbout from './EmployerFormAbout.vue';
|
||||
import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
|
||||
import { useCustomerForm } from 'src/pages/03_customer-management/form';
|
||||
|
||||
const customerFormStore = useCustomerForm();
|
||||
import { FormCitizen } from 'components/upload-file/';
|
||||
import { waitAll } from 'src/stores/utils';
|
||||
import {
|
||||
FormCitizen,
|
||||
CorpFormBusinessRegistration,
|
||||
PersFormBusinessRegistration,
|
||||
} from 'components/upload-file/';
|
||||
|
||||
import useOcrStore from 'stores/ocr';
|
||||
import useCustomerStore from 'stores/customer';
|
||||
|
||||
const ocrStore = useOcrStore();
|
||||
import {
|
||||
SaveButton,
|
||||
EditButton,
|
||||
DeleteButton,
|
||||
UndoButton,
|
||||
} from 'components/button';
|
||||
import UploadFile from 'src/components/upload-file/UploadFile.vue';
|
||||
import { uploadFileListCustomer } from '../../constant';
|
||||
|
||||
const statusOcr = ref(false);
|
||||
|
||||
const customer = defineModel<CustomerCreate>('customer', { required: true });
|
||||
import { UploadFileGroup } from 'src/components/upload-file/';
|
||||
import { uploadFileListCustomer, columnsAttachment } from '../../constant';
|
||||
import { group } from 'node:console';
|
||||
|
||||
const ocrStore = useOcrStore();
|
||||
const customerStore = useCustomerStore();
|
||||
const item = defineModel<NonNullable<CustomerCreate['customerBranch']>[number]>(
|
||||
'customerBranch',
|
||||
{ required: true },
|
||||
|
|
@ -40,14 +43,21 @@ defineEmits<{
|
|||
(e: 'delete'): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
index: number;
|
||||
customerName: string;
|
||||
readonly?: boolean;
|
||||
prefixId?: string;
|
||||
actionDisabled?: boolean;
|
||||
customerType?: 'CORP' | 'PERS';
|
||||
}>();
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
index: number;
|
||||
customerName: string;
|
||||
readonly?: boolean;
|
||||
prefixId?: string;
|
||||
onCreate?: boolean;
|
||||
actionDisabled?: boolean;
|
||||
customerType?: 'CORP' | 'PERS';
|
||||
hideAction?: boolean;
|
||||
}>(),
|
||||
{
|
||||
hideAction: false,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -55,42 +65,44 @@ defineProps<{
|
|||
class="col-12 text-weight-bold row items-center q-mb-sm"
|
||||
:style="{ opacity: actionDisabled ? '.5' : undefined }"
|
||||
:id="`form-branch-customer-no-${index}`"
|
||||
v-if="!hideAction"
|
||||
>
|
||||
{{
|
||||
index === 0
|
||||
? $t('customer.form.headQuarters.title')
|
||||
: $t('customer.form.branch.title', { name: index || 0 })
|
||||
}}
|
||||
<EditButton
|
||||
icon-only
|
||||
v-if="readonly"
|
||||
@click="$emit('edit')"
|
||||
class="q-ml-auto"
|
||||
type="button"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
<DeleteButton
|
||||
icon-only
|
||||
v-if="readonly"
|
||||
@click="$emit('delete')"
|
||||
type="button"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
<UndoButton
|
||||
icon-only
|
||||
v-if="!readonly"
|
||||
@click="$emit('cancel')"
|
||||
type="button"
|
||||
class="q-ml-auto"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
<SaveButton
|
||||
icon-only
|
||||
v-if="!readonly"
|
||||
@click="$emit('save')"
|
||||
type="submit"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
<div class="q-ml-auto row no-warp">
|
||||
<EditButton
|
||||
icon-only
|
||||
v-if="readonly"
|
||||
@click="$emit('edit')"
|
||||
class="q-ml-auto"
|
||||
type="button"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
<DeleteButton
|
||||
icon-only
|
||||
v-if="readonly"
|
||||
@click="$emit('delete')"
|
||||
type="button"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
<UndoButton
|
||||
icon-only
|
||||
v-if="!readonly && !onCreate"
|
||||
@click="$emit('cancel')"
|
||||
type="button"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
<SaveButton
|
||||
v-if="!readonly"
|
||||
icon-only
|
||||
@click="$emit('save')"
|
||||
type="submit"
|
||||
:disabled="actionDisabled"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<div class="col-12" :style="{ opacity: actionDisabled ? '.5' : undefined }">
|
||||
|
|
@ -102,50 +114,43 @@ defineProps<{
|
|||
class="bordered-b"
|
||||
active-color="primary"
|
||||
no-caps
|
||||
style="color: hsl(var(--text-mute))"
|
||||
>
|
||||
<q-tab name="main" :label="$t('customerBranch.tab.main')" />
|
||||
<q-tab name="address" :label="$t('customerBranch.tab.address')" />
|
||||
<q-tab name="business" :label="$t('customerBranch.tab.business')" />
|
||||
<q-tab
|
||||
v-if="customerType === 'CORP'"
|
||||
name="authorized"
|
||||
:label="$t('customerBranch.tab.authorized')"
|
||||
/>
|
||||
<q-tab name="address" :label="$t('customerBranch.tab.address')" />
|
||||
<q-tab name="contact" :label="$t('customerBranch.tab.contact')" />
|
||||
<q-tab name="attachment" :label="$t('customerBranch.tab.attachment')" />
|
||||
</q-tabs>
|
||||
<q-tab-panels v-model="tab">
|
||||
<q-tab-panel name="main">
|
||||
<EmployerFormAbout
|
||||
:prefixId="prefixId"
|
||||
:index="index.toString()"
|
||||
:readonly="readonly"
|
||||
:customer-type="customerType"
|
||||
:customer-name="customerName"
|
||||
v-model:citizen-id="item.citizenId"
|
||||
v-model:id="item.id"
|
||||
v-model:legal-person-no="item.legalPersonNo"
|
||||
v-model:branch-code="item.code"
|
||||
v-model:customer-code="customer.code"
|
||||
v-model:register-company-name="item.registerCompanyName"
|
||||
v-model:register-name="item.registerName"
|
||||
v-model:register-name-en="item.registerNameEN"
|
||||
v-model:register-date="item.registerDate"
|
||||
v-model:authorized-capital="item.authorizedCapital"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="address">
|
||||
<AddressForm
|
||||
use-work-place
|
||||
:prefix-id="prefixId || 'employer'"
|
||||
hide-title
|
||||
dense
|
||||
:readonly="readonly"
|
||||
outlined
|
||||
:title="$t('form.address')"
|
||||
v-model:workplace="item.workplace"
|
||||
v-model:workplace-en="item.workplaceEN"
|
||||
v-model:address="item.address"
|
||||
v-model:addressEN="item.addressEN"
|
||||
v-model:province-id="item.provinceId"
|
||||
v-model:district-id="item.districtId"
|
||||
v-model:sub-district-id="item.subDistrictId"
|
||||
:addressTitle="$t('form.address')"
|
||||
:addressTitleEN="$t('form.address', { suffix: '(EN)' })"
|
||||
v-model:prefixName="item.namePrefix"
|
||||
v-model:firstName="item.firstName"
|
||||
v-model:lastName="item.lastName"
|
||||
v-model:firstNameEN="item.firstNameEN"
|
||||
v-model:lastNameEN="item.lastNameEN"
|
||||
v-model:gender="item.gender"
|
||||
v-model:birthDate="item.birthDate"
|
||||
v-model:customerName="item.customerName"
|
||||
v-model:legalPersonNo="item.legalPersonNo"
|
||||
v-model:branchCode="item.code"
|
||||
v-model:registerName="item.registerName"
|
||||
v-model:registerNameEN="item.registerNameEN"
|
||||
v-model:registerDate="item.registerDate"
|
||||
v-model:authorizedCapital="item.authorizedCapital"
|
||||
v-model:telephoneNo="item.telephoneNo"
|
||||
v-model:codeCustomer="item.codeCustomer"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="business">
|
||||
|
|
@ -154,120 +159,283 @@ defineProps<{
|
|||
outlined
|
||||
:prefix-id="prefixId || 'employer'"
|
||||
:readonly="readonly"
|
||||
v-model:employment-office="item.employmentOffice"
|
||||
v-model:bussiness-type="item.businessType"
|
||||
v-model:bussiness-type-en="item.businessTypeEN"
|
||||
v-model:job-position="item.jobPosition"
|
||||
v-model:job-position-en="item.jobPositionEN"
|
||||
v-model:job-description="item.jobDescription"
|
||||
v-model:sale-employee="item.saleEmployee"
|
||||
v-model:pay-date="item.payDate"
|
||||
v-model:pay-date-e-n="item.payDateEN"
|
||||
v-model:wage-rate="item.wageRate"
|
||||
v-model:wage-rate-text="item.wageRateText"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel v-if="customerType === 'CORP'" name="authorized">
|
||||
<EmployerFormAuthorized
|
||||
:prefix-id="prefixId || 'employer'"
|
||||
:readonly="readonly"
|
||||
v-model:authorized-name="item.authorizedName"
|
||||
v-model:authorized-name-e-n="item.authorizedNameEN"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="address">
|
||||
<AddressForm
|
||||
:prefix-id="prefixId || 'employer'"
|
||||
hide-title
|
||||
dense
|
||||
outlined
|
||||
use-employment
|
||||
:readonly="readonly"
|
||||
:title="$t('form.address')"
|
||||
v-model:homeCode="item.homeCode"
|
||||
v-model:employmentOffice="item.employmentOffice"
|
||||
v-model:employmentOfficeEN="item.employmentOfficeEN"
|
||||
v-model:address="item.address"
|
||||
v-model:addressEN="item.addressEN"
|
||||
v-model:street="item.street"
|
||||
v-model:streetEN="item.streetEN"
|
||||
v-model:moo="item.moo"
|
||||
v-model:mooEN="item.mooEN"
|
||||
v-model:soi="item.soi"
|
||||
v-model:soiEN="item.soiEN"
|
||||
v-model:province-id="item.provinceId"
|
||||
v-model:district-id="item.districtId"
|
||||
v-model:sub-district-id="item.subDistrictId"
|
||||
:addressTitle="$t('form.address')"
|
||||
:addressTitleEN="$t('form.address', { suffix: '(EN)' })"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="contact">
|
||||
<EmployerFormContact
|
||||
:readonly="readonly"
|
||||
:prefixId="prefixId"
|
||||
v-model:contactName="item.contactName"
|
||||
v-model:email="item.email"
|
||||
v-model:telephone="item.telephoneNo"
|
||||
v-model:contactTel="item.contactTel"
|
||||
v-model:officeTel="item.officeTel"
|
||||
v-model:agent="item.agent"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="attachment">
|
||||
<UploadFile
|
||||
<UploadFileGroup
|
||||
v-model:current-id="item.id"
|
||||
v-model="item.file"
|
||||
hide-action
|
||||
:readonly="readonly"
|
||||
:dropdown-list="uploadFileListCustomer"
|
||||
v-model:status-ocr="statusOcr"
|
||||
v-model:file="item.file"
|
||||
:tree-file="
|
||||
Object.values(
|
||||
item.file?.reduce<
|
||||
Record<string, { label: string; file: { label: string }[] }>
|
||||
>((a, c) => {
|
||||
const _group = c.group || 'other';
|
||||
|
||||
if (!a[_group]) {
|
||||
a[_group] = {
|
||||
label: $t(
|
||||
uploadFileListCustomer.find((v) => v.value === _group)
|
||||
?.label || _group,
|
||||
),
|
||||
file: [
|
||||
{
|
||||
label:
|
||||
c.name ||
|
||||
`${c.group}-${c.file?.name || Date.now()}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
a[_group].file.push({
|
||||
label:
|
||||
c.name || `${c.group}-${c.file?.name || Date.now()}`,
|
||||
});
|
||||
}
|
||||
return a;
|
||||
}, {}) || {},
|
||||
)
|
||||
"
|
||||
@send-ocr="
|
||||
async (group: any, file: any) => {
|
||||
:ocr="
|
||||
async (group, file) => {
|
||||
const res = await ocrStore.sendOcr({
|
||||
file: file,
|
||||
category: group,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
const map = res.fields.reduce<Record<string, string>>(
|
||||
(a, c) => {
|
||||
a[c.name] = c.value;
|
||||
return a;
|
||||
},
|
||||
{},
|
||||
);
|
||||
if (!item.citizenId) item.citizenId = map['citizen_id'] || '';
|
||||
if (!item.address) item.address = map['address'] || '';
|
||||
if (!customer.firstName)
|
||||
customer.firstName = map['firstname'] || '';
|
||||
if (!customer.lastName)
|
||||
customer.lastName = map['lastname'] || '';
|
||||
if (!customer.firstNameEN)
|
||||
customer.firstNameEN = map['firstname_en'] || '';
|
||||
if (!customer.lastNameEN)
|
||||
customer.lastNameEN = map['lastname_en'] || '';
|
||||
if (!customer.birthDate)
|
||||
customer.birthDate = new Date(map['birth_date'] || '');
|
||||
}
|
||||
const tempValue = {
|
||||
status: true,
|
||||
group,
|
||||
meta: res.fields,
|
||||
};
|
||||
|
||||
statusOcr = false;
|
||||
return tempValue;
|
||||
}
|
||||
return { status: false, group, meta: [] };
|
||||
}
|
||||
"
|
||||
@delete-file="
|
||||
(filename) => {
|
||||
if (!item.id) return;
|
||||
:menu="uploadFileListCustomer"
|
||||
:columns="columnsAttachment"
|
||||
:auto-save="item.id !== ''"
|
||||
:download="
|
||||
(obj) => {
|
||||
customerStore.getFile({
|
||||
parentId: item.id || '',
|
||||
group: obj.group,
|
||||
fileId: obj._meta.id,
|
||||
download: true,
|
||||
});
|
||||
}
|
||||
"
|
||||
:delete-item="
|
||||
async (obj) => {
|
||||
const res = await customerStore.delMeta({
|
||||
parentId: item.id || '',
|
||||
group: obj.group,
|
||||
metaId: obj._meta.id,
|
||||
});
|
||||
|
||||
customerFormStore.deleteAttachment(
|
||||
{ branchId: item.id, customerId: item.customerId },
|
||||
filename,
|
||||
);
|
||||
if (res) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
"
|
||||
:save="
|
||||
async (
|
||||
group:
|
||||
| 'citizen'
|
||||
| 'house-registration'
|
||||
| 'commercial-registration'
|
||||
| 'vat-registration'
|
||||
| 'power-of-attorney',
|
||||
_meta: any,
|
||||
file: File | undefined,
|
||||
) => {
|
||||
if (file !== undefined && item.id) {
|
||||
const res = await customerStore.postMeta({
|
||||
parentId: item.id || '',
|
||||
group,
|
||||
meta: _meta,
|
||||
file,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
const {
|
||||
customerBranchId,
|
||||
id,
|
||||
employeeId,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
middleName,
|
||||
middleNameEN,
|
||||
|
||||
...payload
|
||||
} = _meta;
|
||||
const res = await customerStore.putMeta({
|
||||
parentId: item.id || '',
|
||||
group,
|
||||
metaId: _meta.id,
|
||||
meta: payload,
|
||||
file,
|
||||
});
|
||||
if (res) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
"
|
||||
:get-file-list="
|
||||
async (
|
||||
group:
|
||||
| 'citizen'
|
||||
| 'house-registration'
|
||||
| 'commercial-registration'
|
||||
| 'vat-registration'
|
||||
| 'power-of-attorney',
|
||||
) => {
|
||||
if (!!item.id) {
|
||||
if (group === 'citizen') {
|
||||
const resMeta = await customerStore.getMetaList({
|
||||
parentId: item.id,
|
||||
group,
|
||||
});
|
||||
|
||||
const tempValue = resMeta.map(async (v: any) => {
|
||||
return {
|
||||
_meta: { ...v },
|
||||
name: v.id || '',
|
||||
group: group,
|
||||
url: await customerStore.getFile({
|
||||
parentId: item.id || '',
|
||||
group,
|
||||
fileId: v.id,
|
||||
}),
|
||||
file: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
return await waitAll(tempValue);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #form="{ mode }">
|
||||
<template #form="{ mode, meta, isEdit }">
|
||||
<FormCitizen
|
||||
v-if="mode === 'citizenId'"
|
||||
v-if="mode === 'citizen' && meta"
|
||||
orc
|
||||
v-model:citizen-id="item.citizenId"
|
||||
v-model:birth-date="customer.birthDate"
|
||||
v-model:first-name="customer.firstName"
|
||||
v-model:first-name-en="customer.firstNameEN"
|
||||
v-model:last-name="customer.lastName"
|
||||
v-model:last-name-en="customer.lastNameEN"
|
||||
v-model:address="item.address"
|
||||
ra
|
||||
:readonly="!isEdit"
|
||||
v-model:name-prefix="meta.namePrefix"
|
||||
v-model:citizen-id="meta.citizenId"
|
||||
v-model:birth-date="meta.birthDate"
|
||||
v-model:first-name="meta.firstName"
|
||||
v-model:first-name-en="meta.firstNameEN"
|
||||
v-model:last-name="meta.lastName"
|
||||
v-model:last-name-en="meta.lastNameEN"
|
||||
v-model:gender="meta.gender"
|
||||
v-model:religion="meta.religion"
|
||||
v-model:nationality="meta.nationality"
|
||||
v-model:expire-date="meta.expireDate"
|
||||
v-model:issue-date="meta.issueDate"
|
||||
v-model:homeCode="meta.homeCode"
|
||||
v-model:employmentOffice="meta.employmentOffice"
|
||||
v-model:employmentOfficeEN="meta.employmentOfficeEN"
|
||||
v-model:address="meta.address"
|
||||
v-model:addressEN="meta.addressEN"
|
||||
v-model:street="meta.street"
|
||||
v-model:streetEN="meta.streetEN"
|
||||
v-model:moo="meta.moo"
|
||||
v-model:mooEN="meta.mooEN"
|
||||
v-model:soi="meta.soi"
|
||||
v-model:soiEN="meta.soiEN"
|
||||
v-model:province-id="meta.provinceId"
|
||||
v-model:district-id="meta.districtId"
|
||||
v-model:sub-district-id="meta.subDistrictId"
|
||||
v-model:middle-name="meta.middleName"
|
||||
v-model:middle-name-en="meta.middleNameEN"
|
||||
/>
|
||||
<FormEmployeePassport
|
||||
v-if="mode === 'passport' && meta"
|
||||
prefix-id="drawer-info-employee"
|
||||
id="form-passport"
|
||||
dense
|
||||
outlined
|
||||
separator
|
||||
ocr
|
||||
:title="$t('customerEmployee.form.group.passport')"
|
||||
:readonly="!isEdit"
|
||||
v-model:passport-type="meta.type"
|
||||
v-model:passport-number="meta.number"
|
||||
v-model:passport-issue-date="meta.issueDate"
|
||||
v-model:passport-expiry-date="meta.expireDate"
|
||||
v-model:passport-issuing-place="meta.issuePlace"
|
||||
v-model:passport-issuing-country="meta.issueCountry"
|
||||
/>
|
||||
<FormEmployeeVisa
|
||||
v-if="mode === 'visa' && meta"
|
||||
prefix-id="drawer-info-employee"
|
||||
id="form-visa"
|
||||
ocr
|
||||
dense
|
||||
outlined
|
||||
title="customerEmployee.form.group.visa"
|
||||
:readonly="!isEdit"
|
||||
v-model:visa-type="meta.type"
|
||||
v-model:visa-number="meta.number"
|
||||
v-model:visa-issue-date="meta.issueDate"
|
||||
v-model:visa-expiry-date="meta.expireDate"
|
||||
v-model:visa-issuing-place="meta.issuePlace"
|
||||
/>
|
||||
|
||||
<CorpFormBusinessRegistration
|
||||
v-if="
|
||||
mode === 'commercial-registration' &&
|
||||
meta &&
|
||||
customerType === 'CORP'
|
||||
"
|
||||
/>
|
||||
|
||||
<PersFormBusinessRegistration
|
||||
v-if="
|
||||
mode === 'commercial-registration' &&
|
||||
meta &&
|
||||
customerType === 'PERS'
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</UploadFile>
|
||||
</UploadFileGroup>
|
||||
|
||||
<!-- <EmployerFormAttachment
|
||||
:readonly="readonly"
|
||||
|
|
|
|||
|
|
@ -1,31 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { selectFilterOptionRefMod } from 'stores/utils';
|
||||
import { dateFormat, parseAndFormatDate } from 'src/utils/datetime';
|
||||
import { onMounted, watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import DatePicker from 'src/components/shared/DatePicker.vue';
|
||||
|
||||
const { locale } = useI18n({ useScope: 'global' });
|
||||
|
||||
const employmentOffice = defineModel<string>('employmentOffice');
|
||||
const bussinessType = defineModel<string>('bussinessType');
|
||||
const jobPosition = defineModel<string>('jobPosition');
|
||||
|
||||
const bussinessTypeEN = defineModel<string>('bussinessTypeEn');
|
||||
const jobPositionEN = defineModel<string>('jobPositionEn');
|
||||
|
||||
const jobDescription = defineModel<string>('jobDescription');
|
||||
|
||||
const payDate = defineModel<Date | null | string>('payDate');
|
||||
const wageRate = defineModel<number>('wageRate');
|
||||
|
||||
const saleEmployee = defineModel<string>('saleEmployee');
|
||||
|
||||
const rawOption = ref();
|
||||
|
||||
const bussinessType = defineModel<string>('bussinessType');
|
||||
const jobPosition = defineModel<string>('jobPosition');
|
||||
const jobDescription = defineModel<string>('jobDescription');
|
||||
const payDate = defineModel<string>('payDate');
|
||||
const payDateEN = defineModel<string>('payDateEN');
|
||||
const wageRate = defineModel<number>('wageRate');
|
||||
const wageRateText = defineModel<string>('wageRateText');
|
||||
|
||||
const typeBusinessOption = ref([]);
|
||||
const typeBusinessENOption = ref([]);
|
||||
const jobPositionOption = ref([]);
|
||||
const jobPositionENOption = ref([]);
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
|
|
@ -38,6 +32,8 @@ defineProps<{
|
|||
onMounted(async () => {
|
||||
const resultOption = await fetch('/option/option.json');
|
||||
rawOption.value = await resultOption.json();
|
||||
typeBusinessENOption.value = rawOption.value.eng.businessType;
|
||||
jobPositionENOption.value = rawOption.value.eng.position;
|
||||
|
||||
if (locale.value === 'eng') {
|
||||
typeBusinessOption.value = rawOption.value.eng.businessType;
|
||||
|
|
@ -48,20 +44,31 @@ onMounted(async () => {
|
|||
jobPositionOption.value = rawOption.value.tha.position;
|
||||
}
|
||||
});
|
||||
watch(typeBusinessOption, () => {
|
||||
|
||||
watch([typeBusinessOption, typeBusinessENOption], () => {
|
||||
typeBusinessFilter = selectFilterOptionRefMod(
|
||||
typeBusinessOption,
|
||||
typeBusinessOptions,
|
||||
'label',
|
||||
);
|
||||
typeBusinessENFilter = selectFilterOptionRefMod(
|
||||
typeBusinessENOption,
|
||||
typeBusinessENOptions,
|
||||
'label',
|
||||
);
|
||||
});
|
||||
|
||||
watch(jobPositionOption, () => {
|
||||
watch([jobPositionOption, jobPositionENOption], () => {
|
||||
jobPositionFilter = selectFilterOptionRefMod(
|
||||
jobPositionOption,
|
||||
jobPositionOptions,
|
||||
'label',
|
||||
);
|
||||
jobPositionENFilter = selectFilterOptionRefMod(
|
||||
jobPositionENOption,
|
||||
jobPositionENOptions,
|
||||
'label',
|
||||
);
|
||||
});
|
||||
|
||||
const typeBusinessOptions = ref<Record<string, unknown>[]>([]);
|
||||
|
|
@ -77,22 +84,23 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
jobPositionOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
const typeBusinessENOptions = ref<Record<string, unknown>[]>([]);
|
||||
let typeBusinessENFilter = selectFilterOptionRefMod(
|
||||
typeBusinessENOption,
|
||||
typeBusinessENOptions,
|
||||
'label',
|
||||
);
|
||||
|
||||
const jobPositionENOptions = ref<Record<string, unknown>[]>([]);
|
||||
let jobPositionENFilter = selectFilterOptionRefMod(
|
||||
jobPositionENOption,
|
||||
jobPositionENOptions,
|
||||
'label',
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-employment-office`"
|
||||
:id="`${prefixId}-input-employment-office`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12"
|
||||
:label="$t('form.address')"
|
||||
v-model="employmentOffice"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -102,10 +110,10 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
:hide-dropdown-icon="readonly"
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
v-model="bussinessType"
|
||||
class="col-md-6 col-12"
|
||||
:dense="dense"
|
||||
|
|
@ -114,6 +122,7 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
:options="typeBusinessOptions"
|
||||
:for="`${prefixId}-select-business-type`"
|
||||
@filter="typeBusinessFilter"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
|
|
@ -123,19 +132,38 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
<q-select
|
||||
:for="`${prefixId}-input-bussiness-type-en`"
|
||||
:id="`${prefixId}-input-bussiness-type-en`"
|
||||
:dense="dense"
|
||||
:label="`${$t('customer.form.businessType')} (EN)`"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
:hide-dropdown-icon="readonly"
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
v-model="bussinessType"
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('customer.form.businessTypeEN')"
|
||||
v-model="bussinessTypeEN"
|
||||
/>
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="typeBusinessENOptions"
|
||||
@filter="typeBusinessENFilter"
|
||||
:rules="[(val: string) => !!val || $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>
|
||||
|
||||
<q-select
|
||||
outlined
|
||||
|
|
@ -146,10 +174,10 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
:hide-dropdown-icon="readonly"
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
lazy-rules="ondemand"
|
||||
v-model="jobPosition"
|
||||
class="col-md-6 col-12"
|
||||
:dense="dense"
|
||||
|
|
@ -158,6 +186,40 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
:options="jobPositionOptions"
|
||||
:for="`${prefixId}-select-job-position`"
|
||||
@filter="jobPositionFilter"
|
||||
:rules="[(val: string) => !!val || $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>
|
||||
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
:hide-dropdown-icon="readonly"
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
v-model="jobPosition"
|
||||
class="col-md-6 col-12"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="`${$t('customer.form.jobPosition')} (EN)`"
|
||||
:options="jobPositionENOptions"
|
||||
:for="`${prefixId}-input-job-position-en`"
|
||||
:id="`${prefixId}-input-job-position-en`"
|
||||
@filter="jobPositionENFilter"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
|
|
@ -169,20 +231,6 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
</q-select>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-job-position-en`"
|
||||
:id="`${prefixId}-input-job-position-en`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('customer.form.jobPositionEN')"
|
||||
v-model="jobPositionEN"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-job-description`"
|
||||
:id="`${prefixId}-input-job-description`"
|
||||
:dense="dense"
|
||||
|
|
@ -191,19 +239,41 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('customer.form.jobDescription')"
|
||||
v-model="jobDescription"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
:id="`${prefixId}-date-picker-pay-date`"
|
||||
v-model="payDate"
|
||||
:label="$t('customer.form.payDay')"
|
||||
:readonly="readonly"
|
||||
clearable
|
||||
:model-value="readonly ? jobDescription || '-' : jobDescription"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (jobDescription = v) : '')
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
:for="`${prefixId}-input-pay-rate`"
|
||||
:id="`${prefixId}-input-pay-rate`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('customer.form.payDay')"
|
||||
:model-value="readonly ? payDate || '-' : payDate"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (payDate = v) : '')"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
:for="`${prefixId}-input-pay-rate`"
|
||||
:id="`${prefixId}-input-pay-rate`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
label="Pay day"
|
||||
:model-value="readonly ? payDateEN || '-' : payDateEN"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (payDateEN = v) : '')
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-pay-rate`"
|
||||
:id="`${prefixId}-input-pay-rate`"
|
||||
:dense="dense"
|
||||
|
|
@ -216,16 +286,18 @@ let jobPositionFilter = selectFilterOptionRefMod(
|
|||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-sales-person`"
|
||||
:id="`${prefixId}-input-sales-person`"
|
||||
:for="`${prefixId}-input-pay-rate`"
|
||||
:id="`${prefixId}-input-pay-rate`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-6"
|
||||
:label="$t('customer.form.salesPerson')"
|
||||
v-model="saleEmployee"
|
||||
class="col-md-3 col-6"
|
||||
:label="`${$t('customer.form.payRate')} (Text)`"
|
||||
:model-value="readonly ? wageRateText || '-' : wageRateText"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (wageRateText = v) : '')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ defineProps<{
|
|||
prefixId?: string;
|
||||
}>();
|
||||
const contactName = defineModel<string>('contactName');
|
||||
const mail = defineModel<string>('email');
|
||||
const telephone = defineModel<string>('telephone');
|
||||
const email = defineModel<string>('email');
|
||||
const contactTel = defineModel<string>('contactTel');
|
||||
const officeTel = defineModel<string>('officeTel');
|
||||
const agent = defineModel<string>('agent');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-md-9 col-12 row q-col-gutter-md">
|
||||
<div class="col-md-9 col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-contact-name`"
|
||||
:id="`${prefixId}-input-contact-name`"
|
||||
dense
|
||||
|
|
@ -20,11 +21,13 @@ const telephone = defineModel<string>('telephone');
|
|||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('customer.table.contactName')"
|
||||
v-model="contactName"
|
||||
:model-value="readonly ? contactName || '-' : contactName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (contactName = v) : '')
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-mail`"
|
||||
:id="`${prefixId}-input-mail`"
|
||||
dense
|
||||
|
|
@ -33,10 +36,29 @@ const telephone = defineModel<string>('telephone');
|
|||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('form.email')"
|
||||
v-model="mail"
|
||||
/>
|
||||
:rules="
|
||||
readonly
|
||||
? undefined
|
||||
: [
|
||||
(v: string) =>
|
||||
!v ||
|
||||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
|
||||
$t('form.error.invalid'),
|
||||
]
|
||||
"
|
||||
:model-value="readonly ? email || '-' : email"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-email-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
lazy-rules="ondemand"
|
||||
:for="`${prefixId}-input-telephone`"
|
||||
:id="`${prefixId}-input-telephone`"
|
||||
dense
|
||||
|
|
@ -45,7 +67,56 @@ const telephone = defineModel<string>('telephone');
|
|||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('form.telephone')"
|
||||
v-model="telephone"
|
||||
: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-input
|
||||
:for="`${prefixId}-input-telephone`"
|
||||
:id="`${prefixId}-input-telephone`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('customer.form.headQuarters.telephoneNo')"
|
||||
:model-value="readonly ? officeTel || '-' : officeTel"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (officeTel = v) : '')
|
||||
"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-input
|
||||
:for="`${prefixId}-input-telephone`"
|
||||
:id="`${prefixId}-input-telephone`"
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-6 col-12"
|
||||
:label="$t('customer.form.agent')"
|
||||
:model-value="readonly ? agent || '-' : agent"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (agent = v) : '')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const countryCode = [
|
|||
export const uploadFileListCustomer: {
|
||||
label: string;
|
||||
value: string;
|
||||
_meta?: Record<string, any>;
|
||||
}[] = [
|
||||
{
|
||||
label: 'customer.typeFile.citizenId',
|
||||
|
|
@ -41,22 +42,22 @@ export const uploadFileListCustomer: {
|
|||
},
|
||||
{
|
||||
label: 'customer.typeFile.registrationBook',
|
||||
value: 'registrationBook',
|
||||
value: 'house-registration',
|
||||
},
|
||||
|
||||
{
|
||||
label: 'customer.typeFile.houseMap',
|
||||
value: 'houseMap',
|
||||
value: 'vat-registration',
|
||||
},
|
||||
|
||||
{
|
||||
label: 'customer.typeFile.businessRegistration',
|
||||
value: 'businessRegistration',
|
||||
value: 'commercial-registration',
|
||||
},
|
||||
|
||||
{
|
||||
label: 'customer.typeFile.dbdCertificate',
|
||||
value: 'dbdCertificate',
|
||||
value: 'power-of-attorney',
|
||||
},
|
||||
|
||||
{
|
||||
|
|
@ -78,6 +79,7 @@ export const uploadFileListCustomer: {
|
|||
export const uploadFileListEmployee: {
|
||||
label: string;
|
||||
value: string;
|
||||
_meta?: Record<string, any>;
|
||||
}[] = [
|
||||
{
|
||||
label: 'customerEmployee.fileType.passport',
|
||||
|
|
@ -86,6 +88,17 @@ export const uploadFileListEmployee: {
|
|||
{
|
||||
label: 'customerEmployee.fileType.visa',
|
||||
value: 'visa',
|
||||
_meta: {
|
||||
number: '',
|
||||
type: '',
|
||||
entryCount: 0,
|
||||
issueCountry: '',
|
||||
issuePlace: '',
|
||||
issueDate: new Date(),
|
||||
expireDate: new Date(),
|
||||
mrz: '',
|
||||
remark: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.tm6',
|
||||
|
|
@ -93,7 +106,7 @@ export const uploadFileListEmployee: {
|
|||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.workPermit',
|
||||
value: 'workPermit',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.noticeJobEmployment',
|
||||
|
|
@ -101,19 +114,19 @@ export const uploadFileListEmployee: {
|
|||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.noticeJobEntry',
|
||||
value: 'noticeJobEntry',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.historyJob',
|
||||
value: 'historyJob',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.acceptJob',
|
||||
value: 'acceptJob',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.receipt',
|
||||
value: 'receipt',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.other',
|
||||
|
|
@ -134,6 +147,33 @@ export const formMenuIconEmployee = [
|
|||
},
|
||||
];
|
||||
|
||||
export const columnsAttachment = [
|
||||
{
|
||||
name: 'orderNumber',
|
||||
align: 'center',
|
||||
label: 'general.orderNumber',
|
||||
field: 'branchNo',
|
||||
},
|
||||
{
|
||||
name: 'document',
|
||||
align: 'center',
|
||||
label: 'general.document',
|
||||
field: 'attachmentName',
|
||||
},
|
||||
{
|
||||
name: 'uploadDate',
|
||||
align: 'center',
|
||||
label: 'general.uploadDate',
|
||||
field: 'uploadDate',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'action',
|
||||
label: '',
|
||||
field: 'action',
|
||||
},
|
||||
] satisfies QTableProps['columns'];
|
||||
|
||||
export const columnsEmployee = [
|
||||
{
|
||||
name: 'orderNumber',
|
||||
|
|
@ -207,10 +247,10 @@ export const columnsCustomer = [
|
|||
},
|
||||
|
||||
{
|
||||
name: 'customerName',
|
||||
name: 'titleName',
|
||||
align: 'left',
|
||||
label: 'customer.table.fullname',
|
||||
field: 'customerName',
|
||||
label: 'customer.table.titleName',
|
||||
field: 'titleName',
|
||||
sortable: true,
|
||||
},
|
||||
|
||||
|
|
@ -223,30 +263,33 @@ export const columnsCustomer = [
|
|||
},
|
||||
|
||||
{
|
||||
name: 'address',
|
||||
align: 'left',
|
||||
label: 'customer.table.address',
|
||||
field: 'address',
|
||||
name: 'jobPosition',
|
||||
align: 'center',
|
||||
label: 'customer.table.jobPosition',
|
||||
field: 'jobPosition',
|
||||
sortable: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'workPlace',
|
||||
align: 'left',
|
||||
label: 'customer.table.workPlace',
|
||||
field: 'workPlace',
|
||||
name: 'address',
|
||||
align: 'center',
|
||||
label: 'customer.table.address',
|
||||
field: 'address',
|
||||
sortable: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'contactName',
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
label: 'customer.table.contactName',
|
||||
field: 'contactName',
|
||||
sortable: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'contactPhone',
|
||||
name: 'officeTel',
|
||||
align: 'left',
|
||||
label: 'customer.table.contactPhone',
|
||||
field: 'contactPhone',
|
||||
label: 'customer.table.officeTel',
|
||||
field: 'officeTel',
|
||||
},
|
||||
] satisfies QTableProps['columns'];
|
||||
|
|
|
|||
|
|
@ -9,26 +9,28 @@ import useCustomerStore from 'stores/customer';
|
|||
import useEmployeeStore from 'stores/employee';
|
||||
import useFlowStore from 'stores/flow';
|
||||
|
||||
import { baseUrl } from 'src/stores/utils';
|
||||
|
||||
export const useCustomerForm = defineStore('form-customer', () => {
|
||||
const { t } = useI18n();
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const customerStore = useCustomerStore();
|
||||
const branchStore = useMyBranch();
|
||||
|
||||
const defaultFormData: CustomerCreate = {
|
||||
code: '',
|
||||
// code: '',
|
||||
// namePrefix: '',
|
||||
// firstName: '',
|
||||
// lastName: '',
|
||||
// firstNameEN: '',
|
||||
// lastNameEN: '',
|
||||
// gender: '',
|
||||
// birthDate: new Date(),
|
||||
customerBranch: [],
|
||||
selectedImage: '',
|
||||
status: 'CREATED',
|
||||
customerType: 'CORP',
|
||||
namePrefix: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
firstNameEN: '',
|
||||
lastNameEN: '',
|
||||
gender: '',
|
||||
birthDate: new Date(),
|
||||
registeredBranchId: branchStore.currentMyBranch?.id || '',
|
||||
customerBranch: [],
|
||||
image: null,
|
||||
};
|
||||
let resetFormData = structuredClone(defaultFormData);
|
||||
|
|
@ -50,6 +52,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
editCustomerBranchId?: string;
|
||||
treeFile: { label: string; file: { label: string }[] }[];
|
||||
formDataOcr: Record<string, any>;
|
||||
isImageEdit: boolean;
|
||||
}>({
|
||||
dialogType: 'info',
|
||||
dialogOpen: false,
|
||||
|
|
@ -65,6 +68,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
defaultCustomerImageUrl: '',
|
||||
treeFile: [],
|
||||
formDataOcr: {},
|
||||
isImageEdit: false,
|
||||
});
|
||||
|
||||
watch(
|
||||
|
|
@ -83,9 +87,10 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
}
|
||||
|
||||
function isFormDataDifferent() {
|
||||
return (
|
||||
JSON.stringify(resetFormData) !== JSON.stringify(currentFormData.value)
|
||||
);
|
||||
const { status: resetStatus, ...resetData } = resetFormData;
|
||||
const { status: currStatus, ...currData } = currentFormData.value;
|
||||
|
||||
return JSON.stringify(resetData) !== JSON.stringify(currData);
|
||||
}
|
||||
|
||||
function resetForm(clean = false) {
|
||||
|
|
@ -110,8 +115,9 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
if (state.value.dialogType === 'create') {
|
||||
state.value.editCustomerId = '';
|
||||
}
|
||||
|
||||
const currentImg = currentFormData.value.selectedImage;
|
||||
currentFormData.value = structuredClone(resetFormData);
|
||||
currentFormData.value.selectedImage = currentImg;
|
||||
}
|
||||
|
||||
async function assignFormData(id?: string) {
|
||||
|
|
@ -129,56 +135,74 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
state.value.dialogType = 'edit';
|
||||
state.value.editCustomerId = id;
|
||||
state.value.editCustomerCode = data.code;
|
||||
state.value.customerImageUrl = `${apiBaseUrl}/customer/${id}/image`;
|
||||
state.value.defaultCustomerImageUrl = `${apiBaseUrl}/customer/${id}/image`;
|
||||
state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
|
||||
state.value.defaultCustomerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
|
||||
|
||||
resetFormData.registeredBranchId = data.registeredBranchId;
|
||||
resetFormData.code = data.code || '';
|
||||
resetFormData.status = data.status;
|
||||
resetFormData.customerType = data.customerType;
|
||||
resetFormData.namePrefix = data.namePrefix;
|
||||
resetFormData.firstName = data.firstName;
|
||||
resetFormData.lastName = data.lastName;
|
||||
resetFormData.firstNameEN = data.firstNameEN;
|
||||
resetFormData.lastNameEN = data.lastNameEN;
|
||||
resetFormData.gender = data.gender;
|
||||
resetFormData.birthDate = new Date(data.birthDate);
|
||||
resetFormData.image = null;
|
||||
resetFormData.selectedImage = data.selectedImage;
|
||||
|
||||
resetFormData.customerBranch = await Promise.all(
|
||||
data.branch.map(async (v) => ({
|
||||
id: v.id,
|
||||
code: v.code || '',
|
||||
customerCode: '',
|
||||
provinceId: v.provinceId,
|
||||
districtId: v.districtId,
|
||||
subDistrictId: v.subDistrictId,
|
||||
firstName: v.firstName,
|
||||
firstNameEN: v.firstNameEN,
|
||||
lastName: v.lastName,
|
||||
lastNameEN: v.lastNameEN,
|
||||
gender: v.gender,
|
||||
birthDate: v.birthDate,
|
||||
namePrefix: v.namePrefix,
|
||||
wageRate: v.wageRate,
|
||||
payDate: new Date(v.payDate), // Convert the string to a Date object
|
||||
saleEmployee: v.saleEmployee,
|
||||
wageRateText: v.wageRateText,
|
||||
payDate: v.payDate,
|
||||
jobDescription: v.jobDescription,
|
||||
jobPositionEN: v.jobPositionEN,
|
||||
jobPosition: v.jobPosition,
|
||||
businessTypeEN: v.businessTypeEN,
|
||||
businessType: v.businessType,
|
||||
employmentOffice: v.employmentOffice,
|
||||
employmentOfficeEN: v.employmentOfficeEN,
|
||||
telephoneNo: v.telephoneNo,
|
||||
contactName: v.contactName,
|
||||
email: v.email,
|
||||
subDistrictId: v.subDistrictId,
|
||||
districtId: v.districtId,
|
||||
provinceId: v.provinceId,
|
||||
streetEN: v.streetEN,
|
||||
street: v.street,
|
||||
mooEN: v.mooEN,
|
||||
moo: v.moo,
|
||||
soiEN: v.soiEN,
|
||||
soi: v.soi,
|
||||
addressEN: v.addressEN,
|
||||
address: v.address,
|
||||
workplaceEN: v.workplaceEN,
|
||||
workplace: v.workplace,
|
||||
authorizedCapital: v.authorizedCapital,
|
||||
registerDate: v.registerDate,
|
||||
registerNameEN: v.registerNameEN,
|
||||
registerName: v.registerName,
|
||||
legalPersonNo: v.legalPersonNo,
|
||||
citizenId: v.citizenId,
|
||||
codeCustomer: v.codeCustomer,
|
||||
updatedByUserId: v.updatedByUserId,
|
||||
updatedAt: v.updatedAt,
|
||||
createdByUserId: v.createdByUserId,
|
||||
createdAt: v.createdAt,
|
||||
code: v.code,
|
||||
statusOrder: v.statusOrder,
|
||||
status: v.status,
|
||||
customerId: v.customerId,
|
||||
citizenId: v.citizenId || '',
|
||||
authorizedCapital: v.authorizedCapital || '',
|
||||
registerDate: new Date(v.registerDate), // Convert the string to a Date object
|
||||
registerNameEN: v.registerNameEN || '',
|
||||
registerName: v.registerName || '',
|
||||
legalPersonNo: v.legalPersonNo || '',
|
||||
registerCompanyName: '',
|
||||
id: v.id,
|
||||
homeCode: v.homeCode,
|
||||
contactTel: v.contactTel,
|
||||
officeTel: v.officeTel,
|
||||
agent: v.agent,
|
||||
customerName: v.customerName,
|
||||
authorizedName: v.authorizedName,
|
||||
authorizedNameEN: v.authorizedNameEN,
|
||||
|
||||
payDateEN: v.payDateEN,
|
||||
statusSave: true,
|
||||
contactName: v.contactName || '',
|
||||
file: await customerStore.listAttachment(v.id).then(async (r) => {
|
||||
if (r) {
|
||||
return await Promise.all(
|
||||
|
|
@ -202,68 +226,91 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
currentFormData.value = structuredClone(resetFormData);
|
||||
}
|
||||
|
||||
function addCurrentCustomerBranch() {
|
||||
async function addCurrentCustomerBranch() {
|
||||
if (currentFormData.value.customerBranch?.some((v) => !v.id)) return;
|
||||
|
||||
currentFormData.value.customerBranch?.push({
|
||||
id: '',
|
||||
code:
|
||||
currentFormData.value.customerBranch.length !== 0
|
||||
? currentFormData.value.customerBranch?.[0].code === null
|
||||
? ''
|
||||
: currentFormData.value.customerBranch?.[0].code
|
||||
: '',
|
||||
|
||||
customerCode: '',
|
||||
provinceId: '',
|
||||
districtId: '',
|
||||
subDistrictId: '',
|
||||
wageRate: 0,
|
||||
payDate: new Date(), // Convert the string to a Date object
|
||||
saleEmployee: '',
|
||||
jobDescription: '',
|
||||
jobPositionEN: '',
|
||||
jobPosition: '',
|
||||
businessTypeEN: '',
|
||||
businessType: '',
|
||||
employmentOffice: '',
|
||||
telephoneNo: '',
|
||||
email: '',
|
||||
addressEN: '',
|
||||
address: '',
|
||||
workplaceEN: '',
|
||||
workplace: '',
|
||||
status: 'CREATED',
|
||||
customerId: '',
|
||||
citizenId:
|
||||
branchCode:
|
||||
currentFormData.value.customerBranch.length !== 0
|
||||
? currentFormData.value.customerBranch?.[0].citizenId === null
|
||||
? currentFormData.value.customerBranch?.[0].branchCode === null
|
||||
? ''
|
||||
: currentFormData.value.customerBranch?.[0].citizenId
|
||||
: currentFormData.value.customerBranch?.[0].branchCode
|
||||
: '',
|
||||
authorizedCapital: '',
|
||||
registerDate: new Date(), // Convert the string to a Date object
|
||||
registerNameEN: '',
|
||||
registerName: '',
|
||||
codeCustomer: '',
|
||||
|
||||
legalPersonNo:
|
||||
currentFormData.value.customerBranch.length !== 0
|
||||
? currentFormData.value.customerBranch?.[0].legalPersonNo === null
|
||||
? ''
|
||||
: currentFormData.value.customerBranch?.[0].legalPersonNo
|
||||
: '',
|
||||
registerCompanyName: '',
|
||||
citizenId:
|
||||
currentFormData.value.customerBranch.length !== 0
|
||||
? currentFormData.value.customerBranch?.[0].citizenId === null
|
||||
? ''
|
||||
: currentFormData.value.customerBranch?.[0].citizenId
|
||||
: '',
|
||||
namePrefix: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
firstNameEN: '',
|
||||
lastNameEN: '',
|
||||
telephoneNo: '',
|
||||
gender: '',
|
||||
birthDate: '',
|
||||
|
||||
businessType: '',
|
||||
jobPosition: '',
|
||||
jobDescription: '',
|
||||
payDate: '',
|
||||
payDateEN: '',
|
||||
wageRate: 0,
|
||||
wageRateText: '',
|
||||
homeCode: '',
|
||||
employmentOffice: '',
|
||||
employmentOfficeEN: '',
|
||||
|
||||
address: '',
|
||||
addressEN: '',
|
||||
street: '',
|
||||
streetEN: '',
|
||||
moo: '',
|
||||
mooEN: '',
|
||||
soi: '',
|
||||
soiEN: '',
|
||||
provinceId: '',
|
||||
districtId: '',
|
||||
subDistrictId: '',
|
||||
|
||||
contactName: '',
|
||||
email: '',
|
||||
contactTel: '',
|
||||
officeTel: '',
|
||||
agent: '',
|
||||
status: 'CREATED',
|
||||
|
||||
customerName: '',
|
||||
registerName: '',
|
||||
registerNameEN: '',
|
||||
registerDate: null,
|
||||
authorizedCapital: '',
|
||||
authorizedName: '',
|
||||
authorizedNameEN: '',
|
||||
file: [],
|
||||
});
|
||||
state.value.branchIndex =
|
||||
(currentFormData.value.customerBranch?.length || 0) - 1;
|
||||
}
|
||||
|
||||
async function submitFormCustomer() {
|
||||
async function submitFormCustomer(imgList?: {
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}) {
|
||||
if (state.value.dialogType === 'info') return;
|
||||
|
||||
if (state.value.dialogType === 'create') {
|
||||
const _data = await customerStore.create(currentFormData.value);
|
||||
const _data = await customerStore.create(currentFormData.value, imgList);
|
||||
|
||||
if (_data) await assignFormData(_data.id);
|
||||
|
||||
|
|
@ -313,38 +360,63 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
const customerStore = useCustomerStore();
|
||||
const customerFormStore = useCustomerForm();
|
||||
|
||||
const defaultFormData: CustomerBranchCreate & { id?: string } = {
|
||||
code: '',
|
||||
customerCode: '',
|
||||
const defaultFormData: CustomerBranchCreate & {
|
||||
id?: string;
|
||||
codeCustomer?: string;
|
||||
} = {
|
||||
id: '',
|
||||
customerId: '',
|
||||
// branchCode: '',
|
||||
// codeCustomer: '',
|
||||
|
||||
legalPersonNo: '',
|
||||
citizenId: '',
|
||||
namePrefix: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
firstNameEN: '',
|
||||
lastNameEN: '',
|
||||
telephoneNo: '',
|
||||
gender: '',
|
||||
birthDate: '',
|
||||
|
||||
businessType: '',
|
||||
jobPosition: '',
|
||||
jobDescription: '',
|
||||
payDate: '',
|
||||
payDateEN: '',
|
||||
wageRate: 0,
|
||||
wageRateText: '',
|
||||
homeCode: '',
|
||||
employmentOffice: '',
|
||||
employmentOfficeEN: '',
|
||||
|
||||
address: '',
|
||||
addressEN: '',
|
||||
street: '',
|
||||
streetEN: '',
|
||||
moo: '',
|
||||
mooEN: '',
|
||||
soi: '',
|
||||
soiEN: '',
|
||||
provinceId: '',
|
||||
districtId: '',
|
||||
subDistrictId: '',
|
||||
wageRate: 0,
|
||||
payDate: new Date(), // Convert the string to a Date object
|
||||
saleEmployee: '',
|
||||
jobDescription: '',
|
||||
jobPositionEN: '',
|
||||
jobPosition: '',
|
||||
businessTypeEN: '',
|
||||
businessType: '',
|
||||
employmentOffice: '',
|
||||
telephoneNo: '',
|
||||
email: '',
|
||||
addressEN: '',
|
||||
address: '',
|
||||
workplaceEN: '',
|
||||
workplace: '',
|
||||
status: 'CREATED',
|
||||
customerId: '',
|
||||
citizenId: '',
|
||||
authorizedCapital: '',
|
||||
registerDate: new Date(), // Convert the string to a Date object
|
||||
registerNameEN: '',
|
||||
registerName: '',
|
||||
legalPersonNo: '',
|
||||
registerCompanyName: '',
|
||||
statusSave: false,
|
||||
|
||||
contactName: '',
|
||||
email: '',
|
||||
contactTel: '',
|
||||
officeTel: '',
|
||||
agent: '',
|
||||
status: 'CREATED',
|
||||
|
||||
customerName: '',
|
||||
registerName: '',
|
||||
registerNameEN: '',
|
||||
registerDate: null,
|
||||
authorizedCapital: '',
|
||||
authorizedName: '',
|
||||
authorizedNameEN: '',
|
||||
file: [],
|
||||
};
|
||||
|
||||
|
|
@ -378,28 +450,25 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
const _data = await customerStore.getBranchById(id);
|
||||
|
||||
if (!_data) return;
|
||||
|
||||
resetFormData = {
|
||||
id: _data.id,
|
||||
code: _data.code,
|
||||
customerCode: '',
|
||||
provinceId: _data.provinceId,
|
||||
districtId: _data.districtId,
|
||||
subDistrictId: _data.subDistrictId,
|
||||
wageRate: _data.wageRate,
|
||||
payDate: new Date(_data.payDate), // Convert the string to a Date object
|
||||
saleEmployee: _data.saleEmployee,
|
||||
payDate: _data.payDate, // Convert the string to a Date object
|
||||
payDateEN: _data.payDateEN,
|
||||
jobDescription: _data.jobDescription,
|
||||
jobPositionEN: _data.jobPositionEN,
|
||||
jobPosition: _data.jobPosition,
|
||||
businessTypeEN: _data.businessTypeEN,
|
||||
businessType: _data.businessType,
|
||||
employmentOffice: _data.employmentOffice,
|
||||
employmentOfficeEN: _data.employmentOfficeEN,
|
||||
telephoneNo: _data.telephoneNo,
|
||||
email: _data.email,
|
||||
addressEN: _data.addressEN,
|
||||
address: _data.address,
|
||||
workplaceEN: _data.workplaceEN,
|
||||
workplace: _data.workplace,
|
||||
status: 'CREATED',
|
||||
customerId: _data.customerId,
|
||||
citizenId: _data.citizenId,
|
||||
|
|
@ -409,7 +478,28 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
registerName: _data.registerName,
|
||||
legalPersonNo: _data.legalPersonNo,
|
||||
contactName: _data.contactName,
|
||||
registerCompanyName: '',
|
||||
namePrefix: _data.namePrefix,
|
||||
firstName: _data.firstName,
|
||||
firstNameEN: _data.firstNameEN,
|
||||
lastName: _data.lastName,
|
||||
lastNameEN: _data.lastNameEN,
|
||||
gender: _data.gender,
|
||||
birthDate: _data.birthDate,
|
||||
moo: _data.moo,
|
||||
mooEN: _data.mooEN,
|
||||
soi: _data.soi,
|
||||
soiEN: _data.soiEN,
|
||||
street: _data.street,
|
||||
streetEN: _data.streetEN,
|
||||
wageRateText: _data.wageRateText,
|
||||
contactTel: _data.contactTel,
|
||||
officeTel: _data.officeTel,
|
||||
agent: _data.agent,
|
||||
codeCustomer: _data.codeCustomer,
|
||||
customerName: _data.customerName,
|
||||
homeCode: _data.homeCode,
|
||||
authorizedName: _data.authorizedName,
|
||||
authorizedNameEN: _data.authorizedNameEN,
|
||||
statusSave: false,
|
||||
file: [],
|
||||
};
|
||||
|
|
@ -433,6 +523,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
);
|
||||
|
||||
async function submitForm() {
|
||||
console.log(currentFormData.value);
|
||||
if (!state.value.currentCustomerId) {
|
||||
throw new Error(
|
||||
'Employer id cannot be found. Did you properly set employer id?',
|
||||
|
|
@ -479,6 +570,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
currentTab: string;
|
||||
dialogModal: boolean;
|
||||
drawerModal: boolean;
|
||||
isImageEdit: boolean;
|
||||
|
||||
currentEmployeeCode: string;
|
||||
currentEmployee: Employee | null;
|
||||
|
|
@ -511,6 +603,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
| undefined;
|
||||
ocr: boolean;
|
||||
}>({
|
||||
isImageEdit: false,
|
||||
currentIndex: -1,
|
||||
statusSavePersonal: false,
|
||||
drawerModal: false,
|
||||
|
|
@ -533,7 +626,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
const defaultFormData: EmployeeCreate = {
|
||||
id: '',
|
||||
code: '',
|
||||
image: null,
|
||||
customerBranchId: '',
|
||||
nrcNo: '',
|
||||
dateOfBirth: null,
|
||||
|
|
@ -553,24 +645,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
address: '',
|
||||
zipCode: '',
|
||||
|
||||
passportType: '',
|
||||
passportNumber: '',
|
||||
passportIssueDate: null,
|
||||
passportExpiryDate: null,
|
||||
passportIssuingCountry: '',
|
||||
passportIssuingPlace: '',
|
||||
previousPassportReference: '',
|
||||
|
||||
visaType: '',
|
||||
visaNumber: '',
|
||||
visaIssueDate: null,
|
||||
visaExpiryDate: null,
|
||||
visaIssuingPlace: '',
|
||||
visaStayUntilDate: null,
|
||||
tm6Number: '',
|
||||
entryDate: null,
|
||||
workerStatus: '',
|
||||
|
||||
subDistrictId: '',
|
||||
districtId: '',
|
||||
provinceId: '',
|
||||
|
|
@ -615,6 +689,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
motherLastNameEN: '',
|
||||
motherBirthPlace: '',
|
||||
},
|
||||
image: null,
|
||||
};
|
||||
|
||||
let resetEmployeeData = structuredClone(defaultFormData);
|
||||
|
|
@ -642,6 +717,9 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
resetEmployeeData = structuredClone(defaultFormData);
|
||||
state.value.statusSavePersonal = false;
|
||||
state.value.profileUrl = '';
|
||||
} else {
|
||||
resetEmployeeData.selectedImage =
|
||||
currentFromDataEmployee.value.selectedImage;
|
||||
}
|
||||
currentFromDataEmployee.value = structuredClone(resetEmployeeData);
|
||||
}
|
||||
|
|
@ -782,20 +860,27 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
await assignFormDataEmployee(currentFromDataEmployee.value.id);
|
||||
}
|
||||
|
||||
async function submitPersonal() {
|
||||
async function submitPersonal(imgList?: {
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}) {
|
||||
if (state.value.dialogType === 'create') {
|
||||
const res = await employeeStore.create({
|
||||
...currentFromDataEmployee.value,
|
||||
customerBranchId: state.value.formDataEmployeeOwner?.id || '',
|
||||
const res = await employeeStore.create(
|
||||
{
|
||||
...currentFromDataEmployee.value,
|
||||
customerBranchId: state.value.formDataEmployeeOwner?.id || '',
|
||||
|
||||
employeeWork: [],
|
||||
employeeCheckup: [],
|
||||
employeeOtherInfo: undefined,
|
||||
});
|
||||
employeeWork: [],
|
||||
employeeCheckup: [],
|
||||
employeeOtherInfo: undefined,
|
||||
},
|
||||
imgList,
|
||||
);
|
||||
|
||||
if (res) {
|
||||
await assignFormDataEmployee(res.id);
|
||||
currentFromDataEmployee.value.id = res.id;
|
||||
currentFromDataEmployee.value.file = res.file;
|
||||
state.value.statusSavePersonal = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -890,7 +975,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
}),
|
||||
)
|
||||
: [],
|
||||
image: null,
|
||||
};
|
||||
|
||||
currentFromDataEmployee.value = structuredClone(resetEmployeeData);
|
||||
|
|
@ -900,7 +984,8 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
|
||||
state.value.currentEmployeeCode = payload.code;
|
||||
|
||||
state.value.profileUrl = profileImageUrl || '';
|
||||
state.value.profileUrl =
|
||||
`${baseUrl}/employee/${id}/image/${_data.selectedImage}` || '';
|
||||
|
||||
profileImageUrl
|
||||
? (state.value.profileSubmit = true)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,206 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import QuatationForm from './QuatationForm.vue';
|
||||
import SideMenu from 'components/SideMenu.vue';
|
||||
import ImageUploadDialog from 'components/ImageUploadDialog.vue';
|
||||
import { watch } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const isOpen = ref(true);
|
||||
const imageUploadDialog = ref<InstanceType<typeof ImageUploadDialog>>();
|
||||
const file = ref<File | null>(null);
|
||||
import { productTreeDecoration } from './constants';
|
||||
import useProductServiceStore from 'src/stores/product-service';
|
||||
|
||||
import {
|
||||
ProductGroup,
|
||||
ProductList,
|
||||
Service,
|
||||
} from 'src/stores/product-service/types';
|
||||
|
||||
import QuotationForm from './QuotationForm.vue';
|
||||
import TreeView from 'src/components/shared/TreeView.vue';
|
||||
import { AddButton } from 'src/components/button';
|
||||
import MainButton from 'src/components/button/MainButton.vue';
|
||||
|
||||
const dialog = ref(true);
|
||||
|
||||
const nodes = ref([
|
||||
{
|
||||
title: 'กลุ่มสินค้าและบริการที่ 1',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
children: [
|
||||
{
|
||||
title: 'งานที่ 1',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
children: [
|
||||
{
|
||||
title: 'สินค้า 1',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
title: 'สินค้า 2',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
title: 'สินค้า 3',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
title: 'สินค้า 4',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
title: 'สินค้า 5',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
title: 'สินค้า 6',
|
||||
subtitle: 'TG01000000001',
|
||||
selected: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const productServiceStore = useProductServiceStore();
|
||||
|
||||
type ProductGroupId = string;
|
||||
|
||||
const productGroup = ref<ProductGroup[]>([]);
|
||||
const productList = ref<Partial<Record<ProductGroupId, ProductList[]>>>({});
|
||||
const serviceList = ref<Partial<Record<ProductGroupId, Service[]>>>({});
|
||||
|
||||
type Id = string;
|
||||
const product = ref<Record<Id, ProductList>>({});
|
||||
const service = ref<Record<Id, Service>>({});
|
||||
|
||||
const selectedGroup = ref<ProductGroup | null>(null);
|
||||
const selectedGroupSub = ref<'product' | 'service' | null>(null);
|
||||
const selectedProductServiceId = ref('');
|
||||
|
||||
onMounted(async () => {
|
||||
const ret = await productServiceStore.fetchListProductService({
|
||||
page: 1,
|
||||
pageSize: 9999,
|
||||
});
|
||||
if (ret) productGroup.value = ret.result;
|
||||
});
|
||||
|
||||
async function getAllProduct(
|
||||
groupId: string,
|
||||
opts?: { force?: false; page?: number; pageSize?: number },
|
||||
) {
|
||||
selectedGroupSub.value = 'product';
|
||||
if (!opts?.force && productList.value[groupId] !== undefined) return;
|
||||
const ret = await productServiceStore.fetchListProduct({
|
||||
page: opts?.page ?? 1,
|
||||
pageSize: opts?.pageSize ?? 9999,
|
||||
productGroupId: groupId,
|
||||
});
|
||||
if (ret) productList.value[groupId] = ret.result;
|
||||
}
|
||||
|
||||
async function getAllService(
|
||||
groupId: string,
|
||||
opts?: { force?: false; page?: number; pageSize?: number },
|
||||
) {
|
||||
selectedGroupSub.value = 'service';
|
||||
if (!opts?.force && serviceList.value[groupId] !== undefined) return;
|
||||
const ret = await productServiceStore.fetchListService({
|
||||
page: opts?.page ?? 1,
|
||||
pageSize: opts?.pageSize ?? 9999,
|
||||
productGroupId: groupId,
|
||||
fullDetail: true,
|
||||
});
|
||||
if (ret) serviceList.value[groupId] = ret.result;
|
||||
}
|
||||
|
||||
async function getProduct(id: string, force = false) {
|
||||
selectedGroupSub.value = 'product';
|
||||
selectedProductServiceId.value = id;
|
||||
if (!force && product.value[id] !== undefined) return;
|
||||
const ret = await productServiceStore.fetchListProductById(id);
|
||||
if (ret) product.value[id] = ret;
|
||||
}
|
||||
|
||||
async function getService(id: string, force = false) {
|
||||
selectedGroupSub.value = 'service';
|
||||
selectedProductServiceId.value = id;
|
||||
if (!force && service.value[id] !== undefined) return;
|
||||
const ret = await productServiceStore.fetchListServiceById(id);
|
||||
if (ret) service.value[id] = ret;
|
||||
}
|
||||
|
||||
function convertToTree() {
|
||||
// TODO: convert product or service into selectable tree
|
||||
// NOTE: this is meant to be used inside getService() and getProduct() before return and after return
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ImageUploadDialog
|
||||
<div v-for="item in productGroup" class="row items-center">
|
||||
{{ item.name }}
|
||||
{{ item.code }}
|
||||
<AddButton icon-only @click="selectedGroup = item" />
|
||||
</div>
|
||||
|
||||
<template v-if="selectedGroup">
|
||||
<MainButton
|
||||
icon="mdi-check"
|
||||
color="var(--blue-6-hsl)"
|
||||
@click="getAllProduct(selectedGroup.id)"
|
||||
>
|
||||
Product
|
||||
</MainButton>
|
||||
<MainButton
|
||||
icon="mdi-check"
|
||||
color="var(--blue-6-hsl)"
|
||||
@click="getAllService(selectedGroup.id)"
|
||||
>
|
||||
Service
|
||||
</MainButton>
|
||||
|
||||
<div
|
||||
v-if="selectedGroupSub === 'product' && productList[selectedGroup.id]"
|
||||
v-for="item in productList[selectedGroup.id]"
|
||||
class="row items-center"
|
||||
>
|
||||
{{ item.name }}
|
||||
{{ item.code }}
|
||||
<AddButton icon-only @click="getProduct(item.id)" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="selectedGroupSub === 'service' && serviceList[selectedGroup.id]"
|
||||
v-for="item in serviceList[selectedGroup.id]"
|
||||
class="row items-center"
|
||||
>
|
||||
{{ item.name }}
|
||||
{{ item.code }}
|
||||
<AddButton icon-only @click="getService(item.id)" />
|
||||
</div>
|
||||
|
||||
<template
|
||||
v-if="selectedGroupSub === 'service' && service[selectedProductServiceId]"
|
||||
>
|
||||
{{ service[selectedProductServiceId] }}
|
||||
</template>
|
||||
<template
|
||||
v-if="selectedGroupSub === 'product' && product[selectedProductServiceId]"
|
||||
>
|
||||
{{ product[selectedProductServiceId] }}
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<div class="surface-1 rounded full-height q-pa-md">
|
||||
<TreeView
|
||||
:decoration="productTreeDecoration"
|
||||
v-model:nodes="nodes"
|
||||
expandable
|
||||
/>
|
||||
</div>
|
||||
<!-- <ImageUploadDialog
|
||||
v-model:dialog-state="isOpen"
|
||||
v-model:file="file"
|
||||
ref="imageUploadDialog"
|
||||
|
|
@ -55,8 +244,8 @@ const file = ref<File | null>(null);
|
|||
id="my-anchor"
|
||||
>
|
||||
My Menu
|
||||
</div>
|
||||
<QuatationForm v-model:dialog-state="isOpen" />
|
||||
</div> -->
|
||||
<QuotationForm v-model:dialog-state="dialog" readonly />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,200 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
|
||||
import MainDialog from 'components/05_quotation/MainDialog.vue';
|
||||
import WorkerItem from 'components/05_quotation/WorkerItem.vue';
|
||||
import AppBox from 'components/app/AppBox.vue';
|
||||
|
||||
const dialogState = defineModel<boolean>({ default: true });
|
||||
|
||||
const selectedBranchIssuer = ref('');
|
||||
const selectedCustomer = ref('');
|
||||
const toggleWorker = ref(true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainDialog v-model:state="dialogState">
|
||||
<template #title>
|
||||
<span class="text-weight-medium" style="color: var(--brand-1)">
|
||||
{{ $t('quotation.form.createTitle') }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<div class="row q-pa-md items-center">
|
||||
<div style="flex: 1"><q-img src="/logo.png" width="8rem" /></div>
|
||||
<q-select
|
||||
lazy-rules="ondemand"
|
||||
v-model="selectedBranchIssuer"
|
||||
:options="[{ label: 'Issuer 1', value: 'Issuer 1' }]"
|
||||
:label="$t('quotation.form.customerBranchSelect')"
|
||||
id="select-branch-issuer"
|
||||
for="select-branch-issuer"
|
||||
style="width: 300px"
|
||||
class="q-mr-md"
|
||||
outlined
|
||||
dense
|
||||
/>
|
||||
<q-select
|
||||
lazy-rules="ondemand"
|
||||
v-model="selectedCustomer"
|
||||
:options="[{ label: 'Customer 1', value: 'Customer 1' }]"
|
||||
:label="$t('quotation.form.customerSelect')"
|
||||
id="select-customer"
|
||||
for="select-customer"
|
||||
style="width: 300px"
|
||||
outlined
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="surface-2 bordered-t row"
|
||||
style="align-items: stretch; overflow-y: auto"
|
||||
>
|
||||
<div
|
||||
class="col-12 col-sm-9 row items-stretch"
|
||||
style="padding: var(--size-3)"
|
||||
>
|
||||
<AppBox
|
||||
class="full-width"
|
||||
bordered
|
||||
style="
|
||||
padding: var(--size-3);
|
||||
max-height: calc(100vh - 300px - var(--size-3) * 2);
|
||||
overflow-y: auto;
|
||||
"
|
||||
>
|
||||
<div class="row items-center q-mb-sm">
|
||||
<span style="flex: 1">
|
||||
{{ $t('quotation.form.listWorker') }}
|
||||
<q-toggle
|
||||
v-model="toggleWorker"
|
||||
id="toggle-status"
|
||||
size="md"
|
||||
padding="none"
|
||||
class="q-ml-md"
|
||||
dense
|
||||
/>
|
||||
</span>
|
||||
<button
|
||||
id="add-btn-plus"
|
||||
class="row items-center"
|
||||
style="border: none; background: transparent"
|
||||
>
|
||||
<Icon
|
||||
height="24"
|
||||
icon="pixelarticons:plus"
|
||||
class="app-text-info cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<AppBox class="surface-2 worker-list" bordered>
|
||||
<WorkerItem
|
||||
:data="{
|
||||
no: 1,
|
||||
refNo: 'CC6613334',
|
||||
nationality: 'Thai',
|
||||
birthDate: '1 May 2001',
|
||||
age: '23 Years',
|
||||
fullName: 'Methapon Metanipat',
|
||||
docExpireDate: '16 May 2025',
|
||||
}"
|
||||
color="male"
|
||||
/>
|
||||
<WorkerItem
|
||||
:data="{
|
||||
no: 1,
|
||||
refNo: 'CC6613334',
|
||||
nationality: 'Thai',
|
||||
birthDate: '1 May 2001',
|
||||
age: '23 Years',
|
||||
fullName: 'Methapon Metanipat',
|
||||
docExpireDate: '16 May 2025',
|
||||
}"
|
||||
color="male"
|
||||
/>
|
||||
<q-tabs
|
||||
inline-label
|
||||
mobile-arrows
|
||||
dense
|
||||
align="left"
|
||||
class="full-width"
|
||||
active-color="info"
|
||||
>
|
||||
<q-tab name="ALL">
|
||||
<div class="row">{{ $t('all') }}</div>
|
||||
</q-tab>
|
||||
<q-tab name="USER">
|
||||
<div class="row">
|
||||
{{ $t('USER') }}
|
||||
</div>
|
||||
</q-tab>
|
||||
<q-tab name="MESSENGER">
|
||||
<div class="row">
|
||||
{{ $t('MESSENGER') }}
|
||||
</div>
|
||||
</q-tab>
|
||||
<q-tab name="DELEGATE">
|
||||
<div class="row">
|
||||
{{ $t('DELEGATE') }}
|
||||
</div>
|
||||
</q-tab>
|
||||
<q-tab name="AGENCY">
|
||||
<div class="row">
|
||||
{{ $t('AGENCY') }}
|
||||
</div>
|
||||
</q-tab>
|
||||
</q-tabs>
|
||||
</AppBox>
|
||||
</AppBox>
|
||||
</div>
|
||||
<div
|
||||
class="col-12 col-sm-3"
|
||||
style="
|
||||
padding: var(--size-3);
|
||||
padding-left: 0;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 300px);
|
||||
"
|
||||
>
|
||||
<AppBox bordered class="column" style="gap: var(--size-3)">
|
||||
<div class="rounded bordered q-px-md q-py-sm row">
|
||||
<div class="col-4">{{ $t('quotation.form.labelNo') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded bordered q-px-md q-py-sm row">
|
||||
<div class="col-4">{{ $t('quotation.form.labelDate') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded bordered q-px-md q-py-sm row">
|
||||
<div class="col-4">{{ $t('quotation.form.labelTime') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded bordered q-px-md q-py-sm row">
|
||||
<div class="col-4">{{ $t('quotation.form.labelProcesser') }}</div>
|
||||
</div>
|
||||
</AppBox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="row justify-end full-width">
|
||||
<q-btn
|
||||
color="primary"
|
||||
dense
|
||||
no-caps
|
||||
:label="$t('quotation.form.buttonSave')"
|
||||
class="q-px-md"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</MainDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.worker-list > :deep(*:not(:last-child)) {
|
||||
margin-bottom: var(--size-2);
|
||||
}
|
||||
</style>
|
||||
180
src/pages/05_quotation/QuotationForm.vue
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { QSelect } from 'quasar';
|
||||
|
||||
import AppBox from 'components/app/AppBox.vue';
|
||||
import MainDialog from 'components/05_quotation/MainDialog.vue';
|
||||
import WorkerItem from 'components/05_quotation/WorkerItem.vue';
|
||||
import QuotationFormInfo from './QuotationFormInfo.vue';
|
||||
import { AddButton, SaveButton } from 'components/button';
|
||||
|
||||
defineProps<{
|
||||
readonly?: boolean;
|
||||
}>();
|
||||
|
||||
const dialogState = defineModel<boolean>({ default: true });
|
||||
|
||||
const selectedBranchIssuer = ref('');
|
||||
const selectedCustomer = ref('');
|
||||
const toggleWorker = ref(true);
|
||||
|
||||
const quotationNo = ref('');
|
||||
const actor = ref('');
|
||||
const workName = ref('');
|
||||
const contactor = ref('');
|
||||
const telephone = ref('');
|
||||
const documentReceivePoint = ref('');
|
||||
const dueDate = ref('');
|
||||
const payType = ref('');
|
||||
const paySplitCount = ref('');
|
||||
const payBank = ref('');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainDialog v-model:state="dialogState">
|
||||
<template #title>
|
||||
<span class="text-weight-medium" style="color: var(--brand-1)">
|
||||
{{ $t('quotation.form.createTitle') }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<div class="row q-pa-md items-center">
|
||||
<div style="flex: 1"><q-img src="/logo.png" width="8rem" /></div>
|
||||
<q-select
|
||||
v-model="selectedBranchIssuer"
|
||||
:options="[{ label: 'Issuer 1', value: 'Issuer 1' }]"
|
||||
:label="$t('quotation.form.customerBranchSelect')"
|
||||
id="select-branch-issuer"
|
||||
for="select-branch-issuer"
|
||||
style="width: 300px"
|
||||
class="q-mr-md"
|
||||
outlined
|
||||
dense
|
||||
/>
|
||||
<q-select
|
||||
v-model="selectedCustomer"
|
||||
:options="[{ label: 'Customer 1', value: 'Customer 1' }]"
|
||||
:label="$t('quotation.form.customerSelect')"
|
||||
id="select-customer"
|
||||
for="select-customer"
|
||||
style="width: 300px"
|
||||
outlined
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="surface-2 bordered-t row"
|
||||
style="flex-grow: 1; overflow-y: hidden"
|
||||
:style="{
|
||||
overflowY: $q.screen.gt.sm ? 'hidden' : 'auto',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="col-12 col-sm-9 row"
|
||||
style="padding: var(--size-3); overflow-y: auto"
|
||||
:style="{
|
||||
paddingRight: $q.screen.gt.sm ? 'var(--size-2)' : 'var(--size-3)',
|
||||
maxHeight: $q.screen.gt.sm ? '100%' : undefined,
|
||||
}"
|
||||
>
|
||||
<div class="col">
|
||||
<AppBox bordered style="padding: var(--size-3)">
|
||||
<div class="row items-center q-mb-sm">
|
||||
<span style="flex: 1">
|
||||
{{ $t('quotation.label.listWorker') }}
|
||||
<q-toggle
|
||||
v-model="toggleWorker"
|
||||
id="toggle-status"
|
||||
size="md"
|
||||
padding="none"
|
||||
class="q-ml-md"
|
||||
dense
|
||||
/>
|
||||
</span>
|
||||
<AddButton icon-only />
|
||||
</div>
|
||||
|
||||
<AppBox class="surface-2 worker-list" v-if="toggleWorker" bordered>
|
||||
<WorkerItem
|
||||
v-for="_ in Array(20)"
|
||||
:data="{
|
||||
no: 1,
|
||||
refNo: 'CC6613334',
|
||||
nationality: 'Thai',
|
||||
birthDate: '1 May 2001',
|
||||
age: '23 Years',
|
||||
fullName: 'Methapon Metanipat',
|
||||
docExpireDate: '16 May 2025',
|
||||
}"
|
||||
color="male"
|
||||
/>
|
||||
</AppBox>
|
||||
</AppBox>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-12 col-sm-3"
|
||||
style="padding: var(--size-3); overflow-y: auto"
|
||||
:style="{
|
||||
paddingLeft: $q.screen.gt.sm ? 'var(--size-1)' : 'var(--size-3)',
|
||||
maxHeight: $q.screen.gt.sm ? '100%' : undefined,
|
||||
}"
|
||||
>
|
||||
<QuotationFormInfo
|
||||
v-model:quotation-no="quotationNo"
|
||||
v-model:actor="actor"
|
||||
v-model:work-name="workName"
|
||||
v-model:contactor="contactor"
|
||||
v-model:telephone="telephone"
|
||||
v-model:document-receive-point="documentReceivePoint"
|
||||
v-model:due-date="dueDate"
|
||||
v-model:pay-type="payType"
|
||||
v-model:pay-bank="payBank"
|
||||
v-model:pay-split-count="paySplitCount"
|
||||
:readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="row justify-end full-width">
|
||||
<SaveButton />
|
||||
</div>
|
||||
</template>
|
||||
</MainDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.worker-list > :deep(*:not(:last-child)) {
|
||||
margin-bottom: var(--size-2);
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1/1;
|
||||
font-size: 1.5rem;
|
||||
padding: var(--size-1);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
|
||||
.bg-color-orange {
|
||||
--_color: var(--yellow-7-hsl);
|
||||
color: white;
|
||||
background: hsla(var(--_color));
|
||||
}
|
||||
|
||||
.dark .bg-color-orange {
|
||||
--_color: var(--orange-6-hsl);
|
||||
}
|
||||
|
||||
.bg-color-orange-light {
|
||||
--_color: var(--yellow-7-hsl);
|
||||
background: hsla(var(--_color) / 0.2);
|
||||
}
|
||||
.dark .bg-color-orange {
|
||||
--_color: var(--orange-6-hsl / 0.2);
|
||||
}
|
||||
</style>
|
||||
328
src/pages/05_quotation/QuotationFormInfo.vue
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
<script lang="ts" setup>
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { QSelect } from 'quasar';
|
||||
import { selectFilterOptionRefMod } from 'src/stores/utils';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import AppBox from 'components/app/AppBox.vue';
|
||||
|
||||
import useOptionStore from 'src/stores/options';
|
||||
|
||||
defineProps<{
|
||||
readonly?: boolean;
|
||||
data?: {
|
||||
total: number;
|
||||
discount: number;
|
||||
totalVatExcluded: number;
|
||||
totalVatIncluded: number;
|
||||
totalAfterDiscount: number;
|
||||
};
|
||||
}>();
|
||||
|
||||
const quotationNo = defineModel<string>('quotationNo', { required: true });
|
||||
const actor = defineModel<string>('actor', { required: true });
|
||||
const workName = defineModel<string>('workName', { required: true });
|
||||
const contactor = defineModel<string>('contactor', { required: true });
|
||||
const telephone = defineModel<string>('telephone', { required: true });
|
||||
const documentReceivePoint = defineModel<string>('documentReceivePoint', {
|
||||
required: true,
|
||||
});
|
||||
const dueDate = defineModel<string>('dueDate', { required: true });
|
||||
const payType = defineModel<string>('payType', { required: true });
|
||||
const paySplitCount = defineModel<string>('paySplitCount', { required: true });
|
||||
const payBank = defineModel<string>('payBank', { required: true });
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
||||
const bankBookOptions = ref<Record<string, unknown>[]>([]);
|
||||
let bankBoookFilter: (
|
||||
value: string,
|
||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
||||
) => void;
|
||||
|
||||
onMounted(() => {
|
||||
if (optionStore.globalOption) {
|
||||
bankBoookFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.bankBook),
|
||||
bankBookOptions,
|
||||
'label',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => optionStore.globalOption,
|
||||
() => {
|
||||
bankBoookFilter = selectFilterOptionRefMod(
|
||||
ref(optionStore.globalOption.bankBook),
|
||||
bankBookOptions,
|
||||
'label',
|
||||
);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppBox no-padding bordered class="column">
|
||||
<div
|
||||
class="bordered-b q-px-md q-py-sm row bg-color-orange-light items-center"
|
||||
>
|
||||
<div class="icon-wrapper bg-color-orange q-mr-sm">
|
||||
<q-icon name="mdi-file-outline" />
|
||||
</div>
|
||||
<span class="text-weight-bold">
|
||||
{{ $t('quotation.label.infoDocument') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="q-pa-sm">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<q-input
|
||||
:label="$t('quotation.label.quotationNo')"
|
||||
:readonly
|
||||
v-model="quotationNo"
|
||||
class="col-12"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
<q-input
|
||||
:label="$t('quotation.label.actor')"
|
||||
:readonly
|
||||
v-model="actor"
|
||||
class="col-12"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
<q-input
|
||||
:label="$t('quotation.label.workName')"
|
||||
:readonly
|
||||
v-model="workName"
|
||||
class="col-12"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
<q-input
|
||||
:label="$t('quotation.label.contactor')"
|
||||
:readonly
|
||||
v-model="contactor"
|
||||
class="col-12"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
<q-input
|
||||
:label="$t('quotation.label.telephone')"
|
||||
:readonly="readonly"
|
||||
v-model="telephone"
|
||||
class="col-12"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
<q-input
|
||||
:label="$t('quotation.label.documentReceivePoint')"
|
||||
:readonly
|
||||
v-model="documentReceivePoint"
|
||||
class="col-12"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
<q-input
|
||||
:label="$t('quotation.label.dueDate')"
|
||||
:readonly
|
||||
v-model="dueDate"
|
||||
class="col-12"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bordered-b q-px-md q-py-sm row bg-color-orange-light items-center"
|
||||
>
|
||||
<div class="icon-wrapper bg-color-orange q-mr-sm">
|
||||
<q-icon name="mdi-bank-outline" />
|
||||
</div>
|
||||
<span class="text-weight-bold">
|
||||
{{ $t('quotation.label.infoPayment') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="q-pa-sm">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
class="col-12"
|
||||
autocomplete="off"
|
||||
for="select-payType"
|
||||
dense
|
||||
:label="$t('quotation.label.payType')"
|
||||
:options="[]"
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="payType"
|
||||
></q-select>
|
||||
<div class="col-8 column">
|
||||
<q-field readonly dense outlined>
|
||||
<template #control>
|
||||
<span>{{ $t('quotation.label.paySplitCount') }}</span>
|
||||
<span class="app-text-muted">
|
||||
({{ $t('quotation.label.payTotal') }})
|
||||
</span>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input
|
||||
v-model="paySplitCount"
|
||||
:readonly
|
||||
class="col-6"
|
||||
type="number"
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
</div>
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
emit-value
|
||||
fill-input
|
||||
map-options
|
||||
hide-bottom-space
|
||||
option-value="value"
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
class="col-12"
|
||||
autocomplete="off"
|
||||
for="select-bankbook"
|
||||
dense
|
||||
:label="$t('quotation.label.bank')"
|
||||
:options="bankBookOptions"
|
||||
:readonly
|
||||
:hide-dropdown-icon="readonly"
|
||||
v-model="payBank"
|
||||
@filter="bankBoookFilter"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item
|
||||
v-if="scope.opt"
|
||||
v-bind="scope.itemProps"
|
||||
class="row items-center"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-img
|
||||
:src="`/img/bank/${scope.opt.value}.png`"
|
||||
class="bordered"
|
||||
style="
|
||||
border-radius: 50%;
|
||||
aspect-ratio: 1;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ scope.opt.label }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<template v-slot:selected-item="scope">
|
||||
<q-item-section
|
||||
v-if="scope.opt && !!payBank"
|
||||
avatar
|
||||
class="q-py-sm row"
|
||||
>
|
||||
<q-img
|
||||
:src="`/img/bank/${scope.opt.value}.png`"
|
||||
class="bordered"
|
||||
style="
|
||||
border-radius: 50%;
|
||||
aspect-ratio: 1;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
"
|
||||
/>
|
||||
</q-item-section>
|
||||
</template>
|
||||
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bordered-b q-px-md q-py-sm row bg-color-orange-light items-center"
|
||||
>
|
||||
<div class="icon-wrapper bg-color-orange q-mr-sm">
|
||||
<Icon icon="iconoir:coins" />
|
||||
</div>
|
||||
<span class="text-weight-bold">
|
||||
{{ $t('quotation.label.infoSummary') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="q-pa-sm">
|
||||
<div class="row">
|
||||
{{ $t('general.total') }}
|
||||
<span class="q-ml-auto">{{ data?.total || 0 }} ฿</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
{{ $t('general.discount') }}
|
||||
<span class="q-ml-auto">{{ data?.discount || 0 }} ฿</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
{{ $t('general.totalAfterDiscount') }}
|
||||
<span class="q-ml-auto">{{ data?.totalAfterDiscount || 0 }} ฿</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
{{ $t('general.totalVatExcluded') }}
|
||||
<span class="q-ml-auto">{{ data?.totalVatExcluded || 0 }} ฿</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
{{ $t('general.totalVatIncluded') }}
|
||||
<span class="q-ml-auto">{{ data?.totalVatIncluded || 0 }} ฿</span>
|
||||
</div>
|
||||
</div>
|
||||
</AppBox>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1/1;
|
||||
font-size: 1.5rem;
|
||||
padding: var(--size-1);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
|
||||
.bg-color-orange {
|
||||
--_color: var(--yellow-7-hsl);
|
||||
color: white;
|
||||
background: hsla(var(--_color));
|
||||
}
|
||||
|
||||
.dark .bg-color-orange {
|
||||
--_color: var(--orange-6-hsl);
|
||||
}
|
||||
|
||||
.bg-color-orange-light {
|
||||
--_color: var(--yellow-7-hsl);
|
||||
background: hsla(var(--_color) / 0.2);
|
||||
}
|
||||
.dark .bg-color-orange {
|
||||
--_color: var(--orange-6-hsl / 0.2);
|
||||
}
|
||||
</style>
|
||||
30
src/pages/05_quotation/constants.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export const productTreeDecoration = [
|
||||
{
|
||||
level: 0,
|
||||
icon: 'mdi-folder-outline',
|
||||
bg: 'hsla(var(--pink-6-hsl)/0.1)',
|
||||
fg: 'var(--pink-6)',
|
||||
},
|
||||
{
|
||||
level: 1,
|
||||
icon: 'mdi-server-outline',
|
||||
bg: 'hsla(var(--orange-5-hsl)/0.1)',
|
||||
fg: 'var(--orange-5)',
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
icon: 'mdi-shopping-outline',
|
||||
bg: 'hsla(var(--teal-10-hsl)/0.1)',
|
||||
fg: 'var(--teal-10)',
|
||||
},
|
||||
];
|
||||
|
||||
export const pageTabs = [
|
||||
'all',
|
||||
'fullAmountCash',
|
||||
'installmentsCash',
|
||||
'fullAmountBill',
|
||||
'installmentsBill',
|
||||
];
|
||||
|
||||
export const fieldSelectedOption = [{ label: 'general.type', value: 'value' }];
|
||||
|
|
@ -1,13 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { getInstance } from 'src/services/keycloak';
|
||||
import useUtilsStore, { dialog, notify } from 'stores/utils';
|
||||
import useUtilsStore from 'stores/utils';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
const $q = useQuasar();
|
||||
const { locale } = useI18n();
|
||||
const utilsStore = useUtilsStore();
|
||||
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(() => {
|
||||
utilsStore.currentTitle.title = 'menu.dms';
|
||||
|
|
@ -18,6 +33,7 @@ onMounted(() => {
|
|||
handler: () => {},
|
||||
},
|
||||
];
|
||||
sendMessage();
|
||||
});
|
||||
|
||||
watch(
|
||||
|
|
@ -27,10 +43,15 @@ watch(
|
|||
rt.value = kc.refreshToken;
|
||||
},
|
||||
);
|
||||
|
||||
watch([locale, $q.dark], () => {
|
||||
sendMessage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe
|
||||
ref="iframe"
|
||||
:src="`${EDM_SERVICE}/?at=${at}&rt=${rt}`"
|
||||
frameborder="0"
|
||||
class="full-width full-height rounded"
|
||||
|
|
|
|||