Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 9s

This commit is contained in:
Methapon2001 2025-03-27 10:30:06 +07:00
commit 71c1f9c770
42 changed files with 1733 additions and 14298 deletions

View file

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

View file

@ -1,61 +0,0 @@
module.exports = {
root: true,
parserOptions: {
parser: require.resolve('@typescript-eslint/parser'),
extraFileExtensions: ['.vue'],
},
env: {
browser: true,
es2021: true,
node: true,
'vue/setup-compiler-macros': true,
},
extends: [
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
// ESLint typescript rules
'plugin:@typescript-eslint/recommended',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/vue3-essential',
// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
'prettier',
],
plugins: [
// required to apply rules which need type information
'@typescript-eslint',
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue',
],
globals: {
ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly',
},
// add your custom rules here
rules: {
quotes: ['warn', 'single', { avoidEscape: true }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'prefer-promise-reject-errors': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
};

12398
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,16 +7,16 @@
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "scripts": {
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0", "test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev", "dev": "quasar dev",
"build": "quasar build", "build": "quasar build",
"postinstall": "quasar prepare",
"changelog:generate": "git-cliff -o CHANGELOG.md" "changelog:generate": "git-cliff -o CHANGELOG.md"
}, },
"dependencies": { "dependencies": {
"@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0", "@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0",
"@quasar/extras": "^1.16.12", "@quasar/extras": "^1.16.17",
"@tato30/vue-pdf": "^1.11.0", "@tato30/vue-pdf": "^1.11.0",
"@vuepic/vue-datepicker": "^8.8.1", "@vuepic/vue-datepicker": "^8.8.1",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
@ -36,7 +36,7 @@
"number-to-words": "^1.2.4", "number-to-words": "^1.2.4",
"open-props": "^1.7.5", "open-props": "^1.7.5",
"pinia": "^2.2.2", "pinia": "^2.2.2",
"quasar": "^2.16.9", "quasar": "^2.18.1",
"signature_pad": "^5.0.2", "signature_pad": "^5.0.2",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.7.5",
"tesseract.js": "^5.1.1", "tesseract.js": "^5.1.1",
@ -45,35 +45,31 @@
"uuid": "^10.0.0", "uuid": "^10.0.0",
"vue": "^3.4.38", "vue": "^3.4.38",
"vue-dragscroll": "^4.0.6", "vue-dragscroll": "^4.0.6",
"vue-i18n": "^9.14.0", "vue-i18n": "^11.1.2",
"vue-pdf": "^4.3.0", "vue-pdf": "^4.3.0",
"vue-router": "^4.4.3", "vue-router": "^4.4.3",
"vue3-apexcharts": "^1.7.0" "vue-tsc": "^2.2.8",
"vue3-apexcharts": "^1.8.0"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^9.3.0", "@faker-js/faker": "^9.3.0",
"@iconify/vue": "^4.1.2", "@iconify/vue": "^4.1.2",
"@intlify/unplugin-vue-i18n": "^4.0.0", "@intlify/unplugin-vue-i18n": "^6.0.5",
"@playwright/test": "^1.46.1", "@playwright/test": "^1.46.1",
"@quasar/app-vite": "2.0.0-beta.19", "@quasar/app-vite": "^2.2.0",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/markdown-it-highlightjs": "^3.3.4", "@types/markdown-it-highlightjs": "^3.3.4",
"@types/node": "^20.16.1", "@types/node": "^20.16.1",
"@types/number-to-words": "^1.2.3", "@types/number-to-words": "^1.2.3",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.27.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vue-component-type-helpers": "^2.1.10" "vue-component-type-helpers": "^2.1.10"
}, },
"engines": { "engines": {
"node": "^24 || ^22 || ^20 || ^18", "node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",
"npm": ">= 6.13.4", "npm": ">= 6.13.4",
"yarn": ">= 1.21.1" "yarn": ">= 1.21.1"
} }

2712
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

18
postcss.config.js Normal file
View file

@ -0,0 +1,18 @@
import autoprefixer from 'autoprefixer';
export default {
plugins: [
autoprefixer({
overrideBrowserslist: [
'last 4 Chrome versions',
'last 4 Firefox versions',
'last 4 Edge versions',
'last 4 Safari versions',
'last 4 Android versions',
'last 4 ChromeAndroid versions',
'last 4 FirefoxAndroid versions',
'last 4 iOS versions',
],
}),
],
};

View file

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

View file

@ -1,167 +0,0 @@
{
"config": {
"configFile": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/playwright.config.ts",
"rootDir": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/tests",
"forbidOnly": false,
"fullyParallel": true,
"globalSetup": null,
"globalTeardown": null,
"globalTimeout": 0,
"grep": {},
"grepInvert": null,
"maxFailures": 0,
"metadata": {
"actualWorkers": 1
},
"preserveOutput": "always",
"reporter": [
[
"json",
{
"outputFile": "reports.json"
}
]
],
"reportSlowTests": {
"max": 5,
"threshold": 15000
},
"quiet": false,
"projects": [
{
"outputDir": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/test-results",
"repeatEach": 1,
"retries": 0,
"metadata": {},
"id": "chromium",
"name": "chromium",
"testDir": "/Users/linping/Desktop/Chamomind&FrappeT/JWS_TestScript/tests",
"testIgnore": [],
"testMatch": [
"**/*.@(spec|test).?(c|m)[jt]s?(x)"
],
"timeout": 30000
}
],
"shard": null,
"updateSnapshots": "missing",
"version": "1.44.1",
"workers": 1,
"webServer": null
},
"suites": [
{
"title": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
"column": 0,
"line": 0,
"specs": [
{
"title": "Login",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 30000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "chromium",
"projectName": "chromium",
"results": [
{
"workerIndex": 4,
"status": "passed",
"duration": 3024,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2024-07-30T02:59:00.817Z",
"attachments": []
}
],
"status": "expected"
}
],
"id": "8c5091bd59605f227965-8109f0f4a59e27330a76",
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
"line": 16,
"column": 1
},
{
"title": "Create Branch Managenment",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 30000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "chromium",
"projectName": "chromium",
"results": [
{
"workerIndex": 4,
"status": "passed",
"duration": 5091,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2024-07-30T02:59:05.659Z",
"attachments": []
}
],
"status": "expected"
}
],
"id": "8c5091bd59605f227965-5a0d70f27623401a3479",
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
"line": 27,
"column": 1
},
{
"title": "Create Branch Managenment Second",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 30000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "chromium",
"projectName": "chromium",
"results": [
{
"workerIndex": 4,
"status": "passed",
"duration": 5029,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2024-07-30T02:59:10.755Z",
"attachments": []
}
],
"status": "expected"
}
],
"id": "8c5091bd59605f227965-d619bd2184e7f07d4970",
"file": "01-Admin-BranchManagement/JWS_BM_001_CreateHeadquarters.spec.ts",
"line": 52,
"column": 1
}
]
}
],
"errors": [],
"stats": {
"startTime": "2024-07-30T02:59:00.334Z",
"duration": 15556.794999999925,
"expected": 3,
"skipped": 0,
"unexpected": 0,
"flaky": 0
}
}

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { boot } from 'quasar/wrappers'; import { defineBoot } from '#q-app/wrappers';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import messages from 'src/i18n'; import messages from 'src/i18n';
@ -21,16 +21,17 @@ declare module 'vue-i18n' {
} }
/* eslint-enable @typescript-eslint/no-empty-interface */ /* eslint-enable @typescript-eslint/no-empty-interface */
export const i18n = createI18n({ export const i18n = createI18n<
locale: 'tha', { message: MessageSchema },
MessageLanguages,
false
>({
locale: 'en-US',
legacy: false, legacy: false,
messages: { messages,
'en-US': {},
...messages,
},
}); });
export default boot(({ app }) => { export default defineBoot(({ app }) => {
// Set i18n instance on app // Set i18n instance on app
app.use(i18n); app.use(i18n);
}); });

View file

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

2
src/env.d.ts vendored
View file

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

View file

@ -151,6 +151,8 @@ export default {
dueDate: 'Due date', dueDate: 'Due date',
year: 'year', year: 'year',
tableOfContent: 'Table of Contents', tableOfContent: 'Table of Contents',
draw: 'Draw',
newUpload: 'New Upload',
}, },
menu: { menu: {
@ -874,7 +876,7 @@ export default {
SplitCustom: 'Custom Installments Bill', SplitCustom: 'Custom Installments Bill',
BillFull: 'Full Amount Bill', BillFull: 'Full Amount Bill',
BillSplit: 'Installments Bill', BillSplit: 'Installments Bill',
BillCustomSplit: 'Custom Installments Bill', BillSplitCustom: 'Custom Installments Bill',
}, },
status: { status: {
@ -1072,10 +1074,8 @@ export default {
confirmSavingStatus: confirmSavingStatus:
'Do you want to confirm the saving of the status change data?', 'Do you want to confirm the saving of the status change data?',
confirmSending: 'Do you confirm the submission of the task?', confirmSending: 'Do you confirm the submission of the task?',
confirmValidate: 'Do you confirm the validation?',
warningSelectDeliveryStaff: warningSelectDeliveryStaff:
'You have not yet selected a document delivery staff.', 'You have not yet selected a document delivery staff.',
confirmEndWorkWarning: confirmEndWorkWarning:
"Do you want to end the work now? The current statuses 'Pending', 'In Progress', 'To Be Reprocessed' will be changed to 'Redo All'.", "Do you want to end the work now? The current statuses 'Pending', 'In Progress', 'To Be Reprocessed' will be changed to 'Redo All'.",
confirmEndWork: 'Do you want to end the work?', confirmEndWork: 'Do you want to end the work?',
@ -1187,9 +1187,9 @@ export default {
validateError: 'Validate Error', validateError: 'Validate Error',
codeMisMatch: 'Code Mismatch', codeMisMatch: 'Code Mismatch',
crossCompanyNotPermit: 'Cannot move between different headoffice', crossCompanyNotPermit: 'Cannot move between different headoffice',
errorOccure: errorOccurred:
'An error has occurred, causing the system to be unable to function. Please try again later.', 'An error has occurred, causing the system to be unable to function. Please try again later.',
invalideData: 'The information is incorrect. Please try again later.', invalidData: 'The information is incorrect. Please try again later.',
authFailed: 'Authentication Failed. Please try again later. ', authFailed: 'Authentication Failed. Please try again later. ',
installmentsValidateFailed: installmentsValidateFailed:
'Validation failed. Each installment must include at least one product. Please review and update the installments accordingly.', 'Validation failed. Each installment must include at least one product. Please review and update the installments accordingly.',

View file

@ -151,6 +151,8 @@ export default {
dueDate: 'วันครบกำหนด', dueDate: 'วันครบกำหนด',
year: 'ปี', year: 'ปี',
tableOfContent: 'สารบัญ', tableOfContent: 'สารบัญ',
draw: 'วาด',
newUpload: 'อัปโหลดใหม่',
}, },
menu: { menu: {
@ -1165,9 +1167,9 @@ export default {
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ', validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
codeMisMatch: 'รหัสไม่ตรงกัน', codeMisMatch: 'รหัสไม่ตรงกัน',
crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้', crossCompanyNotPermit: 'ไม่สามารถดำเนินการระหว่างสำนักงานใหญ่อื่นได้',
errorOccure: errorOccurred:
'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง', 'เกิดข้อผิดพลาดทำให้ระบบไม่สามารถทำงานได้ กรุณาลองใหม่ในภายหลัง',
invalideData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง', invalidData: 'ข้อมูลไม่ถูกต้อง กรุณาตรวจสอบใหม่อีกครั้ง',
authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง', authFailed: 'การยืนยันตัวตนล้มเหลว กรุณาลองใหม่ในภายหลัง',
installmentsValidateFailed: installmentsValidateFailed:
'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ', 'ข้อมูลงวดไม่ถูกต้อง กรุณาตรวจสอบและยืนยันว่าแต่ละงวดมีสินค้าอย่างน้อยหนึ่งรายการ',

View file

@ -50,7 +50,6 @@ const leftDrawerMini = ref(false);
const unread = computed<number>( const unread = computed<number>(
() => notificationData.value.filter((v) => !v.read).length || 0, () => notificationData.value.filter((v) => !v.read).length || 0,
); );
// const filterRole = ref<string[]>();
const userImage = ref<string>(); const userImage = ref<string>();
const userGender = ref(''); const userGender = ref('');
const canvasRef = ref(); const canvasRef = ref();
@ -124,6 +123,17 @@ function readNoti(id: string) {
} }
} }
function signatureSubmit() {
const signature = canvasRef.value.setCanvas();
userStore.setSignature(signature);
canvasModal.value = false;
}
async function signatureFetch() {
const ret = await userStore.getSignature();
if (ret) canvasRef.value.getCanvas(ret);
}
onMounted(async () => { onMounted(async () => {
initTheme(); initTheme();
initLang(); initLang();
@ -367,12 +377,28 @@ onMounted(async () => {
<div class="col column text-caption q-pl-md ellipsis"> <div class="col column text-caption q-pl-md ellipsis">
<span class="block ellipsis full-width text-weight-bold"> <span class="block ellipsis full-width text-weight-bold">
{{ item.title }} {{ item.title }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.title }}
</q-tooltip>
</span> </span>
<span <span
class="block ellipsis full-width text-stone" class="block ellipsis full-width text-stone"
:class="{ 'text-weight-medium': !item.read }" :class="{ 'text-weight-medium': !item.read }"
> >
{{ item.detail }} {{ item.detail }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="300"
:offset="[10, 10]"
>
{{ item.detail }}
</q-tooltip>
</span> </span>
</div> </div>
<span <span
@ -382,15 +408,6 @@ onMounted(async () => {
> >
{{ moment(item.createdAt).fromNow() }} {{ moment(item.createdAt).fromNow() }}
</span> </span>
<q-tooltip
anchor="top middle"
self="bottom middle"
:delay="1000"
:offset="[10, 10]"
>
{{ item.title }}
{{ item.detail }}
</q-tooltip>
</q-item> </q-item>
</section> </section>
<section <section
@ -495,13 +512,15 @@ onMounted(async () => {
no-app-box no-app-box
:title="$t('menu.profile.addSignature')" :title="$t('menu.profile.addSignature')"
:close="() => (canvasModal = false)" :close="() => (canvasModal = false)"
:submit="signatureSubmit"
:show="signatureFetch"
> >
<CanvasComponent ref="canvasRef" v-model:modal="canvasModal" /> <CanvasComponent ref="canvasRef" v-model:modal="canvasModal" />
<template #footer> <template #footer>
<q-btn <q-btn
flat flat
dense dense
:label="$t('clear')" :label="$t('general.clear')"
@click=" @click="
() => { () => {
canvasRef.clearCanvas(), canvasRef.clearUpload(); canvasRef.clearCanvas(), canvasRef.clearUpload();

View file

@ -31,7 +31,7 @@ const options = [
label: 'menu.profile.signature', label: 'menu.profile.signature',
value: 'signature', value: 'signature',
color: 'grey', color: 'grey',
disabled: true, disabled: false,
}, },
{ {
icon: 'mdi-brightness-6', icon: 'mdi-brightness-6',

2
src/markdown-it.d.ts vendored Normal file
View file

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

View file

@ -5,9 +5,7 @@ import hljs from 'highlight.js';
import { nextTick, onMounted, onUnmounted, ref } from 'vue'; import { nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
// @ts-expect-error
import mditFigureWithPCaption from 'markdown-it-image-figures'; import mditFigureWithPCaption from 'markdown-it-image-figures';
// @ts-expect-error
import mditMedia from 'markdown-it-html5-media'; import mditMedia from 'markdown-it-html5-media';
import mditAnchor from 'markdown-it-anchor'; import mditAnchor from 'markdown-it-anchor';
import mditHighlight from 'markdown-it-highlightjs'; import mditHighlight from 'markdown-it-highlightjs';

View file

@ -25,7 +25,7 @@ import { deleteItem } from 'stores/utils';
// NOTE Import Types // NOTE Import Types
import { RequestData, RequestDataStatus } from 'src/stores/request-list/types'; import { RequestData, RequestDataStatus } from 'src/stores/request-list/types';
import { View } from './types.ts'; import { View } from './types';
import { import {
PayCondition, PayCondition,
ProductRelation, ProductRelation,
@ -76,6 +76,7 @@ import { api } from 'src/boot/axios';
import { RouterLink, useRoute } from 'vue-router'; import { RouterLink, useRoute } from 'vue-router';
import { initLang, initTheme, Lang } from 'src/utils/ui'; import { initLang, initTheme, Lang } from 'src/utils/ui';
import { convertTemplate } from 'src/utils/string-template'; import { convertTemplate } from 'src/utils/string-template';
import { getRole } from 'src/services/keycloak';
type Node = { type Node = {
[key: string]: any; [key: string]: any;
@ -211,6 +212,17 @@ const attachmentData = ref<
url?: string; url?: string;
}[] }[]
>([]); >([]);
const hideBtnApproveInvoice = computed(() => {
const role = getRole();
const allowedRoles = [
'system',
'head_of_admin',
'admin',
'head_of_accountant',
'accountant',
];
return !role || !role.some((r) => allowedRoles.includes(r));
});
const getToolbarConfig = computed(() => { const getToolbarConfig = computed(() => {
const toolbar = [['left', 'center', 'justify'], ['toggle'], ['clip']]; const toolbar = [['left', 'center', 'justify'], ['toggle'], ['clip']];
@ -2317,6 +2329,7 @@ function covertToNode() {
" "
> >
<MainButton <MainButton
v-if="!hideBtnApproveInvoice"
solid solid
icon="mdi-account-multiple-check-outline" icon="mdi-account-multiple-check-outline"
color="207 96% 32%" color="207 96% 32%"

View file

@ -3,7 +3,7 @@ import { Icon } from '@iconify/vue';
import { ref, watch, computed } from 'vue'; import { ref, watch, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { View } from './types.ts'; import { View } from './types';
import { formatNumberDecimal, commaInput } from 'stores/utils'; import { formatNumberDecimal, commaInput } from 'stores/utils';
@ -14,7 +14,7 @@ import SelectInput from 'src/components/shared/SelectInput.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { precisionRound } from 'src/utils/arithmetic'; import { precisionRound } from 'src/utils/arithmetic';
import { PayCondition } from 'src/stores/quotations/types.ts'; import { PayCondition } from 'src/stores/quotations/types';
defineEmits<{ defineEmits<{
(e: 'changePayType', type: PayCondition): void; (e: 'changePayType', type: PayCondition): void;

View file

@ -11,6 +11,7 @@ import { useRequestList } from 'src/stores/request-list';
const props = defineProps<{ const props = defineProps<{
readonly?: boolean; readonly?: boolean;
step: Step; step: Step;
requestWorkId: string;
}>(); }>();
const requestListStore = useRequestList(); const requestListStore = useRequestList();
@ -116,7 +117,7 @@ function assignToForm() {
<FormGroupHead> <FormGroupHead>
{{ $t('quotation.templateForm') }} {{ $t('quotation.templateForm') }}
</FormGroupHead> </FormGroupHead>
<FormIssue :readonly="!state.isEdit" /> <FormIssue :request-work-id="requestWorkId" :readonly="!state.isEdit" />
</section> </section>
</main> </main>
</q-expansion-item> </q-expansion-item>

View file

@ -2,14 +2,49 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { MainButton } from 'components/button'; import { MainButton } from 'components/button';
import SelectInput from 'src/components/shared/SelectInput.vue'; import SelectInput from 'src/components/shared/SelectInput.vue';
import { onMounted } from 'vue';
import { api } from 'src/boot/axios';
import { baseUrl } from 'src/stores/utils';
defineProps<{ const prop = defineProps<{
readonly?: boolean; readonly?: boolean;
requestWorkId: string;
}>(); }>();
const templateForm = defineModel<string>(); const templateForm = defineModel<string>();
const templateFormOption = ref< const templateFormOption = ref<
{ label: string; labelEN: string; value: string }[] { label: string; labelEN: string; value: string }[]
>([]); >([]);
onMounted(async () => {
const { data: docTemplate, status } = await api.get<string[]>(
'/doc-template',
{ params: { templateGroup: 'Request' } },
);
if (status < 400) {
templateFormOption.value = docTemplate.map((v) => ({
label: v,
labelEN: v,
value: v,
}));
}
});
async function formDownload() {
const res = await fetch(
baseUrl +
'/doc-template/' +
templateForm.value +
`?templateGroup=Request&data=request-work&dataId=${prop.requestWorkId}`,
);
const blob = await res.blob();
const a = document.createElement('a');
a.download = templateForm.value;
a.href = window.URL.createObjectURL(blob);
a.click();
a.remove();
}
</script> </script>
<template> <template>
@ -24,7 +59,6 @@ const templateFormOption = ref<
<SelectInput <SelectInput
id="quotation-branch" id="quotation-branch"
style="grid-column: span 2" style="grid-column: span 2"
incremental
v-model="templateForm" v-model="templateForm"
class="full-width" class="full-width"
:readonly :readonly
@ -32,21 +66,13 @@ const templateFormOption = ref<
:label="$t('quotation.templateForm')" :label="$t('quotation.templateForm')"
:option-label="$i18n.locale === 'eng' ? 'labelEN' : 'label'" :option-label="$i18n.locale === 'eng' ? 'labelEN' : 'label'"
/> />
<MainButton
outlined
icon="mdi-play-box-outline"
color="207 96% 32%"
class="full-width"
style="grid-column: span 1"
>
{{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton>
<MainButton <MainButton
solid solid
icon="mdi-pencil-outline" icon="mdi-pencil-outline"
color="207 96% 32%" color="207 96% 32%"
class="full-width" class="full-width"
style="grid-column: span 1" style="grid-column: span 1"
@click="formDownload"
> >
{{ $t('general.designForm') }} {{ $t('general.designForm') }}
</MainButton> </MainButton>

View file

@ -906,6 +906,7 @@ async function submitRejectCancel() {
/> />
<FormExpansion <FormExpansion
v-if="value._formExpansion" v-if="value._formExpansion"
:request-work-id="value.id"
:readonly=" :readonly="
data.requestDataStatus === RequestDataStatus.Canceled data.requestDataStatus === RequestDataStatus.Canceled
" "

View file

@ -4,7 +4,7 @@ import { storeToRefs } from 'pinia';
import { QTableSlots } from 'quasar'; import { QTableSlots } from 'quasar';
import { CreditNote, useCreditNote } from 'src/stores/credit-note'; import { CreditNote, useCreditNote } from 'src/stores/credit-note';
import { columns } from './constants.ts'; import { columns } from './constants';
import KebabAction from 'src/components/shared/KebabAction.vue'; import KebabAction from 'src/components/shared/KebabAction.vue';
const creditNote = useCreditNote(); const creditNote = useCreditNote();

View file

@ -4,7 +4,7 @@ import { storeToRefs } from 'pinia';
import { QTableSlots } from 'quasar'; import { QTableSlots } from 'quasar';
import { DebitNote, useDebitNote } from 'src/stores/debit-note'; import { DebitNote, useDebitNote } from 'src/stores/debit-note';
import { columns } from './constants.ts'; import { columns } from './constants';
import KebabAction from 'src/components/shared/KebabAction.vue'; import KebabAction from 'src/components/shared/KebabAction.vue';
const debitNote = useDebitNote(); const debitNote = useDebitNote();

9
src/quasar.d.ts vendored
View file

@ -1,9 +0,0 @@
/* eslint-disable */
// Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package
// Removing this would break `quasar/wrappers` imports as those typings are declared
// into `@quasar/app-vite`
// As a side effect, since `@quasar/app-vite` reference `quasar` to augment it,
// this declaration also apply `quasar` own
// augmentations (eg. adds `$q` into Vue component context)
/// <reference types="@quasar/app-vite" />

View file

@ -1,4 +1,4 @@
import { route } from 'quasar/wrappers'; import { defineRouter } from '#q-app/wrappers';
import { import {
createMemoryHistory, createMemoryHistory,
createRouter, createRouter,
@ -17,7 +17,7 @@ import routes from './routes';
* with the Router instance. * with the Router instance.
*/ */
export default route(function (/* { store, ssrContext } */) { export default defineRouter(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER const createHistory = process.env.SERVER
? createMemoryHistory ? createMemoryHistory
: process.env.VUE_ROUTER_MODE === 'history' : process.env.VUE_ROUTER_MODE === 'history'

10
src/shims-vue.d.ts vendored
View file

@ -1,10 +0,0 @@
/* eslint-disable */
/// <reference types="vite/client" />
// Mocks all files ending in `.vue` showing them as plain Vue instances
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}

View file

@ -1,6 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { api } from 'src/boot/axios'; import { api } from 'src/boot/axios';
import { ref } from 'vue'; import { ref } from 'vue';
import type { AppConfig } from './types';
export const useConfigStore = defineStore('config-store', () => { export const useConfigStore = defineStore('config-store', () => {
const data = ref<AppConfig>(); const data = ref<AppConfig>();

View file

@ -1,3 +1,3 @@
type AppConfig = { export type AppConfig = {
vat: number; vat: number;
}; };

View file

@ -3,17 +3,17 @@ import {
CreditNoteStatus as Status, CreditNoteStatus as Status,
CreditNotePayload as Payload, CreditNotePayload as Payload,
CreditNotePaybackStatus, CreditNotePaybackStatus,
} from './types.ts'; } from './types';
import { ref } from 'vue'; import { ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { api } from 'src/boot/axios.ts'; import { api } from 'src/boot/axios';
import { PaginationResult } from 'src/types.ts'; import { PaginationResult } from 'src/types';
import { manageAttachment, manageFile } from '../utils/index.ts'; import { manageAttachment, manageFile } from '../utils';
const ENDPOINT = 'credit-note'; const ENDPOINT = 'credit-note';
export * from './types.ts'; export * from './types';
export async function getCreditNoteStats() { export async function getCreditNoteStats() {
const res = await api.get<Record<Status, number>>(`/${ENDPOINT}/stats`); const res = await api.get<Record<Status, number>>(`/${ENDPOINT}/stats`);

View file

@ -500,6 +500,6 @@ const useCustomerStore = defineStore('api-customer', () => {
}; };
}); });
export * from './types.ts'; export * from './types';
export default useCustomerStore; export default useCustomerStore;

View file

@ -2,17 +2,17 @@ import {
DebitNote as Data, DebitNote as Data,
DebitNoteStatus as Status, DebitNoteStatus as Status,
DebitNotePayload as Payload, DebitNotePayload as Payload,
} from './types.ts'; } from './types';
import { ref } from 'vue'; import { ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { api } from 'src/boot/axios.ts'; import { api } from 'src/boot/axios';
import { PaginationResult } from 'src/types.ts'; import { PaginationResult } from 'src/types';
import { manageAttachment, manageFile } from '../utils/index.ts'; import { manageAttachment, manageFile } from '../utils';
const ENDPOINT = 'debit-note'; const ENDPOINT = 'debit-note';
export * from './types.ts'; export * from './types';
export async function getDebitNoteStats() { export async function getDebitNoteStats() {
const res = await api.get<Record<Status, number>>(`/${ENDPOINT}/stats`); const res = await api.get<Record<Status, number>>(`/${ENDPOINT}/stats`);

View file

@ -52,8 +52,4 @@ export type Payment = {
}; };
}; };
export enum PaymentDataStatus {
Success = 'PaymentSuccess',
Wait = 'PaymentWait',
}
export type Receipt = Payment; export type Receipt = Payment;

View file

@ -237,4 +237,4 @@ export const useQuotationPayment = defineStore('quotation-payment', () => {
}; };
}); });
export * from './types.ts'; export * from './types';

View file

@ -353,4 +353,4 @@ export const useRequestList = defineStore('request-list', () => {
}; };
}); });
export * from './types.ts'; export * from './types';

View file

@ -27,6 +27,7 @@ export const useTaskOrderStore = defineStore('taskorder-store', () => {
[TaskOrderStatus.Complete]: 0, [TaskOrderStatus.Complete]: 0,
[TaskOrderStatus.Accept]: 0, [TaskOrderStatus.Accept]: 0,
[TaskOrderStatus.Submit]: 0, [TaskOrderStatus.Submit]: 0,
[TaskOrderStatus.Restart]: 0,
}); });
const fileManager = manageAttachment(api, 'task-order'); const fileManager = manageAttachment(api, 'task-order');

View file

@ -15,12 +15,11 @@ import {
import axios from 'axios'; import axios from 'axios';
import useBranchStore from '../branch'; import useBranchStore from '../branch';
import { Branch } from '../branch/types'; import { Branch } from '../branch/types';
import useFlowStore from '../flow'; import { getSignature, setSignature } from './signature';
const branchStore = useBranchStore(); const branchStore = useBranchStore();
const useUserStore = defineStore('api-user', () => { const useUserStore = defineStore('api-user', () => {
const flowStore = useFlowStore();
const userOption = ref<UserOption>({ const userOption = ref<UserOption>({
hqOpts: [], hqOpts: [],
brOpts: [], brOpts: [],
@ -95,37 +94,15 @@ const useUserStore = defineStore('api-user', () => {
}); });
} }
async function fetchAttachment( async function fetchAttachment(userId: string) {
userId: string, const res = await api.get<UserAttachment[]>(`/user/${userId}/attachment`);
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get<UserAttachment[]>(`/user/${userId}/attachment`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (res && res.status === 200) { if (res && res.status === 200) {
return res.data; return res.data;
} }
return false; return false;
} }
async function addAttachment( async function addAttachment(userId: string, payload: UserAttachmentCreate) {
userId: string,
payload: UserAttachmentCreate,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const list: { name: string; file: File }[] = []; const list: { name: string; file: File }[] = [];
payload.file.forEach((v) => { payload.file.forEach((v) => {
@ -156,13 +133,6 @@ const useUserStore = defineStore('api-user', () => {
const res = await api.post<(UserAttachment & { uploadUrl: string })[]>( const res = await api.post<(UserAttachment & { uploadUrl: string })[]>(
`/user/${userId}/attachment`, `/user/${userId}/attachment`,
{ file: list.map((v) => v.name) }, { file: list.map((v) => v.name) },
{
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
); );
await Promise.all( await Promise.all(
@ -182,19 +152,9 @@ const useUserStore = defineStore('api-user', () => {
async function deleteAttachment( async function deleteAttachment(
userId: string, userId: string,
payload: UserAttachmentDelete, payload: UserAttachmentDelete,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
await api.delete(`/user/${userId}/attachment`, { await api.delete(`/user/${userId}/attachment`, {
data: payload, data: payload,
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
} }
@ -217,21 +177,8 @@ const useUserStore = defineStore('api-user', () => {
return false; return false;
} }
async function fetchById( async function fetchById(id: string) {
id: string, const res = await api.get<User>(`/user/${id}`);
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get<User>(`/user/${id}`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false; if (!res) return false;
if (res.status === 200) return res.data; if (res.status === 200) return res.data;
@ -240,21 +187,8 @@ const useUserStore = defineStore('api-user', () => {
return false; return false;
} }
async function fetchImageListById( async function fetchImageListById(id: string) {
id: string, const res = await api.get(`/user/${id}/profile-image`);
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get(`/user/${id}/profile-image`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false; if (!res) return false;
if (res.status === 200) return res.data; if (res.status === 200) return res.data;
@ -274,22 +208,8 @@ const useUserStore = defineStore('api-user', () => {
return name; return name;
} }
async function deleteImageByName( async function deleteImageByName(id: string, name: string) {
id: string, const res = await api.delete(`/user/${id}/profile-image/${name}`);
name: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.delete(`/user/${id}/profile-image/${name}`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false; if (!res) return false;
} }
@ -300,27 +220,12 @@ const useUserStore = defineStore('api-user', () => {
selectedImage: string; selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[]; list: { url: string; imgFile: File | null; name: string }[];
}, },
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { zipCode, ...payload } = data; const { zipCode, ...payload } = data;
const res = await api.post<User>( const res = await api.post<User>('/user', {
'/user', ...payload,
{ selectedImage: imgList.selectedImage || '',
...payload, });
selectedImage: imgList.selectedImage || '',
},
{
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
);
if (imgList.list.length > 0 && res.data.id) { if (imgList.list.length > 0 && res.data.id) {
for (let index = 0; index < imgList.list.length; index++) { for (let index = 0; index < imgList.list.length; index++) {
@ -338,42 +243,18 @@ const useUserStore = defineStore('api-user', () => {
async function editById( async function editById(
id: string, id: string,
data: Partial<UserCreate & { status?: 'ACTIVE' | 'INACTIVE' }>, data: Partial<UserCreate & { status?: 'ACTIVE' | 'INACTIVE' }>,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { zipCode, ...paylond } = data; const { zipCode, ...payload } = data;
const res = await api.put<User>(`/user/${id}`, paylond, { const res = await api.put<User>(`/user/${id}`, payload);
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false; if (!res) return false;
return res.data; return res.data;
} }
async function deleteById( async function deleteById(id: string) {
id: string, const res = await api.delete<User>(`/user/${id}`);
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.delete<User>(`/user/${id}`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false; if (!res) return false;
if (res.status === 200) return res.data; if (res.status === 200) return res.data;
@ -381,21 +262,8 @@ const useUserStore = defineStore('api-user', () => {
return false; return false;
} }
async function getBranch( async function getBranch(userId: string) {
userId: string, const res = await api.get<Pagination<Branch[]>>(`/user/${userId}/branch`);
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get<Pagination<Branch[]>>(`/user/${userId}/branch`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false; if (!res) return false;
if (res.status === 200) return res.data; if (res.status === 200) return res.data;
@ -403,26 +271,10 @@ const useUserStore = defineStore('api-user', () => {
return false; return false;
} }
async function addBranch( async function addBranch(userId: string, branchId: string | string[]) {
userId: string, const res = await api.post(`/user/${userId}/branch`, {
branchId: string | string[], branch: ([] as string[]).concat(branchId),
flow?: { });
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.post(
`/user/${userId}/branch`,
{ branch: ([] as string[]).concat(branchId) },
{
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
);
if (!res) return false; if (!res) return false;
if (res.status >= 400) return false; if (res.status >= 400) return false;
@ -430,21 +282,8 @@ const useUserStore = defineStore('api-user', () => {
return res.data || true; return res.data || true;
} }
async function removeBranch( async function removeBranch(userId: string, branchId: string | string[]) {
userId: string,
branchId: string | string[],
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.delete(`/user/${userId}/branch`, { const res = await api.delete(`/user/${userId}/branch`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
data: { branch: ([] as string[]).concat(branchId) }, data: { branch: ([] as string[]).concat(branchId) },
}); });
@ -454,18 +293,8 @@ const useUserStore = defineStore('api-user', () => {
return res.data || true; return res.data || true;
} }
async function typeStats(flow?: { async function typeStats() {
sessionId?: string; const res = await api.get('/user/type-stats');
refTransactionId?: string;
transactionId?: string;
}) {
const res = await api.get('/user/type-stats', {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false; if (!res) return false;
if (res.status === 200) return res.data; if (res.status === 200) return res.data;
@ -499,6 +328,9 @@ const useUserStore = defineStore('api-user', () => {
addAttachment, addAttachment,
deleteAttachment, deleteAttachment,
getSignature,
setSignature,
typeStats, typeStats,
}; };
}); });

View file

@ -0,0 +1,50 @@
import axios from 'axios';
import { api } from 'src/boot/axios';
import { getUserId } from 'src/services/keycloak';
export async function getSignature() {
const userId = getUserId();
if (!userId) return;
const responseSignature = await api.get<string>(
'/user/' + userId + '/signature',
);
if (!responseSignature.data) return '';
const responseBlob = await axios.get<Blob>(responseSignature.data, {
responseType: 'blob',
});
if (responseBlob.status < 400) {
return await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('error', reject);
reader.addEventListener('load', () => resolve(reader.result as string));
reader.readAsDataURL(responseBlob.data);
});
} else {
return '';
}
}
export async function setSignature(image: string) {
const userId = getUserId();
if (!userId) return;
if (image === '') {
return await deleteSignature();
} else {
return await api.put('/user/' + userId + '/signature', { data: image });
}
}
export async function deleteSignature() {
const userId = getUserId();
if (!userId) return;
return await api.delete('/user/' + userId + '/signature');
}

View file

@ -42,7 +42,7 @@ export function setTheme(theme: Theme): Theme {
export enum Lang { export enum Lang {
English = 'eng', English = 'eng',
Thai = 'tha', Thai = 'tha', // spellchecker:disable-line
} }
/** /**

View file

@ -1,7 +1,4 @@
{ {
"extends": "@quasar/app-vite/tsconfig-preset", "extends": "./.quasar/tsconfig.json",
"compilerOptions": { "exclude": ["./tests"]
"baseUrl": "."
},
"exclude": ["tests"]
} }