From ca3c00b6a1c7ac116630cae7befc0c87e4264b42 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 16 Dec 2024 17:12:39 +0700 Subject: [PATCH] start project --- .env.example | 7 ++ .gitignore | 30 +++++ README.md | 30 +++++ cypress.config.ts | 8 ++ docker/Dockerfile | 19 +++ docker/entrypoint.sh | 18 +++ docker/nginx.conf | 30 +++++ env.d.ts | 1 + index.html | 34 ++++++ package.json | 53 ++++++++ public/favicon.ico | Bin 0 -> 1691 bytes public/robots.txt | 2 + src/App.vue | 26 ++++ src/api/api.org.ts | 7 ++ src/api/index.ts | 29 +++++ src/app.config.ts | 13 ++ src/assets/key/BMA | 51 ++++++++ src/assets/key/BMA.pub | 1 + src/assets/key/BMA.pub.pem | 14 +++ src/assets/line.png | Bin 0 -> 6892 bytes src/assets/sso.png | Bin 0 -> 20752 bytes src/interface/index/Main.ts | 57 +++++++++ src/interface/response/Main.ts | 13 ++ src/main.ts | 38 ++++++ src/plugins/axios.ts | 24 ++++ src/plugins/filters.ts | 20 +++ src/plugins/http.ts | 39 ++++++ src/plugins/keycloak.ts | 70 +++++++++++ src/quasar-user-options.ts | 11 ++ src/registerServiceWorker.ts | 34 ++++++ src/router/index.ts | 29 +++++ src/shims-vue.d.ts | 1 + src/stores/main.ts | 13 ++ src/stores/mixin.ts | 91 ++++++++++++++ src/style/quasar-variables.sass | 139 +++++++++++++++++++++ src/views/ErrorNotFoundPage.vue | 27 ++++ src/views/login.vue | 210 ++++++++++++++++++++++++++++++++ tsconfig.app.json | 16 +++ tsconfig.config.json | 16 +++ tsconfig.json | 14 +++ tsconfig.vitest.json | 9 ++ vite.config.js | 66 ++++++++++ 42 files changed, 1310 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cypress.config.ts create mode 100644 docker/Dockerfile create mode 100644 docker/entrypoint.sh create mode 100644 docker/nginx.conf create mode 100644 env.d.ts create mode 100644 index.html create mode 100644 package.json create mode 100644 public/favicon.ico create mode 100644 public/robots.txt create mode 100644 src/App.vue create mode 100644 src/api/api.org.ts create mode 100644 src/api/index.ts create mode 100644 src/app.config.ts create mode 100644 src/assets/key/BMA create mode 100644 src/assets/key/BMA.pub create mode 100644 src/assets/key/BMA.pub.pem create mode 100644 src/assets/line.png create mode 100644 src/assets/sso.png create mode 100644 src/interface/index/Main.ts create mode 100644 src/interface/response/Main.ts create mode 100644 src/main.ts create mode 100644 src/plugins/axios.ts create mode 100644 src/plugins/filters.ts create mode 100644 src/plugins/http.ts create mode 100644 src/plugins/keycloak.ts create mode 100644 src/quasar-user-options.ts create mode 100644 src/registerServiceWorker.ts create mode 100644 src/router/index.ts create mode 100644 src/shims-vue.d.ts create mode 100644 src/stores/main.ts create mode 100644 src/stores/mixin.ts create mode 100644 src/style/quasar-variables.sass create mode 100644 src/views/ErrorNotFoundPage.vue create mode 100644 src/views/login.vue create mode 100644 tsconfig.app.json create mode 100644 tsconfig.config.json create mode 100644 tsconfig.json create mode 100644 tsconfig.vitest.json create mode 100644 vite.config.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..871e4af --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +KC_URL="https://keycloak-server.com" +KC_REALMS="your-realm" + +VITE_API_URI_CONFIG="https://api-server/api/v1" +VITE_CLIENTID_KEYCLOAK="your-client-id" + +SSO_COOKIE_NAME="sso" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1f03e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..05b99c6 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# HRMS SSO + +## Project setup + +``` +npm install +``` + +### Compiles and hot-reloads for development + +``` +npm run dev +``` + +### Compiles and minifies for production + +``` +npm run build +npm run preview +``` + +### Format and fixes files + +``` +npm run format +``` + +### Customize configuration + +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000..4a22885 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + e2e: { + specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', + baseUrl: 'http://localhost:4173', + }, +}) diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..cfa0071 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,19 @@ +# docker buildx build --platform=linux/amd64 -f docker/Dockerfile . -t hrms-git.chin.in.th/bma-hrms/hrms-checkin:0.1 + +# Build Stage +FROM node:20-alpine as build-stage +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY ./ . +RUN npm run build + +# Production Stage +FROM nginx:stable-alpine AS production-stage +RUN mkdir /app +COPY --from=build-stage /app/dist /app +COPY docker/nginx.conf /etc/nginx/nginx.conf +COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod u+x /usr/local/bin/entrypoint.sh +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..ea4c9ca --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +ROOT_DIR=/app + +# Replace env vars in JavaScript files +echo "Replacing env constants in JS" +for file in $ROOT_DIR/assets/app.*.js* $ROOT_DIR/js/app.*.js* $ROOT_DIR/index.html $ROOT_DIR/precache-manifest*.js $ROOT_DIR/assets/index*.js*; +do + echo "Processing $file ..."; + + sed -i 's|VITE_API_URI_CONFIG|'${VITE_API_URI_CONFIG}'|g' $file + sed -i 's|VITE_URL_SSO|'${VITE_URL_SSO}'|g' $file + +done + +echo "Starting Nginx" +nginx -g 'daemon off;' + diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..6f61d6c --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,30 @@ +user nginx; +worker_processes 1; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; +events { + worker_connections 1024; +} +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + sendfile on; + keepalive_timeout 65; + server { + listen 80; + server_name localhost; + location / { + root /app; + index index.html; + try_files $uri $uri/ /index.html; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..12d62fd --- /dev/null +++ b/index.html @@ -0,0 +1,34 @@ + + + + + + + HRMS SSO + + + +
+ + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..e58ff7e --- /dev/null +++ b/package.json @@ -0,0 +1,53 @@ +{ + "name": "hrms-sso", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "run-p build-only", + "preview": "vite preview --port 3002", + "build-only": "vite build", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier ./src --write" + }, + "dependencies": { + "@arcgis/core": "^4.28.10", + "@quasar/extras": "^1.15.8", + "@vuepic/vue-datepicker": "^5.2.1", + "keycloak-js": "^22.0.2", + "moment": "^2.29.4", + "pinia": "^2.1.4", + "quasar": "^2.17.5", + "register-service-worker": "^1.7.2", + "simple-vue-camera": "^1.1.3", + "vite-plugin-pwa": "^0.16.7", + "vue": "^3.4.15", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@quasar/vite-plugin": "^1.3.0", + "@rushstack/eslint-patch": "^1.1.4", + "@types/jsdom": "^20.0.1", + "@types/node": "^18.18.10", + "@types/vue-router": "^2.0.0", + "@vitejs/plugin-vue": "^4.0.0", + "@vitejs/plugin-vue-jsx": "^3.0.0", + "@vue/eslint-config-prettier": "^7.0.0", + "@vue/eslint-config-typescript": "^11.0.0", + "@vue/test-utils": "^2.2.6", + "@vue/tsconfig": "^0.1.3", + "cypress": "^12.0.2", + "eslint": "^8.22.0", + "eslint-plugin-cypress": "^2.12.1", + "eslint-plugin-vue": "^9.3.0", + "jsdom": "^20.0.3", + "npm-run-all": "^4.1.5", + "prettier": "^2.7.1", + "sass": "^1.32.12", + "start-server-and-test": "^1.15.2", + "typescript": "~4.7.4", + "vite": "^4.0.0", + "vitest": "^0.25.6", + "vue-tsc": "^1.0.12" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4d31d3ed4b56b623500495600ce3fe7be41e3e1e GIT binary patch literal 1691 zcmV;M24wk(P)^5JxN4CRCt`tSNU&LRTTc-%ygaZw6%1h zwCrGs76JyFV5}n1MhzGwLbO6fSv3A25{>^r6NMinD50n!CR8EB&>D?}fU?sD3YDdF zgHG*2SGrF-?>)cs=1u7^Gb}=acXIRQ-FMzO-}%nD=bmT7V;=u6088r5S^t=lt-f;O zVj6F^aOQEQa$5hRewDL{vy`*&*YvrLCH1tCnXUQ&c+oVzw4rjPs%b?EV8_D`D-@*j zx)D0^sk`9!jHik{j(Pzo;{rjf)HH3cxio4%R~{0C4mT5aYT8TVpz&hQ!!ZE4HRWkE zUS!_N3qgT2DyCMsS{23kuk)wr0mW^mP6=gEZcW9B!_($Fya1@0&Y0^$y9;;ZhR2}A zYYQlIMj=4!vh572BPX7j(kAK$nycf#7iCLu-6a|yVOxk4hh3& z$x|^UG#oEimm}I@!-}|xV{#|^yRogM0kzHnL|JVFq20mbm~M|PF92SiD`Qs$gSv{v zk+ImCKF5f8X?GjKxPED%7jM^|hn>cjMknC4xFjr^7K=>{RXEnyWq@#zi2&j72EYV_ zzo8gE9Umm7GY1sBQ*#dG{XG!rHzy<`k49HstH6<-cI@kD#r5zYYW2V!9F7gZjL&Qyolpk7=t9?IMdgOd;;7*a{<0c zosLxmA*rvM^S%Kn<{aeOjPMA2k}?%Vt=Dmdlt+_RCo^Bpn5$}gf<#%WO0#^z{G2%XTm^)H3-WS zQt@hhGQPTT6}c0Vu*a?m5HA z!kL~9RB_*bIB7a|Hr61MgV~F*`oal3fA$c9N%IjV;T0z5tcYkd(esiv2SZ@P z`b%dK%24!k7e)A#(I21kfa<`)vU(T9` zJx%qfW=JBLE59)$cAE{cOh&`|yL1%yEX7Vvsp&t`Kuv!yPed2irO!Za{6w_S>mQox zP{-=aJjId>J%z( zb+Zr+;H6V#WMc&F!$X)I8H4Rv3$bF)_XQC5zDyUgdL0dk`x;;ysq*%{_1QqSczhJcJBgXcyu@UVL+AEd8291 z=X*f{WM^i(sI0W55hVm8%t*nKR!zdJOSa4TPp}a*EIYzw7$5UTS~BT1OZ_mQ7qYR; zhr4I<8xFTBg{K?4ZL7_6X*{)_H8&e0n#=4azFcj<5r}3;6P0 z<#q#Ug`fOY@j%1VmYcSef}v&aI(z`oxOHgR+T!5-yX3YAkLJU~uxQ#5&kLN(*A|Bz z_>Q*Lcz>ACxEe|j!vtabdFKf(ZP6d{(@gPvnkh;tbED9;;(bS`;piIQT_A(TjRztK lciX%Pk9c@&_yzDd#=p9(1H?pbBT4`O002ovPDHLkV1gU887%++ literal 0 HcmV?d00001 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..ea76d33 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/api/api.org.ts b/src/api/api.org.ts new file mode 100644 index 0000000..086e557 --- /dev/null +++ b/src/api/api.org.ts @@ -0,0 +1,7 @@ +import env from "./index"; +const org = `${env.API_URI}/org`; +// const log = `${env.API_URI}/log`; + +export default { + org, +}; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..8069fca --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,29 @@ +/**config api */ +import { ref } from 'vue' + +const env = ref(process.env.NODE_ENV || 'development') +export const apiUrlConfig = import.meta.env.VITE_API_URI_CONFIG +// if (process.env.VUE_APP_TEST) { +// env = "test"; +// } + +const config = ref({ + development: { + // API_URI: "https://localhost:7260/api", + API_URI: 'https://bma-ehr.frappet.synology.me/api/v1', + }, + test: { + API_URI: 'http://localhost:5010/api/v1', + }, + production: { + API_URI: apiUrlConfig, + }, +}) + +const API_URI = ref(config.value[env.value].API_URI) + +export default { + env: env.value, + config: config.value, + API_URI: API_URI.value, +} diff --git a/src/app.config.ts b/src/app.config.ts new file mode 100644 index 0000000..42aca54 --- /dev/null +++ b/src/app.config.ts @@ -0,0 +1,13 @@ +/**ใช้รวมไฟล์ย่อยๆ ของ api แต่ละไฟล์ */ + +/** API ระบบลงเวลา */ +import org from "@/api/api.org"; + +const API = { + /**message */ + ...org, +}; + +export default { + API: API, +}; diff --git a/src/assets/key/BMA b/src/assets/key/BMA new file mode 100644 index 0000000..5088b1a --- /dev/null +++ b/src/assets/key/BMA @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/src/assets/key/BMA.pub b/src/assets/key/BMA.pub new file mode 100644 index 0000000..3260601 --- /dev/null +++ b/src/assets/key/BMA.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQClVi9NES4zetZ5pvbr40/VWDu3xjv2298+EySGqFG/YTh9a6pyzIaxrNHPuZvQH2p79B2xE2HmuAqTLFQhRhUiFKBaXa4TONXNcF6++by7sCSDWr5aTtI8gR/gaLHEwh81ZDB5ukVeGd2DtZxBHPzoBBQvKQuEkUP1qJsNRBBNYuSRq6dXEvyv2IK4RaPomWEdflqjV+6BA+A6GtOq3dc9sRSrLFpMaVZMY38fZPEOM8fBJRZdHfv7ktW1H6klEqKt8kOtjXCvjRKlNUn6HX3X+V6NYu/cJdZLFj2hWdgE/c/mP7gqrRnsS3WC8HL7eY6wFOu8PxPQicF/R44sPz3pDizv2mRT3Tl7G3bgVd8OlLxk3SuLY5WguZz7LzvP9N+lOBl29sHOt2wZeeZit+t7j/8N+KC1i7J2gplGn0CobekjSy8Y1VzCWZN+EKXstxLpe7ajxamw2BC4nc+CbsAD4U6sgJ7qiu4xfKjgNo2YYrG3ySVN+qMYDzt0P5P+Z5XeFDfwohcPvnQkRtsNXNKT7JEiDrqZGDx/qg4ePmqrXYi/kJ/ufclA0HjMSy8+kFZSrezquK7Fa2slADSh895sQS5W6D95LLYs4PkeFXNyq0L2FuJ2DyAF4Cc+ojSguksFQmdE46Fu0Qut2EgsHESV7CHaXi3bG8Yb2GkC+M+nTQ== waruneeta@AUYs-MacBook-Pro.local diff --git a/src/assets/key/BMA.pub.pem b/src/assets/key/BMA.pub.pem new file mode 100644 index 0000000..36cff09 --- /dev/null +++ b/src/assets/key/BMA.pub.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApVYvTREuM3rWeab26+NP +1Vg7t8Y79tvfPhMkhqhRv2E4fWuqcsyGsazRz7mb0B9qe/QdsRNh5rgKkyxUIUYV +IhSgWl2uEzjVzXBevvm8u7Akg1q+Wk7SPIEf4GixxMIfNWQwebpFXhndg7WcQRz8 +6AQULykLhJFD9aibDUQQTWLkkaunVxL8r9iCuEWj6JlhHX5ao1fugQPgOhrTqt3X +PbEUqyxaTGlWTGN/H2TxDjPHwSUWXR37+5LVtR+pJRKirfJDrY1wr40SpTVJ+h19 +1/lejWLv3CXWSxY9oVnYBP3P5j+4Kq0Z7Et1gvBy+3mOsBTrvD8T0InBf0eOLD89 +6Q4s79pkU905ext24FXfDpS8ZN0ri2OVoLmc+y87z/TfpTgZdvbBzrdsGXnmYrfr +e4//DfigtYuydoKZRp9AqG3pI0svGNVcwlmTfhCl7LcS6Xu2o8WpsNgQuJ3Pgm7A +A+FOrICe6oruMXyo4DaNmGKxt8klTfqjGA87dD+T/meV3hQ38KIXD750JEbbDVzS +k+yRIg66mRg8f6oOHj5qq12Iv5Cf7n3JQNB4zEsvPpBWUq3s6riuxWtrJQA0ofPe +bEEuVug/eSy2LOD5HhVzcqtC9hbidg8gBeAnPqI0oLpLBUJnROOhbtELrdhILBxE +lewh2l4t2xvGG9hpAvjPp00CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/src/assets/line.png b/src/assets/line.png new file mode 100644 index 0000000000000000000000000000000000000000..e8950bf39436e1bf99ce621c05696ce4e575a2a3 GIT binary patch literal 6892 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Π&E-yh0=6&y1G=C?~irDisl!h1tvAX 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 literal 0 HcmV?d00001 diff --git a/src/interface/index/Main.ts b/src/interface/index/Main.ts new file mode 100644 index 0000000..14def36 --- /dev/null +++ b/src/interface/index/Main.ts @@ -0,0 +1,57 @@ +interface DataOption { + id: string + name: string +} + +interface DataDateMonthObject { + month: number + year: number +} +interface FormRef { + date: object | null + reason: object | null + [key: string]: any +} +interface notiType { + id: string + sender: string + body: string + timereceive: Date + isOpen: boolean +} + +interface LocationObject { + latitude: number + longitude: number +} + +interface Pagination { + sortBy: string | null + descending: boolean + page: number + rowsPerPage: number | undefined +} +interface DataCheckIn { + checkInDate: string + checkInDateTime: string + checkInId: string + checkInLocation: string + checkInStatus: string + checkInTime: string + checkOutLocation: string + checkOutStatus: string + checkOutTime: string + editReason: string + editStatus: string + isEdit: boolean +} + +export type { + DataOption, + FormRef, + notiType, + DataDateMonthObject, + LocationObject, + Pagination, + DataCheckIn, +} diff --git a/src/interface/response/Main.ts b/src/interface/response/Main.ts new file mode 100644 index 0000000..574f78b --- /dev/null +++ b/src/interface/response/Main.ts @@ -0,0 +1,13 @@ +interface Noti { + id: string + body: string + receiverUserId: string + type: string + payload: null + isOpen: false + receiveDate: Date + openDate: null + createdFullName: string +} + +export type { Noti } diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..fb28e50 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,38 @@ +import { createApp, defineAsyncComponent } from 'vue' +import App from '@/App.vue' +import '@/registerServiceWorker' +import router from '@/router' +import { createPinia } from 'pinia' +import { Quasar, Dialog, Notify, Loading } from 'quasar' +import '@vuepic/vue-datepicker/dist/main.css' +import quasarUserOptions from '@/quasar-user-options' + +import 'quasar/src/css/index.sass' +import th from 'quasar/lang/th' + +import http from '@/plugins/http' + +const app = createApp(App) +const pinia = createPinia() + +app.use(router) +app.use(pinia) + +app.use(Quasar, { + ...quasarUserOptions, + plugins: { + Notify, + Dialog, + Loading, + }, + lang: th, +}) + +app.component( + 'datepicker', + defineAsyncComponent(() => import('@vuepic/vue-datepicker')) +) + +app.config.globalProperties.$http = http + +app.mount('#app') diff --git a/src/plugins/axios.ts b/src/plugins/axios.ts new file mode 100644 index 0000000..36f4b2f --- /dev/null +++ b/src/plugins/axios.ts @@ -0,0 +1,24 @@ +import axios from 'axios' +// import { dotnetPath } from "../path/axiosPath"; +// import { getToken } from "@baloise/vue-keycloak"; +import { getToken } from './auth' + +const axiosInstance = axios.create({ + withCredentials: false, +}) + +// axiosInstance.defaults.baseURL = dotnetPath; +axiosInstance.interceptors.request.use( + async (config) => { + const token = await getToken() + config.headers = { + Authorization: `Bearer ${token}`, + } + return config + }, + (error) => { + Promise.reject(error) + } +) + +export default axiosInstance diff --git a/src/plugins/filters.ts b/src/plugins/filters.ts new file mode 100644 index 0000000..04d9354 --- /dev/null +++ b/src/plugins/filters.ts @@ -0,0 +1,20 @@ +/** + * GLOABL Filters + * - ไฟล์นี้จะไว้เก็บฟังก์ชันง่าย ๆ พวก Helper Functions ทั้งหลาย + */ + +const filters = { + /** + * ฟังก์ชัน compactNumber ใช้แปลงตัวเลขยาว ๆ ให้กลายเป็นเลขสั้น ๆ แบบที่พวก Social Media ชอบใช้กัน เช่น 1,000 แปลงเป็น 1K หรือ 1,000,000 แปลงเป็น 1M + * วิธีใช้ : {{ $filters.compactNumber(value) }} + * + * @param val รับค่าพารามิเตอร์เป็นตัวแปรชนิดตัวเลข + * @returns คืนค่าเป็นตัวเลขที่แปลงค่าแล้ว + */ + compactNumber(val: number) { + const formatter = Intl.NumberFormat('en', { notation: 'compact' }) + return formatter.format(val) + }, +} + +export default filters diff --git a/src/plugins/http.ts b/src/plugins/http.ts new file mode 100644 index 0000000..1120346 --- /dev/null +++ b/src/plugins/http.ts @@ -0,0 +1,39 @@ +import Axios, { type AxiosRequestConfig, type AxiosResponse } from "axios"; + +const http = Axios.create({ + timeout: 1000000000, // เพิ่มค่า timeout + headers: { + "X-Requested-With": "XMLHttpRequest", + }, +}); + +http.interceptors.request.use( + async function (config: AxiosRequestConfig) { + config.headers = config.headers ?? {}; + + return config; + }, + function (error: any) { + return Promise.reject(error); + } +); + +http.interceptors.response.use( + function (response: AxiosResponse) { + return response; + }, + function (error: any) { + if (typeof error !== undefined) { + // eslint-disable-next-line no-prototype-builtins + if (error.hasOwnProperty("response")) { + if (error.response.status === 401 || error.response.status === 403) { + // Store.commit("SET_ERROR_MESSAGE", error.response.data.message); + // Store.commit("REMOVE_ACCESS_TOKEN") + } + } + } + return Promise.reject(error); + } +); + +export default http; diff --git a/src/plugins/keycloak.ts b/src/plugins/keycloak.ts new file mode 100644 index 0000000..d882c3e --- /dev/null +++ b/src/plugins/keycloak.ts @@ -0,0 +1,70 @@ +// authen with keycloak client +import Keycloak from "keycloak-js"; + +// const ACCESS_TOKEN = 'BMAHRIS_KEYCLOAK_IDENTITY' +// const REFRESH_TOKEN = 'BMAHRIS_KEYCLOAK_REFRESH' +// const keycloakConfig = { +// url: import.meta.env.VITE_URL_KEYCLOAK, +// realm: import.meta.env.VITE_REALM_KEYCLOAK, +// clientId: import.meta.env.VITE_CLIENTID_KEYCLOAK, +// clientSecret: import.meta.env.VITE_CLIENTSECRET_KEYCLOAK, +// } + +// const keycloak = new Keycloak(keycloakConfig) + +// async function kcAuthen(access_token: string, refresh_token: string) { +// await setCookie(ACCESS_TOKEN, access_token, 1) +// await setCookie(REFRESH_TOKEN, refresh_token, 1) +// window.location.href = '/' +// } + +// async function kcLogout() { +// await deleteCookie(ACCESS_TOKEN) +// await deleteCookie(REFRESH_TOKEN) +// if (keycloak.authenticated !== undefined) { +// keycloak.logout() +// } +// window.location.href = '/login' +// } + +// async function getToken() { +// return { +// token: getCookie(ACCESS_TOKEN), +// refresh_token: getCookie(REFRESH_TOKEN), +// } +// } + +// function setCookie(name: string, value: any, days: number) { +// let expires = '' +// if (days) { +// const date = new Date() +// date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000) +// expires = '; expires=' + date.toUTCString() +// } +// document.cookie = name + '=' + (value || '') + expires + '; path=/' +// } + +// function getCookie(name: string) { +// const nameEQ = name + '=' +// const ca = document.cookie.split(';') +// for (let i = 0; i < ca.length; i++) { +// let c = ca[i] +// while (c.charAt(0) == ' ') c = c.substring(1, c.length) +// if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length) +// } +// return null +// } + +// function deleteCookie(name: string) { +// document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;` +// } + +// export default keycloak +// export { +// keycloakConfig, +// getToken, +// kcAuthen, +// kcLogout, +// ACCESS_TOKEN, +// REFRESH_TOKEN, +// } diff --git a/src/quasar-user-options.ts b/src/quasar-user-options.ts new file mode 100644 index 0000000..56dff8b --- /dev/null +++ b/src/quasar-user-options.ts @@ -0,0 +1,11 @@ +// import "./styles/quasar.scss" +import '@quasar/extras/material-icons/material-icons.css' +import '@quasar/extras/material-icons-outlined/material-icons-outlined.css' +import '@quasar/extras/fontawesome-v5/fontawesome-v5.css' +import '@quasar/extras/mdi-v4/mdi-v4.css' + +// To be used on app.use(Quasar, { ... }) +export default { + config: {}, + plugins: {}, +} diff --git a/src/registerServiceWorker.ts b/src/registerServiceWorker.ts new file mode 100644 index 0000000..adbd55d --- /dev/null +++ b/src/registerServiceWorker.ts @@ -0,0 +1,34 @@ +/* eslint-disable no-console */ + +import { register } from 'register-service-worker' + +if (process.env.NODE_ENV === 'production') { + register('registerSW.js', { + ready() { + console.log( + 'App is being served from cache by a service worker.\n' + + 'For more details, visit https://goo.gl/AFskqB' + ) + }, + registered() { + console.log('Service worker has been registered.') + }, + cached() { + console.log('Content has been cached for offline use.') + }, + updatefound() { + console.log('New content is downloading.') + }, + updated() { + console.log('New content is available; please refresh.') + }, + offline() { + console.log( + 'No internet connection found. App is running in offline mode.' + ) + }, + error(error) { + console.error('Error during service worker registration:', error) + }, + }) +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..ea578e6 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,29 @@ +import { createRouter, createWebHistory } from "vue-router"; +const loginView = () => import("@/views/login.vue"); + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: "/", + name: "loginMain", + component: loginView, + children: [ + /** + * 404 Not Found + * ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route + */ + { + path: "/:pathMatch(.*)*", + name: "NotFound", + component: () => import("@/views/ErrorNotFoundPage.vue"), + meta: { + Auth: true, + }, + }, + ], + }, + ], +}); + +export default router; diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts new file mode 100644 index 0000000..01b57df --- /dev/null +++ b/src/shims-vue.d.ts @@ -0,0 +1 @@ +declare module '*.vue' diff --git a/src/stores/main.ts b/src/stores/main.ts new file mode 100644 index 0000000..f4b0ae6 --- /dev/null +++ b/src/stores/main.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia' +import { useCounterMixin } from '@/stores/mixin' + +const mixin = useCounterMixin() + +/** store for checkin history*/ +export const useSsoHrms = defineStore('hrmssso', () => { + + + return { + + } +}) diff --git a/src/stores/mixin.ts b/src/stores/mixin.ts new file mode 100644 index 0000000..573e404 --- /dev/null +++ b/src/stores/mixin.ts @@ -0,0 +1,91 @@ +import { defineStore } from "pinia"; +import { Loading, QSpinnerCube } from "quasar"; + +export const useCounterMixin = defineStore("mixin", () => { + function date2Thai(srcDate: Date, isFullMonth = false, isTime = false) { + if (srcDate == null) { + return null; + ` + `; + } + const date = new Date(srcDate); + const isValidDate = Boolean(+date); + if (!isValidDate) return srcDate.toString(); + if (isValidDate && date.getFullYear() < 1000) return srcDate.toString(); + const fullMonthThai = [ + "มกราคม", + "กุมภาพันธ์", + "มีนาคม", + "เมษายน", + "พฤษภาคม", + "มิถุนายน", + "กรกฎาคม", + "สิงหาคม", + "กันยายน", + "ตุลาคม", + "พฤศจิกายน", + "ธันวาคม", + ]; + const abbrMonthThai = [ + "ม.ค.", + "ก.พ.", + "มี.ค.", + "เม.ย.", + "พ.ค.", + "มิ.ย.", + "ก.ค.", + "ส.ค.", + "ก.ย.", + "ต.ค.", + "พ.ย.", + "ธ.ค.", + ]; + let dstYear = 0; + if (date.getFullYear() > 2500) { + dstYear = date.getFullYear(); + } else { + dstYear = date.getFullYear() + 543; + } + let dstMonth = ""; + if (isFullMonth) { + dstMonth = fullMonthThai[date.getMonth()]; + } else { + dstMonth = abbrMonthThai[date.getMonth()]; + } + let dstTime = ""; + if (isTime) { + const H = date.getHours().toString().padStart(2, "0"); + const M = date.getMinutes().toString().padStart(2, "0"); + // const S = date.getSeconds().toString().padStart(2, "0") + // dstTime = " " + H + ":" + M + ":" + S + " น." + dstTime = " " + H + ":" + M + " น."; + } + return ( + date.getDate().toString().padStart(2, "0") + + " " + + dstMonth + + " " + + dstYear + + dstTime + ); + } + + const showLoader = () => { + Loading.show({ + spinner: QSpinnerCube, + spinnerSize: 140, + spinnerColor: "primary", + backgroundColor: "white", + }); + }; + + const hideLoader = () => { + Loading.hide(); + }; + + return { + date2Thai, + showLoader, + hideLoader, + }; +}); diff --git a/src/style/quasar-variables.sass b/src/style/quasar-variables.sass new file mode 100644 index 0000000..f2c3dc4 --- /dev/null +++ b/src/style/quasar-variables.sass @@ -0,0 +1,139 @@ +// FILE (create it): src/quasar-variables.sass + +$primary: #02A998 +$secondary: #016987 +$accent: #9C27B0 + +// $dark: #1D1D1D +$dark: #35473C + +$positive: #21BA45 +$negative: #C10015 +$info: #31CCEC +$warning: #F2C037 + +$add: #00aa86 +.text-add + color: $add !important +.bg-add + background: $add !important + +$edit: #019fc4 +.text-edit + color: $edit !important +.bg-edit + background: $edit !important + +$public: #016987 +.text-public + color: $public !important +.bg-public + background: $public !important + +$save: #4154b3 +.text-save + color: $save !important +.bg-save + background: $save !important + +$nativetab: #c8d3db +.text-nativetab + color: $nativetab !important +.bg-nativetab + background: $nativetab !important + +$activetab: #4a5568 +.text-activetab + color: $activetab !important +.bg-activetab + background: $activetab !important + +.inputgreen .q-field__prefix, +.inputgreen .q-field__suffix, +.inputgreen .q-field__input, +.inputgreen .q-field__native + + color: rgb(6, 136, 77) + +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@100;200;300;400;500;600;700;800;900&display=swap') + +$noto-thai: 'Noto Sans Thai', sans-serif + +#azay-app, +div + font-family: $noto-thai !important + text-rendering: optimizeLegibility + -webkit-font-smoothing: antialiased + -moz-osx-font-smoothing: grayscale + +$separator-color: #EDEDED !default + +.bg-teal-1 + background: #e0f2f1a6 !important + +.table_ellipsis + max-width: 200px + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + +.table_ellipsis:hover + word-wrap: break-word + overflow: visible + white-space: normal + +.table_ellipsis2 + max-width: 25vw + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + +.table_ellipsis2:hover + word-wrap: break-word + overflow: visible + white-space: normal + transition: width 2s + +$muti-tab: #87d4cc +.text-muti-tab + color: $muti-tab !important +.bg-muti-tab + background: $muti-tab !important + + +/* editor */ + +.q-editor + font-size: 1rem + line-height: 1.5rem + font-weight: 400 + +.q-editor h1, .q-menu h1 + font-size: 1.5rem + line-height: 2rem + font-weight: 400 + margin-block-start: 0em + margin-block-end: 0em + +.q-editor h2, .q-menu h2 + font-size: 1.25rem + line-height: 1.5rem + font-weight: 400 + margin-block-start: 0em + margin-block-end: 0em + + +.q-editor h3, .q-menu h3 + font-size: 1.1rem + line-height: 1.5rem + font-weight: 400 + margin-block-start: 0em + margin-block-end: 0em + +.q-editor p, .q-menu p + margin: 0 + +/* q-tree */ + +.q-tree + color: #c8d3db diff --git a/src/views/ErrorNotFoundPage.vue b/src/views/ErrorNotFoundPage.vue new file mode 100644 index 0000000..4768349 --- /dev/null +++ b/src/views/ErrorNotFoundPage.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/views/login.vue b/src/views/login.vue new file mode 100644 index 0000000..1912c6c --- /dev/null +++ b/src/views/login.vue @@ -0,0 +1,210 @@ + + + + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..bde017e --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "@vue/tsconfig/tsconfig.web.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "esModuleInterop": true, + "ignoreDeprecations": "5.0", + "allowSyntheticDefaultImports": true, + "lib": ["dom", "es2015", "es2018", "es2018.promise"] + } +} diff --git a/tsconfig.config.json b/tsconfig.config.json new file mode 100644 index 0000000..48ba8af --- /dev/null +++ b/tsconfig.config.json @@ -0,0 +1,16 @@ +{ + "extends": "@vue/tsconfig/tsconfig.node.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "types": ["node"], + "ignoreDeprecations": "5.0", + "verbatimModuleSyntax": true, + "module": "esnext" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..31f9003 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.config.json" + }, + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.vitest.json" + } + ] +} diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json new file mode 100644 index 0000000..d080d61 --- /dev/null +++ b/tsconfig.vitest.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.app.json", + "exclude": [], + "compilerOptions": { + "composite": true, + "lib": [], + "types": ["node", "jsdom"] + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..292aa7d --- /dev/null +++ b/vite.config.js @@ -0,0 +1,66 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import { quasar, transformAssetUrls } from '@quasar/vite-plugin' +import { VitePWA } from 'vite-plugin-pwa' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue({ + template: { transformAssetUrls }, + }), + quasar({ + sassVariables: 'src/style/quasar-variables.sass', + }), + vueJsx(), + VitePWA({ + registerType: 'autoUpdate', + injectRegister: 'auto', + workbox: { + cleanupOutdatedCaches: true, + globPatterns: ['**/*.*'], + }, + includeAssets: ['icons/safari-pinned-tab.svg'], + manifest: { + name: 'BMA-Checkin', + short_name: 'EHR Checkin', + theme_color: '#ffffff', + icons: [ + { + src: 'icons/android-chrome-192x192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: 'icons/android-chrome-512x512.png', + sizes: '512x512', + type: 'image/png', + }, + { + src: 'icons/android-chrome-512x512.png', + sizes: '512x512', + type: 'image/png', + purpose: ['any', 'maskable'], + }, + ], + }, + }), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + build: { + target: 'esnext', + }, + server: { + port: 3002, + }, + optimizeDeps: { + include: ['esri-loader'], + }, +})