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