build(scaffolding): set up all folders and files, most of them empty, built some logic, clarified file names.
This commit is contained in:
parent
a8e6b8750d
commit
a63ae452a8
62
.gitignore
vendored
Normal file
62
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Cordova related directories and files
|
||||||
|
/src-cordova/node_modules
|
||||||
|
/src-cordova/platforms
|
||||||
|
/src-cordova/plugins
|
||||||
|
/src-cordova/www
|
||||||
|
|
||||||
|
# Capacitor related directories and files
|
||||||
|
/src-capacitor/www
|
||||||
|
/src-capacitor/node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Quasar
|
||||||
|
.quasar/
|
||||||
|
dist/
|
||||||
|
public/statics/
|
||||||
|
/quasar.config.*.temporary.compiled*
|
||||||
|
|
||||||
|
# Vite / Quasar Build
|
||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
.cache/
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Environment Files
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
cypress/videos/
|
||||||
|
cypress/screenshots/
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Optional IDE/project files
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
# Prettier / Lint
|
||||||
|
.prettiercache
|
||||||
|
|
||||||
|
# Husky
|
||||||
|
.husky/_/
|
||||||
5
.npmrc
Normal file
5
.npmrc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# pnpm-related options
|
||||||
|
shamefully-hoist=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
# to get the latest compatible packages when creating the project https://github.com/pnpm/pnpm/issues/6463
|
||||||
|
resolution-mode=highest
|
||||||
115
README.md
115
README.md
|
|
@ -0,0 +1,115 @@
|
||||||
|
# 🌐 Targo 2.0 Frontend
|
||||||
|
|
||||||
|
> A modern, scalable frontend for managing employees, timesheets, and time-off requests in a rural ISP environment — built with [Vue 3](https://vuejs.org/) and [Quasar Framework](https://quasar.dev/).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
| ------------ | ------------------------------------- |
|
||||||
|
| UI Framework | [Quasar (Vue 3)](https://quasar.dev/) |
|
||||||
|
| Router | Vue Router 4 |
|
||||||
|
| State | Pinia |
|
||||||
|
| HTTP | Axios |
|
||||||
|
| Auth | OAuth2/OIDC (popup-based) via backend |
|
||||||
|
| i18n | Vue I18n |
|
||||||
|
| Build Tools | Vite (default) |
|
||||||
|
| Testing | (Planned) Vitest, Cypress |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
src/
|
||||||
|
├── boot/ # Initialization scripts (e.g. axios, auth)
|
||||||
|
├── modules/ # Feature modules
|
||||||
|
│ ├── dashboard/ # Role-based dashboards (admin, technician, etc.)
|
||||||
|
│ ├── auth/ # Login popup, logout flow
|
||||||
|
│ ├── users/ # User management
|
||||||
|
│ ├── shared/ # Common components, services, etc.
|
||||||
|
│ ├── time-sheet/ # Create, validate, export timesheets and expenses
|
||||||
|
│ ├── router/ # Vue Router config
|
||||||
|
│ ├── validations/ # ??????
|
||||||
|
│ ├── i18n/ # Internationalization references
|
||||||
|
│ ├── pages/ # Route-level pages (fallbacks, misc)
|
||||||
|
│ ├── stores/ # Pinia stores
|
||||||
|
│ ├── layouts/ # App shell (toolbar, drawer)
|
||||||
|
│ ├── css/ # Stylesheets
|
||||||
|
│ ├── assets/ # Images, styles, icons
|
||||||
|
└── App.vue # Root component
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Setup Instructions
|
||||||
|
|
||||||
|
### 1. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
# or
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Start the Dev Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
quasar dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Build for Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
quasar build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🥪 Testing (Planned)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests (Vitest)
|
||||||
|
npm run test:unit
|
||||||
|
|
||||||
|
# E2E tests (Cypress)
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Recommended Quasar Extensions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
quasar ext add @quasar/pwa # PWA mode
|
||||||
|
quasar ext add @quasar/testing # Testing utils
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧹 Linting & Formatting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run lint
|
||||||
|
npm run format
|
||||||
|
```
|
||||||
|
|
||||||
|
Pre-commit hooks are managed with:
|
||||||
|
|
||||||
|
* [husky](https://github.com/typicode/husky)
|
||||||
|
* [lint-staged](https://github.com/okonet/lint-staged)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Notes
|
||||||
|
|
||||||
|
* All routes are prefixed for internal navigation.
|
||||||
|
* Common services and components live in `modules/shared/`.
|
||||||
|
* Avoid placing OIDC or sensitive logic in the frontend — everything auth-related is delegated to the backend.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is proprietary and internal to **\[Targo Communication]**.
|
||||||
83
eslint.config.js
Normal file
83
eslint.config.js
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
import pluginQuasar from '@quasar/app-vite/eslint'
|
||||||
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Ignore the following files.
|
||||||
|
* Please note that pluginQuasar.configs.recommended() already ignores
|
||||||
|
* the "node_modules" folder for you (and all other Quasar project
|
||||||
|
* relevant folders and files).
|
||||||
|
*
|
||||||
|
* ESLint requires "ignores" key to be the only one in this object
|
||||||
|
*/
|
||||||
|
// ignores: []
|
||||||
|
},
|
||||||
|
|
||||||
|
pluginQuasar.configs.recommended(),
|
||||||
|
js.configs.recommended,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://eslint.vuejs.org
|
||||||
|
*
|
||||||
|
* pluginVue.configs.base
|
||||||
|
* -> Settings and rules to enable correct ESLint parsing.
|
||||||
|
* pluginVue.configs[ 'flat/essential']
|
||||||
|
* -> base, plus rules to prevent errors or unintended behavior.
|
||||||
|
* pluginVue.configs["flat/strongly-recommended"]
|
||||||
|
* -> Above, plus rules to considerably improve code readability and/or dev experience.
|
||||||
|
* pluginVue.configs["flat/recommended"]
|
||||||
|
* -> Above, plus rules to enforce subjective community defaults to ensure consistency.
|
||||||
|
*/
|
||||||
|
pluginVue.configs[ 'flat/essential' ],
|
||||||
|
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.vue'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/consistent-type-imports': [
|
||||||
|
'error',
|
||||||
|
{ prefer: 'type-imports' }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// https://github.com/vuejs/eslint-config-typescript
|
||||||
|
vueTsConfigs.recommendedTypeChecked,
|
||||||
|
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node, // SSR, Electron, config files
|
||||||
|
process: 'readonly', // process.env.*
|
||||||
|
ga: 'readonly', // Google Analytics
|
||||||
|
cordova: 'readonly',
|
||||||
|
Capacitor: 'readonly',
|
||||||
|
chrome: 'readonly', // BEX related
|
||||||
|
browser: 'readonly' // BEX related
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// add your custom rules here
|
||||||
|
rules: {
|
||||||
|
'prefer-promise-reject-errors': 'off',
|
||||||
|
|
||||||
|
// allow debugger during development only
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
files: [ 'src-pwa/custom-service-worker.ts' ],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.serviceworker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
21
index.html
Normal file
21
index.html
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title><%= productName %></title>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="<%= productDescription %>">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<meta name="msapplication-tap-highlight" content="no">
|
||||||
|
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/ico" href="favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- quasar:entry-point -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10586
package-lock.json
generated
Normal file
10586
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
package.json
Normal file
50
package.json
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"name": "targo_frontend",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A Quasar PWA Project for managing employee logic",
|
||||||
|
"productName": "App Targo",
|
||||||
|
"author": "Nicolas Drolet <nicolasd@targointernet.com>",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint -c ./eslint.config.js \"./src*/**/*.{ts,js,cjs,mjs,vue}\"",
|
||||||
|
"test": "echo \"No test specified\" && exit 0",
|
||||||
|
"dev": "quasar dev",
|
||||||
|
"build": "quasar build",
|
||||||
|
"postinstall": "quasar prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@quasar/extras": "^1.17.0",
|
||||||
|
"axios": "^1.11.0",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"pinia-plugin-persistedstate": "^4.4.1",
|
||||||
|
"quasar": "^2.18.2",
|
||||||
|
"vue": "^3.5.18",
|
||||||
|
"vue-i18n": "^11.1.11",
|
||||||
|
"vue-router": "^4.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"cypress": "^14.5.2",
|
||||||
|
"@eslint/js": "^9.14.0",
|
||||||
|
"eslint": "^9.31.0",
|
||||||
|
"eslint-plugin-vue": "^10.3.0",
|
||||||
|
"globals": "^15.12.0",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"lint-staged": "^16.1.2",
|
||||||
|
"vitest": "^3.2.4",
|
||||||
|
"vue-tsc": "^2.0.29",
|
||||||
|
"@vue/eslint-config-typescript": "^14.4.0",
|
||||||
|
"vite-plugin-checker": "^0.9.0",
|
||||||
|
"@types/node": "^20.5.9",
|
||||||
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
|
"@quasar/app-vite": "^2.1.0",
|
||||||
|
"autoprefixer": "^10.4.2",
|
||||||
|
"typescript": "~5.5.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",
|
||||||
|
"npm": ">= 6.13.4",
|
||||||
|
"yarn": ">= 1.21.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
postcss.config.js
Normal file
29
postcss.config.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||||
|
|
||||||
|
import autoprefixer from 'autoprefixer'
|
||||||
|
// import rtlcss from 'postcss-rtlcss'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
// https://github.com/postcss/autoprefixer
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/elchininet/postcss-rtlcss
|
||||||
|
// If you want to support RTL css, then
|
||||||
|
// 1. yarn/pnpm/bun/npm install postcss-rtlcss
|
||||||
|
// 2. optionally set quasar.config.js > framework > lang to an RTL language
|
||||||
|
// 3. uncomment the following line (and its import statement above):
|
||||||
|
// rtlcss()
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
BIN
public/icons/favicon-128x128.png
Normal file
BIN
public/icons/favicon-128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
public/icons/favicon-16x16.png
Normal file
BIN
public/icons/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 859 B |
BIN
public/icons/favicon-32x32.png
Normal file
BIN
public/icons/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/icons/favicon-96x96.png
Normal file
BIN
public/icons/favicon-96x96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
235
quasar.config.ts
Normal file
235
quasar.config.ts
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
// Configuration for your app
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file
|
||||||
|
|
||||||
|
import { defineConfig } from '#q-app/wrappers';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
export default defineConfig((ctx) => {
|
||||||
|
return {
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
|
||||||
|
// preFetch: true,
|
||||||
|
|
||||||
|
// app boot file (/src/boot)
|
||||||
|
// --> boot files are part of "main.js"
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||||
|
boot: [
|
||||||
|
'i18n',
|
||||||
|
'axios'
|
||||||
|
],
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css
|
||||||
|
css: [
|
||||||
|
'app.scss'
|
||||||
|
],
|
||||||
|
|
||||||
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
|
extras: [
|
||||||
|
// 'ionicons-v4',
|
||||||
|
// 'mdi-v7',
|
||||||
|
// 'fontawesome-v6',
|
||||||
|
// 'eva-icons',
|
||||||
|
// 'themify',
|
||||||
|
// 'line-awesome',
|
||||||
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
|
||||||
|
'roboto-font', // optional, you are not bound to it
|
||||||
|
'material-icons', // optional, you are not bound to it
|
||||||
|
],
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
||||||
|
build: {
|
||||||
|
target: {
|
||||||
|
browser: [ 'es2022', 'firefox115', 'chrome115', 'safari14' ],
|
||||||
|
node: 'node20'
|
||||||
|
},
|
||||||
|
|
||||||
|
typescript: {
|
||||||
|
strict: true,
|
||||||
|
vueShim: true
|
||||||
|
// extendTsConfig (tsConfig) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||||
|
// vueRouterBase,
|
||||||
|
// vueDevtools,
|
||||||
|
// vueOptionsAPI: false,
|
||||||
|
|
||||||
|
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
|
||||||
|
|
||||||
|
// publicPath: '/',
|
||||||
|
// analyze: true,
|
||||||
|
// env: {},
|
||||||
|
// rawDefine: {}
|
||||||
|
// ignorePublicFolder: true,
|
||||||
|
// minify: false,
|
||||||
|
// polyfillModulePreload: true,
|
||||||
|
// distDir
|
||||||
|
|
||||||
|
// extendViteConf (viteConf) {},
|
||||||
|
// viteVuePluginOptions: {},
|
||||||
|
|
||||||
|
vitePlugins: [
|
||||||
|
['@intlify/unplugin-vue-i18n/vite', {
|
||||||
|
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
|
||||||
|
// compositionOnly: false,
|
||||||
|
|
||||||
|
// if you want to use named tokens in your Vue I18n messages, such as 'Hello {name}',
|
||||||
|
// you need to set `runtimeOnly: false`
|
||||||
|
// runtimeOnly: false,
|
||||||
|
|
||||||
|
ssr: ctx.modeName === 'ssr',
|
||||||
|
|
||||||
|
// you need to set i18n resource including paths !
|
||||||
|
include: [ fileURLToPath(new URL('./src/i18n', import.meta.url)) ]
|
||||||
|
}],
|
||||||
|
|
||||||
|
['vite-plugin-checker', {
|
||||||
|
vueTsc: true,
|
||||||
|
eslint: {
|
||||||
|
lintCommand: 'eslint -c ./eslint.config.js "./src*/**/*.{ts,js,mjs,cjs,vue}"',
|
||||||
|
useFlatConfig: true
|
||||||
|
}
|
||||||
|
}, { server: false }]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver
|
||||||
|
devServer: {
|
||||||
|
// https: true,
|
||||||
|
open: true // opens browser window automatically
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework
|
||||||
|
framework: {
|
||||||
|
config: {},
|
||||||
|
|
||||||
|
// iconSet: 'material-icons', // Quasar icon set
|
||||||
|
// lang: 'en-US', // Quasar language pack
|
||||||
|
|
||||||
|
// For special cases outside of where the auto-import strategy can have an impact
|
||||||
|
// (like functional components as one of the examples),
|
||||||
|
// you can manually specify Quasar components/directives to be available everywhere:
|
||||||
|
//
|
||||||
|
// components: [],
|
||||||
|
// directives: [],
|
||||||
|
|
||||||
|
// Quasar plugins
|
||||||
|
plugins: []
|
||||||
|
},
|
||||||
|
|
||||||
|
// animations: 'all', // --- includes all animations
|
||||||
|
// https://v2.quasar.dev/options/animations
|
||||||
|
animations: [],
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#sourcefiles
|
||||||
|
// sourceFiles: {
|
||||||
|
// rootComponent: 'src/App.vue',
|
||||||
|
// router: 'src/router/index',
|
||||||
|
// store: 'src/store/index',
|
||||||
|
// pwaRegisterServiceWorker: 'src-pwa/register-service-worker',
|
||||||
|
// pwaServiceWorker: 'src-pwa/custom-service-worker',
|
||||||
|
// pwaManifestFile: 'src-pwa/manifest.json',
|
||||||
|
// electronMain: 'src-electron/electron-main',
|
||||||
|
// electronPreload: 'src-electron/electron-preload'
|
||||||
|
// bexManifestFile: 'src-bex/manifest.json
|
||||||
|
// },
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
|
||||||
|
ssr: {
|
||||||
|
prodPort: 3000, // The default port that the production server should use
|
||||||
|
// (gets superseded if process.env.PORT is specified at runtime)
|
||||||
|
|
||||||
|
middlewares: [
|
||||||
|
'render' // keep this as last one
|
||||||
|
],
|
||||||
|
|
||||||
|
// extendPackageJson (json) {},
|
||||||
|
// extendSSRWebserverConf (esbuildConf) {},
|
||||||
|
|
||||||
|
// manualStoreSerialization: true,
|
||||||
|
// manualStoreSsrContextInjection: true,
|
||||||
|
// manualStoreHydration: true,
|
||||||
|
// manualPostHydrationTrigger: true,
|
||||||
|
|
||||||
|
pwa: false
|
||||||
|
// pwaOfflineHtmlFilename: 'offline.html', // do NOT use index.html as name!
|
||||||
|
|
||||||
|
// pwaExtendGenerateSWOptions (cfg) {},
|
||||||
|
// pwaExtendInjectManifestOptions (cfg) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
|
||||||
|
pwa: {
|
||||||
|
workboxMode: 'GenerateSW' // 'GenerateSW' or 'InjectManifest'
|
||||||
|
// swFilename: 'sw.js',
|
||||||
|
// manifestFilename: 'manifest.json',
|
||||||
|
// extendManifestJson (json) {},
|
||||||
|
// useCredentialsForManifestTag: true,
|
||||||
|
// injectPwaMetaTags: false,
|
||||||
|
// extendPWACustomSWConf (esbuildConf) {},
|
||||||
|
// extendGenerateSWOptions (cfg) {},
|
||||||
|
// extendInjectManifestOptions (cfg) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
|
||||||
|
cordova: {
|
||||||
|
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
|
||||||
|
capacitor: {
|
||||||
|
hideSplashscreen: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
|
||||||
|
electron: {
|
||||||
|
// extendElectronMainConf (esbuildConf) {},
|
||||||
|
// extendElectronPreloadConf (esbuildConf) {},
|
||||||
|
|
||||||
|
// extendPackageJson (json) {},
|
||||||
|
|
||||||
|
// Electron preload scripts (if any) from /src-electron, WITHOUT file extension
|
||||||
|
preloadScripts: [ 'electron-preload' ],
|
||||||
|
|
||||||
|
// specify the debugging port to use for the Electron app when running in development mode
|
||||||
|
inspectPort: 5858,
|
||||||
|
|
||||||
|
bundler: 'packager', // 'packager' or 'builder'
|
||||||
|
|
||||||
|
packager: {
|
||||||
|
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
|
||||||
|
|
||||||
|
// OS X / Mac App Store
|
||||||
|
// appBundleId: '',
|
||||||
|
// appCategoryType: '',
|
||||||
|
// osxSign: '',
|
||||||
|
// protocol: 'myapp://path',
|
||||||
|
|
||||||
|
// Windows only
|
||||||
|
// win32metadata: { ... }
|
||||||
|
},
|
||||||
|
|
||||||
|
builder: {
|
||||||
|
// https://www.electron.build/configuration/configuration
|
||||||
|
|
||||||
|
appId: 'quasar-project'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
||||||
|
bex: {
|
||||||
|
// extendBexScriptsConf (esbuildConf) {},
|
||||||
|
// extendBexManifestJson (json) {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of extra scripts (js/ts) not in your bex manifest that you want to
|
||||||
|
* compile and use in your browser extension. Maybe dynamic use them?
|
||||||
|
*
|
||||||
|
* Each entry in the list should be a relative filename to /src-bex/
|
||||||
|
*
|
||||||
|
* @example [ 'my-script.ts', 'sub-folder/my-other-script.js' ]
|
||||||
|
*/
|
||||||
|
extraScripts: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
7
src/App.vue
Normal file
7
src/App.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
//
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
31
src/boot/axios.ts
Normal file
31
src/boot/axios.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { defineBoot } from '#q-app/wrappers';
|
||||||
|
import axios, { type AxiosInstance } from 'axios';
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$axios: AxiosInstance;
|
||||||
|
$api: AxiosInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Be careful when using SSR for cross-request state pollution
|
||||||
|
// due to creating a Singleton instance here;
|
||||||
|
// If any client changes this (global) instance, it might be a
|
||||||
|
// good idea to move this instance creation inside of the
|
||||||
|
// "export default () => {}" function below (which runs individually
|
||||||
|
// for each client)
|
||||||
|
const api = axios.create({ baseURL: 'https://api.example.com' });
|
||||||
|
|
||||||
|
export default defineBoot(({ app }) => {
|
||||||
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
|
||||||
|
app.config.globalProperties.$axios = axios;
|
||||||
|
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||||
|
// so you won't necessarily have to import axios in each vue file
|
||||||
|
|
||||||
|
app.config.globalProperties.$api = api;
|
||||||
|
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
|
||||||
|
// so you can easily perform requests against your app's API
|
||||||
|
});
|
||||||
|
|
||||||
|
export { api };
|
||||||
33
src/boot/i18n.ts
Normal file
33
src/boot/i18n.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { defineBoot } from '#q-app/wrappers';
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import messages from 'src/modules/i18n';
|
||||||
|
|
||||||
|
export type MessageLanguages = keyof typeof messages;
|
||||||
|
// Type-define 'en-US' as the master schema for the resource
|
||||||
|
export type MessageSchema = typeof messages['en-CA'];
|
||||||
|
|
||||||
|
// See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition
|
||||||
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||||
|
declare module 'vue-i18n' {
|
||||||
|
// define the locale messages schema
|
||||||
|
export interface DefineLocaleMessage extends MessageSchema {}
|
||||||
|
|
||||||
|
// define the datetime format schema
|
||||||
|
export interface DefineDateTimeFormat {}
|
||||||
|
|
||||||
|
// define the number format schema
|
||||||
|
export interface DefineNumberFormat {}
|
||||||
|
}
|
||||||
|
/* eslint-enable @typescript-eslint/no-empty-object-type */
|
||||||
|
|
||||||
|
export default defineBoot(({ app }) => {
|
||||||
|
const i18n = createI18n<{ message: MessageSchema }, MessageLanguages>({
|
||||||
|
locale: 'en-CA',
|
||||||
|
legacy: false,
|
||||||
|
messages,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set i18n instance on app
|
||||||
|
app.use(i18n);
|
||||||
|
});
|
||||||
0
src/boot/oidc-client.ts
Normal file
0
src/boot/oidc-client.ts
Normal file
7
src/env.d.ts
vendored
Normal file
7
src/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
declare namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
NODE_ENV: string;
|
||||||
|
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
|
||||||
|
VUE_ROUTER_BASE: string | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/modules/auth/api/use-auth-access.ts
Normal file
17
src/modules/auth/api/use-auth-access.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { useAuthStore } from "src/modules/stores/auth.store";
|
||||||
|
|
||||||
|
export const useAuthAccess = () => {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const isLoggedIn = async () => {
|
||||||
|
return authStore.hasAuthToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAuthorizedUser = async (email: string) => {
|
||||||
|
return authStore.isAuthorizedUser(email);
|
||||||
|
};
|
||||||
|
|
||||||
|
const forgotPassword = async (email: string) => {
|
||||||
|
return authStore.forgotPassword(email);
|
||||||
|
};
|
||||||
|
}
|
||||||
25
src/modules/auth/api/use-auth-session.ts
Normal file
25
src/modules/auth/api/use-auth-session.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useAuthStore } from "src/modules/stores/auth.store";
|
||||||
|
|
||||||
|
export const useAuthSession = () => {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
return authStore.login();
|
||||||
|
};
|
||||||
|
|
||||||
|
const oidcLogin = async () => {
|
||||||
|
return authStore.oidcLogin();
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
return authStore.logout();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setUser = (user: Record<string, any>) => {
|
||||||
|
return authStore.setUser( user );
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAuthToken = (token: string) => {
|
||||||
|
return authStore.setAuthToken( token );
|
||||||
|
};
|
||||||
|
}
|
||||||
0
src/modules/auth/auth.config.ts
Normal file
0
src/modules/auth/auth.config.ts
Normal file
0
src/modules/auth/auth.router.ts
Normal file
0
src/modules/auth/auth.router.ts
Normal file
0
src/modules/auth/pages/auth-callback.vue
Normal file
0
src/modules/auth/pages/auth-callback.vue
Normal file
0
src/modules/auth/pages/auth-email-verification.vue
Normal file
0
src/modules/auth/pages/auth-email-verification.vue
Normal file
0
src/modules/auth/pages/auth-forgot-password.vue
Normal file
0
src/modules/auth/pages/auth-forgot-password.vue
Normal file
0
src/modules/auth/pages/auth-login.vue
Normal file
0
src/modules/auth/pages/auth-login.vue
Normal file
1
src/modules/css/app.scss
Normal file
1
src/modules/css/app.scss
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// app global css in SCSS form
|
||||||
25
src/modules/css/quasar.variables.scss
Normal file
25
src/modules/css/quasar.variables.scss
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Quasar SCSS (& Sass) Variables
|
||||||
|
// --------------------------------------------------
|
||||||
|
// To customize the look and feel of this app, you can override
|
||||||
|
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
|
||||||
|
|
||||||
|
// Check documentation for full list of Quasar variables
|
||||||
|
|
||||||
|
// Your own variables (that are declared here) and Quasar's own
|
||||||
|
// ones will be available out of the box in your .vue/.scss/.sass files
|
||||||
|
|
||||||
|
// It's highly recommended to change the default colors
|
||||||
|
// to match your app's branding.
|
||||||
|
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
||||||
|
|
||||||
|
$primary : #1976D2;
|
||||||
|
$secondary : #26A69A;
|
||||||
|
$accent : #9C27B0;
|
||||||
|
|
||||||
|
$dark : #1D1D1D;
|
||||||
|
$dark-page : #121212;
|
||||||
|
|
||||||
|
$positive : #21BA45;
|
||||||
|
$negative : #C10015;
|
||||||
|
$info : #31CCEC;
|
||||||
|
$warning : #F2C037;
|
||||||
0
src/modules/dashboard/api/api-admin.ts
Normal file
0
src/modules/dashboard/api/api-admin.ts
Normal file
0
src/modules/dashboard/api/api-customer.ts
Normal file
0
src/modules/dashboard/api/api-customer.ts
Normal file
0
src/modules/dashboard/api/api-dealer.ts
Normal file
0
src/modules/dashboard/api/api-dealer.ts
Normal file
0
src/modules/dashboard/api/api-shared.ts
Normal file
0
src/modules/dashboard/api/api-shared.ts
Normal file
0
src/modules/dashboard/api/api-support.ts
Normal file
0
src/modules/dashboard/api/api-support.ts
Normal file
0
src/modules/dashboard/api/api-technician.ts
Normal file
0
src/modules/dashboard/api/api-technician.ts
Normal file
0
src/modules/dashboard/index.ts
Normal file
0
src/modules/dashboard/index.ts
Normal file
7
src/modules/i18n/en-ca/index.ts
Normal file
7
src/modules/i18n/en-ca/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// This is just an example,
|
||||||
|
// so you can safely delete all default props below
|
||||||
|
|
||||||
|
export default {
|
||||||
|
failed: 'Action failed',
|
||||||
|
success: 'Action was successful'
|
||||||
|
};
|
||||||
5
src/modules/i18n/index.ts
Normal file
5
src/modules/i18n/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import enCA from './en-ca';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'en-ca': enCA
|
||||||
|
};
|
||||||
37
src/modules/layouts/MainLayout.vue
Normal file
37
src/modules/layouts/MainLayout.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<q-layout view="lHh Lpr lFf" class="wrapper">
|
||||||
|
<NavBar />
|
||||||
|
<q-page-container :class="pageClass">
|
||||||
|
<router-view class="q-pa-sm" />
|
||||||
|
</q-page-container>
|
||||||
|
<FooterBar />
|
||||||
|
</q-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import NavBar from 'src/modules/shared/components/navs/navBars/NavBar.vue';
|
||||||
|
import FooterBar from 'src/modules/shared/components/navs/footerBars/FooterBar.vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
|
||||||
|
const pageClass = computed(() => {
|
||||||
|
return !$q.screen.xs ? ' container' : 'full-width container';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: $grey-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 90%;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
27
src/modules/pages/page-error.vue
Normal file
27
src/modules/pages/page-error.vue
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 30vh">
|
||||||
|
404
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-h2" style="opacity:.4">
|
||||||
|
Oops. Nothing here...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
class="q-mt-xl"
|
||||||
|
color="white"
|
||||||
|
text-color="blue"
|
||||||
|
unelevated
|
||||||
|
to="/"
|
||||||
|
label="Go Home"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
//
|
||||||
|
</script>
|
||||||
0
src/modules/pages/page-help.vue
Normal file
0
src/modules/pages/page-help.vue
Normal file
0
src/modules/pages/page-index.vue
Normal file
0
src/modules/pages/page-index.vue
Normal file
35
src/modules/router/index.ts
Normal file
35
src/modules/router/index.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { defineRouter } from '#q-app/wrappers';
|
||||||
|
import {
|
||||||
|
createMemoryHistory,
|
||||||
|
createRouter,
|
||||||
|
createWebHashHistory,
|
||||||
|
createWebHistory,
|
||||||
|
} from 'vue-router';
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If not building with SSR mode, you can
|
||||||
|
* directly export the Router instantiation;
|
||||||
|
*
|
||||||
|
* The function below can be async too; either use
|
||||||
|
* async/await or return a Promise which resolves
|
||||||
|
* with the Router instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default defineRouter(function (/* { store, ssrContext } */) {
|
||||||
|
const createHistory = process.env.SERVER
|
||||||
|
? createMemoryHistory
|
||||||
|
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
|
||||||
|
|
||||||
|
const Router = createRouter({
|
||||||
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
routes,
|
||||||
|
|
||||||
|
// Leave this as is and make changes in quasar.conf.js instead!
|
||||||
|
// quasar.conf.js -> build -> vueRouterMode
|
||||||
|
// quasar.conf.js -> build -> publicPath
|
||||||
|
history: createHistory(process.env.VUE_ROUTER_BASE),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Router;
|
||||||
|
});
|
||||||
18
src/modules/router/routes.ts
Normal file
18
src/modules/router/routes.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
|
children: [{ path: '', component: () => import('pages/IndexPage.vue') }],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Always leave this as last one,
|
||||||
|
// but you can also remove it
|
||||||
|
{
|
||||||
|
path: '/:catchAll(.*)*',
|
||||||
|
component: () => import('pages/ErrorNotFound.vue'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
0
src/modules/shared/components/auto-logout.vue
Normal file
0
src/modules/shared/components/auto-logout.vue
Normal file
0
src/modules/shared/components/language-switch.vue
Normal file
0
src/modules/shared/components/language-switch.vue
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
import { useAuthStore } from 'src/modules/stores/auth.store';
|
||||||
|
// import { getInitials } from 'src/helpers/object';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const userConnected = authStore.user;
|
||||||
|
const userRole: string = userConnected.role;
|
||||||
|
const leftDrawerOpen = ref(false);
|
||||||
|
|
||||||
|
function toggleLeftDrawer() {
|
||||||
|
leftDrawerOpen.value = !leftDrawerOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToUsers = () => {
|
||||||
|
router.replace('/users');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToShiftsValidations = () => {
|
||||||
|
router.replace('/time_sheet_validations');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToHome = () => {
|
||||||
|
router.replace('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
// const goToHelp = () => {
|
||||||
|
// router.replace('/help');
|
||||||
|
// };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-footer bordered class="bg-white">
|
||||||
|
<q-tabs
|
||||||
|
no-caps
|
||||||
|
active-color="primary"
|
||||||
|
indicator-color="transparent"
|
||||||
|
class="text-grey-8"
|
||||||
|
>
|
||||||
|
<q-tab name="home" icon="home" @click="goToHome" />
|
||||||
|
<!-- <q-tab name="help" icon="help" @click="goToHelp" /> -->
|
||||||
|
<q-tab name="menu" icon="menu" @click="toggleLeftDrawer" />
|
||||||
|
<q-drawer v-model="leftDrawerOpen" side="right">
|
||||||
|
<q-scroll-area
|
||||||
|
style="
|
||||||
|
height: calc(100% - 150px);
|
||||||
|
margin-top: 150px;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<q-list padding>
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
:active="route.path === '/users'"
|
||||||
|
active-class="bg-primary text-white"
|
||||||
|
@click="goToUsers"
|
||||||
|
v-if="userRole === 'admin'"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="list" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section> {{ $t('navBar.navItem_1') }} </q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
:active="route.path === '/time_sheet_validations'"
|
||||||
|
active-class="bg-primary text-white"
|
||||||
|
@click="goToShiftsValidations"
|
||||||
|
v-if="userRole === 'admin'"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="supervisor_account" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>{{ $t('navBar.navItem_2') }} </q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-scroll-area>
|
||||||
|
|
||||||
|
<q-img
|
||||||
|
class="absolute-top"
|
||||||
|
src="https://cdn.quasar.dev/img/material.png"
|
||||||
|
style="height: 150px"
|
||||||
|
>
|
||||||
|
<div class="absolute-bottom bg-transparent">
|
||||||
|
<!-- <q-avatar color="primary" size="68px" class="q-mb-sm">
|
||||||
|
{{
|
||||||
|
getInitials(
|
||||||
|
`${userConnected.first_name} ${userConnected.last_name}`,
|
||||||
|
)
|
||||||
|
}}</q-avatar -->
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="text-weight-bold">
|
||||||
|
{{ userConnected.firstName }} {{ userConnected.lastName }}
|
||||||
|
</div>
|
||||||
|
<div>{{ userConnected.email }}</div>
|
||||||
|
</div>
|
||||||
|
</q-img>
|
||||||
|
</q-drawer>
|
||||||
|
</q-tabs>
|
||||||
|
</q-footer>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div class="gt-sm footer">
|
||||||
|
<div class="container">© {{ $t('footerLayout.title') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
height: 8em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 3em auto 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 1em;
|
||||||
|
width: 90%;
|
||||||
|
height: 100%;
|
||||||
|
border-top: 2px solid $primary;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import FooterBarMobile from './footer-bar-mobile.vue';
|
||||||
|
import FooterBarWeb from './footer-bar-web.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FooterBarWeb class="gt-sm" />
|
||||||
|
<FooterBarMobile class="lt-md" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useAuthStore } from 'src/modules/stores/auth.store';
|
||||||
|
// import dialogs from 'src/components/dialogs';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const user = authStore.user;
|
||||||
|
// const { NotificationsDialog, AccountDialog } = dialogs;
|
||||||
|
const route = useRoute();
|
||||||
|
const backRoutes = [
|
||||||
|
'newUser',
|
||||||
|
'userById',
|
||||||
|
'timeSheet',
|
||||||
|
'timeSheetValidationsId',
|
||||||
|
];
|
||||||
|
const isBackRoute = computed(
|
||||||
|
() => backRoutes.indexOf(route.name as string) !== -1,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-toolbar v-if="!isBackRoute">
|
||||||
|
<q-toolbar-title>
|
||||||
|
<!-- {{ $t('navBar.mobileIndexTitle') }} {{ user.first_name }} -->
|
||||||
|
</q-toolbar-title>
|
||||||
|
<NotificationsDialog />
|
||||||
|
<AccountDialog />
|
||||||
|
</q-toolbar>
|
||||||
|
<q-toolbar v-else>
|
||||||
|
<q-toolbar-title>
|
||||||
|
<q-btn
|
||||||
|
icon="chevron_left"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
color="white"
|
||||||
|
@click="$router.go(-1)"
|
||||||
|
/>
|
||||||
|
</q-toolbar-title>
|
||||||
|
<div class="text-h6 text-white">
|
||||||
|
{{ $t(`pagesTitles.${route.meta.title}`) }}
|
||||||
|
</div>
|
||||||
|
<q-space />
|
||||||
|
</q-toolbar>
|
||||||
|
</template>
|
||||||
136
src/modules/shared/components/navs/nav-bars/nav-bar-web.vue
Normal file
136
src/modules/shared/components/navs/nav-bars/nav-bar-web.vue
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
<template>
|
||||||
|
<q-toolbar>
|
||||||
|
<q-toolbar-title>
|
||||||
|
<RouterLink class="q-mr-sm navbar-brand" to="/">
|
||||||
|
<q-img
|
||||||
|
src="/public/icons/logo-targo-white.svg"
|
||||||
|
alt="logo"
|
||||||
|
style="width: 50px"
|
||||||
|
to="/"
|
||||||
|
></q-img
|
||||||
|
></RouterLink>
|
||||||
|
</q-toolbar-title>
|
||||||
|
<div>
|
||||||
|
<q-btn
|
||||||
|
class="q-mr-xs"
|
||||||
|
to="/users"
|
||||||
|
flat
|
||||||
|
color="white"
|
||||||
|
:label="$t('navBar.navItem_1')"
|
||||||
|
no-caps
|
||||||
|
v-if="userRole === 'admin'"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
class="q-mr-xs"
|
||||||
|
to="/time_sheet_validations"
|
||||||
|
flat
|
||||||
|
color="white"
|
||||||
|
:label="$t('navBar.navItem_2')"
|
||||||
|
no-caps
|
||||||
|
v-if="userRole === 'admin'"
|
||||||
|
/>
|
||||||
|
<LangSwitch class="q-mr-xs text-white" />
|
||||||
|
<NotificationsDialog />
|
||||||
|
<q-btn round color="white">
|
||||||
|
<q-avatar color="white" text-color="primary">{{
|
||||||
|
// getInitials(`${userConnected.first_name} ${userConnected.last_name}`)
|
||||||
|
}}</q-avatar>
|
||||||
|
<q-menu fit transition-show="flip-right" transition-hide="flip-left">
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<div class="text-subtitle1 q-mb-xs text-no-wrap text-center">
|
||||||
|
<!-- {{ userConnected.first_name }} {{ userConnected.last_name }} -->
|
||||||
|
</div>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item v-ripple clickable @click="goToProfile">
|
||||||
|
<q-item-section avatar
|
||||||
|
><q-icon name="mdi-account" color="primary" size="2em"
|
||||||
|
/></q-item-section>
|
||||||
|
<q-item-section>{{ $t('navBar.menuItem_1') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<!-- <q-item
|
||||||
|
v-if="userType === 'employee'"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="goToTimeSheet"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="work_history" color="primary" size="2em" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ $t('navBar.menuItem_4') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item
|
||||||
|
v-if="userType === 'employee'"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="goToCalender"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="calendar_month" color="primary" size="2em" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ $t('navBar.menuItem_5') }}</q-item-section>
|
||||||
|
</q-item> -->
|
||||||
|
<q-item v-ripple clickable @click="goToHelp">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="help" color="primary" size="2em" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ $t('navBar.menuItem_2') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item v-ripple clickable @click="handleLogout">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="logout" color="primary" size="2em" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>{{ $t('navBar.menuItem_3') }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</q-toolbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
// import { getInitials } from 'src/helpers/object';
|
||||||
|
import { useAuthStore } from 'src/modules/stores/auth.store';
|
||||||
|
import LangSwitch from 'src/components/LangSwitch.vue';
|
||||||
|
// import dialogs from 'src/components/dialogs';
|
||||||
|
// import authenticationApi from 'src/composables/useAuthentication';
|
||||||
|
|
||||||
|
// const { logout } = authenticationApi.useAuthUser();
|
||||||
|
// const { NotificationsDialog } = dialogs;
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const $q = useQuasar();
|
||||||
|
|
||||||
|
const userConnected = authStore.user;
|
||||||
|
const userRole = userConnected.role;
|
||||||
|
// const userType = userConnected.type;
|
||||||
|
|
||||||
|
const goToProfile = () => {
|
||||||
|
router.replace('/profile');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToHelp = () => {
|
||||||
|
router.replace('/help');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToCalender = () => {
|
||||||
|
const pdfUrl = '/calendrier_annuel.pdf';
|
||||||
|
window.open(pdfUrl, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToTimeSheet = () => {
|
||||||
|
router.replace('/time_sheet');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
// const response = await logout();
|
||||||
|
// const { type, message } = response;
|
||||||
|
// $q.notify({ type, message });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
12
src/modules/shared/components/navs/nav-bars/nav-bar.vue
Normal file
12
src/modules/shared/components/navs/nav-bars/nav-bar.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import NavBarMobile from './nav-bar-mobile.vue';
|
||||||
|
import NavBarWeb from './nav-bar-web.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-header elevated>
|
||||||
|
<NavBarMobile class="lt-md" />
|
||||||
|
<NavBarWeb class="gt-sm" />
|
||||||
|
</q-header>
|
||||||
|
</template>
|
||||||
|
|
||||||
0
src/modules/shared/models/models-global.ts
Normal file
0
src/modules/shared/models/models-global.ts
Normal file
6
src/modules/shared/models/models-user.ts
Normal file
6
src/modules/shared/models/models-user.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface User {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
29
src/modules/shared/utils/deep-equal.ts
Normal file
29
src/modules/shared/utils/deep-equal.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export const deepEqual = (o1: any, o2: any) => {
|
||||||
|
if (o1 === o2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
o1 == null ||
|
||||||
|
o2 == null ||
|
||||||
|
typeof o1 !== 'object' ||
|
||||||
|
typeof o2 !== 'object'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys1 = Object.keys(o1);
|
||||||
|
const keys2 = Object.keys(o2);
|
||||||
|
|
||||||
|
if (keys1.length !== keys2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of keys1) {
|
||||||
|
if (!deepEqual(o1[key], o2[key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
25
src/modules/stores/alert.store.ts
Normal file
25
src/modules/stores/alert.store.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useAlertStore = defineStore('alert', {
|
||||||
|
state: () => ({
|
||||||
|
message: '',
|
||||||
|
type: 'info' as 'success' | 'error' | 'info' | 'warning',
|
||||||
|
visible: false,
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
showAlert(msg: string, type: 'success' | 'error' | 'info' | 'warning' = 'info') {
|
||||||
|
this.message = msg;
|
||||||
|
this.type = type;
|
||||||
|
this.visible = true;
|
||||||
|
|
||||||
|
// Auto-hide after 3 seconds (optional)
|
||||||
|
setTimeout(() => this.hideAlert(), 3000);
|
||||||
|
},
|
||||||
|
|
||||||
|
hideAlert() {
|
||||||
|
this.visible = false;
|
||||||
|
this.message = '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
60
src/modules/stores/auth.store.ts
Normal file
60
src/modules/stores/auth.store.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import router from "src/modules/router";
|
||||||
|
import { api } from "src/boot/axios";
|
||||||
|
import { User } from "../shared/components/models/models-user";
|
||||||
|
|
||||||
|
|
||||||
|
interface AuthState {
|
||||||
|
token: string | null;
|
||||||
|
user: User;
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', {
|
||||||
|
state: (): AuthState => ({
|
||||||
|
token: null,
|
||||||
|
user: {
|
||||||
|
firstName: 'Guest',
|
||||||
|
lastName: 'Guest',
|
||||||
|
email: 'guest@guest.com',
|
||||||
|
role: 'guest'
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
hasAuthToken: (state) => !!state.token,
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async login() {
|
||||||
|
return "standard login";
|
||||||
|
},
|
||||||
|
|
||||||
|
async oidcLogin() {
|
||||||
|
return "openIDConnect login";
|
||||||
|
},
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
return "logout";
|
||||||
|
},
|
||||||
|
|
||||||
|
setAuthToken(token: string) {
|
||||||
|
return "setting auth token";
|
||||||
|
},
|
||||||
|
|
||||||
|
setUser(user: Record<string, any>) {
|
||||||
|
return "setting user info";
|
||||||
|
},
|
||||||
|
|
||||||
|
isAuthorizedUser(email: string) {
|
||||||
|
return "checking user authorization";
|
||||||
|
},
|
||||||
|
|
||||||
|
async forgotPassword(email: string) {
|
||||||
|
return "resetting password";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
32
src/modules/stores/index.ts
Normal file
32
src/modules/stores/index.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { defineStore } from '#q-app/wrappers'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When adding new properties to stores, you should also
|
||||||
|
* extend the `PiniaCustomProperties` interface.
|
||||||
|
* @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties
|
||||||
|
*/
|
||||||
|
declare module 'pinia' {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
|
export interface PiniaCustomProperties {
|
||||||
|
// add your custom properties here, if any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If not building with SSR mode, you can
|
||||||
|
* directly export the Store instantiation;
|
||||||
|
*
|
||||||
|
* The function below can be async too; either use
|
||||||
|
* async/await or return a Promise which resolves
|
||||||
|
* with the Store instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default defineStore((/* { ssrContext } */) => {
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
// You can add Pinia plugins here
|
||||||
|
// pinia.use(SomePiniaPlugin)
|
||||||
|
|
||||||
|
return pinia
|
||||||
|
})
|
||||||
10
src/modules/stores/store-flag.d.ts
vendored
Normal file
10
src/modules/stores/store-flag.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||||
|
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||||
|
import 'quasar/dist/types/feature-flag';
|
||||||
|
|
||||||
|
declare module 'quasar/dist/types/feature-flag' {
|
||||||
|
interface QuasarFeatureFlags {
|
||||||
|
store: true;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/modules/timesheets/api/use-expenses-api.ts
Normal file
0
src/modules/timesheets/api/use-expenses-api.ts
Normal file
0
src/modules/timesheets/api/use-shift-api.ts
Normal file
0
src/modules/timesheets/api/use-shift-api.ts
Normal file
0
src/modules/timesheets/api/use-timesheet-api.ts
Normal file
0
src/modules/timesheets/api/use-timesheet-api.ts
Normal file
0
src/modules/timesheets/components/shift/shift.vue
Normal file
0
src/modules/timesheets/components/shift/shift.vue
Normal file
0
src/modules/timesheets/timesheet-config.ts
Normal file
0
src/modules/timesheets/timesheet-config.ts
Normal file
0
src/modules/timesheets/timesheet-types.ts
Normal file
0
src/modules/timesheets/timesheet-types.ts
Normal file
0
src/modules/users/api/use-accounts.ts
Normal file
0
src/modules/users/api/use-accounts.ts
Normal file
0
src/modules/users/api/use-users.ts
Normal file
0
src/modules/users/api/use-users.ts
Normal file
0
src/modules/users/components/user-add.vue
Normal file
0
src/modules/users/components/user-add.vue
Normal file
0
src/modules/users/components/user-update.vue
Normal file
0
src/modules/users/components/user-update.vue
Normal file
0
src/modules/users/components/user.vue
Normal file
0
src/modules/users/components/user.vue
Normal file
0
src/modules/users/user-config.ts
Normal file
0
src/modules/users/user-config.ts
Normal file
0
src/modules/users/user-constants.ts
Normal file
0
src/modules/users/user-constants.ts
Normal file
0
src/modules/users/user-validation.ts
Normal file
0
src/modules/users/user-validation.ts
Normal file
9
src/quasar.d.ts
vendored
Normal file
9
src/quasar.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* 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" />
|
||||||
10
src/shims-vue.d.ts
vendored
Normal file
10
src/shims-vue.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
3
tsconfig.json
Normal file
3
tsconfig.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "./.quasar/tsconfig.json"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user