INIT commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/dist
|
||||||
|
/src-capacitor
|
||||||
|
/src-cordova
|
||||||
|
/.quasar
|
||||||
|
/node_modules
|
||||||
|
.eslintrc.js
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
module.exports = {
|
||||||
|
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
|
||||||
|
// This option interrupts the configuration hierarchy at this file
|
||||||
|
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
|
||||||
|
root: true,
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: '2021', // Allows for the parsing of modern ECMAScript features
|
||||||
|
},
|
||||||
|
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
browser: true,
|
||||||
|
'vue/setup-compiler-macros': true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Rules order is important, please avoid shuffling them
|
||||||
|
extends: [
|
||||||
|
// Base ESLint recommended rules
|
||||||
|
// 'eslint:recommended',
|
||||||
|
|
||||||
|
// Uncomment any of the lines below to choose desired strictness,
|
||||||
|
// but leave only one uncommented!
|
||||||
|
// See https://eslint.vuejs.org/rules/#available-rules
|
||||||
|
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
|
||||||
|
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
|
||||||
|
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
|
||||||
|
|
||||||
|
// https://github.com/prettier/eslint-config-prettier#installation
|
||||||
|
// usage with Prettier, provided by 'eslint-config-prettier'.
|
||||||
|
'prettier'
|
||||||
|
],
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
|
||||||
|
// required to lint *.vue files
|
||||||
|
'vue',
|
||||||
|
|
||||||
|
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
|
||||||
|
// Prettier has not been included as plugin to avoid performance impact
|
||||||
|
// add it as an extension for your IDE
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
globals: {
|
||||||
|
ga: 'readonly', // Google Analytics
|
||||||
|
cordova: 'readonly',
|
||||||
|
__statics: 'readonly',
|
||||||
|
__QUASAR_SSR__: 'readonly',
|
||||||
|
__QUASAR_SSR_SERVER__: 'readonly',
|
||||||
|
__QUASAR_SSR_CLIENT__: 'readonly',
|
||||||
|
__QUASAR_SSR_PWA__: 'readonly',
|
||||||
|
process: 'readonly',
|
||||||
|
Capacitor: 'readonly',
|
||||||
|
chrome: 'readonly'
|
||||||
|
},
|
||||||
|
|
||||||
|
// add your custom rules here
|
||||||
|
rules: {
|
||||||
|
|
||||||
|
'prefer-promise-reject-errors': 'off',
|
||||||
|
|
||||||
|
// allow debugger during development only
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
.DS_Store
|
||||||
|
.thumbs.db
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Quasar core related directories
|
||||||
|
.quasar
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
.vscode
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# pnpm-related options
|
||||||
|
shamefully-hoist=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# ULL Tracker (app)
|
||||||
|
|
||||||
|
https://starter.dev/kits/vue3-apollo-quasar/
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
|
||||||
|
cron:
|
||||||
|
image:
|
||||||
|
|
||||||
|
api:
|
||||||
|
context:
|
||||||
|
build: ../api
|
||||||
|
|
||||||
|
app:
|
||||||
|
context:
|
||||||
|
build: .
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+21
@@ -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>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"src/*": [
|
||||||
|
"src/*"
|
||||||
|
],
|
||||||
|
"app/*": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"components/*": [
|
||||||
|
"src/components/*"
|
||||||
|
],
|
||||||
|
"layouts/*": [
|
||||||
|
"src/layouts/*"
|
||||||
|
],
|
||||||
|
"pages/*": [
|
||||||
|
"src/pages/*"
|
||||||
|
],
|
||||||
|
"assets/*": [
|
||||||
|
"src/assets/*"
|
||||||
|
],
|
||||||
|
"boot/*": [
|
||||||
|
"src/boot/*"
|
||||||
|
],
|
||||||
|
"stores/*": [
|
||||||
|
"src/stores/*"
|
||||||
|
],
|
||||||
|
"vue$": [
|
||||||
|
"node_modules/vue/dist/vue.runtime.esm-bundler.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
".quasar",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A Quasar Project",
|
||||||
|
"productName": "Polétání.cz",
|
||||||
|
"author": "Michal Kváček <michal@kvacek.cz>",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint --ext .js,.vue ./",
|
||||||
|
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@apollo/client": "^3.7.14",
|
||||||
|
"@quasar/extras": "^1.15.11",
|
||||||
|
"@vue/apollo-composable": "4.0.0-beta.2",
|
||||||
|
"@vue/apollo-option": "^4.0.0-beta.5",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"graphql": "16.6.0",
|
||||||
|
"graphql-tag": "2.12.6",
|
||||||
|
"pinia": "^2.0.33",
|
||||||
|
"quasar": "^2.12.0",
|
||||||
|
"vue": "^3.2.47",
|
||||||
|
"vue-router": "^4.1.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@quasar/app-vite": "^1.3.0",
|
||||||
|
"autoprefixer": "^10.4.2",
|
||||||
|
"eslint": "^8.10.0",
|
||||||
|
"eslint-config-prettier": "^8.1.0",
|
||||||
|
"eslint-plugin-vue": "^9.0.0",
|
||||||
|
"postcss": "^8.4.14",
|
||||||
|
"prettier": "^2.5.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": ">= 6.13.4",
|
||||||
|
"yarn": ">= 1.21.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
// https://github.com/postcss/autoprefixer
|
||||||
|
require('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/npm install postcss-rtlcss
|
||||||
|
// 2. optionally set quasar.config.js > framework > lang to an RTL language
|
||||||
|
// 3. uncomment the following line:
|
||||||
|
// require('postcss-rtlcss')
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 859 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,213 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
|
||||||
|
* the ES6 features that are supported by your Node version. https://node.green/
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Configuration for your app
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
||||||
|
|
||||||
|
|
||||||
|
const {configure} = require('quasar/wrappers');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = configure(function (/* ctx */) {
|
||||||
|
return {
|
||||||
|
eslint: {
|
||||||
|
// fix: true,
|
||||||
|
// include = [],
|
||||||
|
// exclude = [],
|
||||||
|
// rawOptions = {},
|
||||||
|
warnings: true,
|
||||||
|
errors: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli/prefetch-feature
|
||||||
|
// preFetch: true,
|
||||||
|
|
||||||
|
// app boot file (/src/boot)
|
||||||
|
// --> boot files are part of "main.js"
|
||||||
|
// https://v2.quasar.dev/quasar-cli/boot-files
|
||||||
|
boot: [],
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||||
|
css: [
|
||||||
|
'app.scss'
|
||||||
|
],
|
||||||
|
|
||||||
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
|
extras: [
|
||||||
|
// 'ionicons-v4',
|
||||||
|
// 'mdi-v5',
|
||||||
|
// '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-js#build
|
||||||
|
build: {
|
||||||
|
target: {
|
||||||
|
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
||||||
|
node: 'node18'
|
||||||
|
},
|
||||||
|
|
||||||
|
vueRouterMode: 'history', // 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: [
|
||||||
|
// [ 'package-name', { ..options.. } ]
|
||||||
|
// ]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||||
|
devServer: {
|
||||||
|
// https: true
|
||||||
|
open: true // opens browser window automatically
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
||||||
|
framework: {
|
||||||
|
config: {},
|
||||||
|
|
||||||
|
iconSet: 'mdi-v6', // Quasar icon set
|
||||||
|
// lang: 'cs-CZ', // 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: [
|
||||||
|
'Dialog',
|
||||||
|
'Meta',
|
||||||
|
'Notify'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// animations: 'all', // --- includes all animations
|
||||||
|
// https://v2.quasar.dev/options/animations
|
||||||
|
animations: [],
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
|
||||||
|
sourceFiles: {
|
||||||
|
rootComponent: 'src/App.vue',
|
||||||
|
router: 'src/router/index',
|
||||||
|
store: 'src/stores/index',
|
||||||
|
// registerServiceWorker: 'src-pwa/register-service-worker',
|
||||||
|
// serviceWorker: 'src-pwa/custom-service-worker',
|
||||||
|
// pwaManifestFile: 'src-pwa/manifest.json',
|
||||||
|
// electronMain: 'src-electron/electron-main',
|
||||||
|
// electronPreload: 'src-electron/electron-preload'
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr
|
||||||
|
ssr: {
|
||||||
|
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
|
||||||
|
// will mess up SSR
|
||||||
|
|
||||||
|
// extendSSRWebserverConf (esbuildConf) {},
|
||||||
|
// extendPackageJson (json) {},
|
||||||
|
|
||||||
|
pwa: false,
|
||||||
|
|
||||||
|
// manualStoreHydration: true,
|
||||||
|
// manualPostHydrationTrigger: true,
|
||||||
|
|
||||||
|
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
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
|
||||||
|
pwa: {
|
||||||
|
workboxMode: 'generateSW', // or 'injectManifest'
|
||||||
|
injectPwaMetaTags: true,
|
||||||
|
swFilename: 'sw.js',
|
||||||
|
manifestFilename: 'manifest.json',
|
||||||
|
useCredentialsForManifestTag: false,
|
||||||
|
// useFilenameHashes: true,
|
||||||
|
// extendGenerateSWOptions (cfg) {}
|
||||||
|
// extendInjectManifestOptions (cfg) {},
|
||||||
|
// extendManifestJson (json) {}
|
||||||
|
// extendPWACustomSWConf (esbuildConf) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli/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/developing-capacitor-apps/configuring-capacitor
|
||||||
|
capacitor: {
|
||||||
|
hideSplashscreen: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
|
||||||
|
electron: {
|
||||||
|
// extendElectronMainConf (esbuildConf)
|
||||||
|
// extendElectronPreloadConf (esbuildConf)
|
||||||
|
|
||||||
|
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: 'app'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
||||||
|
bex: {
|
||||||
|
contentScripts: [
|
||||||
|
'my-content-script'
|
||||||
|
],
|
||||||
|
|
||||||
|
// extendBexScriptsConf (esbuildConf) {}
|
||||||
|
// extendBexManifestJson (json) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {boot} from 'quasar/wrappers'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const accessToken = localStorage.getItem("accessToken")
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: 'http://localhost:8000',
|
||||||
|
withCredentials: true,
|
||||||
|
xsrfHeaderName: "X-CSRF-TOKEN",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json;charset=UTF-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default boot(({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 {axios, api}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "Přidat"
|
||||||
|
},
|
||||||
|
routerLink: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-page-sticky location="bottom" :offset="[30, 30]">
|
||||||
|
<q-fab icon="add" color="primary" :label="props.label" @click="$router.push(props.routerLink)"/>
|
||||||
|
</q-page-sticky>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {isRequestPending} from "src/composables/form/props";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
resetAfterSubmit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isRequestPending,
|
||||||
|
saveButton: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({
|
||||||
|
label: "Save",
|
||||||
|
icon: "save",
|
||||||
|
align: "left"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits(["save"]);
|
||||||
|
const saveForm = () => {
|
||||||
|
const formDataCopy = JSON.parse(JSON.stringify(props.modelValue));
|
||||||
|
emits("save", formDataCopy);
|
||||||
|
|
||||||
|
if (props.resetAfterSubmit) {
|
||||||
|
console.error("Not implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-form @submit.prevent="saveForm" ref="form">
|
||||||
|
|
||||||
|
{{ modelValue }}
|
||||||
|
|
||||||
|
<slot/>
|
||||||
|
|
||||||
|
<q-btn type="submit" color="primary" :icon="props.saveButton.icon" :loading="props.isRequestPending">
|
||||||
|
{{ props.saveButton.label }}
|
||||||
|
<template v-slot:loading>
|
||||||
|
Loading...
|
||||||
|
</template>
|
||||||
|
</q-btn>
|
||||||
|
</q-form>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
previousStep: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
nextStep: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue"])
|
||||||
|
const setStep = (val) => {
|
||||||
|
emit("update:modelValue", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-btn-group flat spread class="q-mt-xl">
|
||||||
|
<q-btn v-if="previousStep" label="Previous step" @click=setStep(props.previousStep) icon="chevron_left"/>
|
||||||
|
<q-btn v-if="nextStep" label="Next step" @click=setStep(props.nextStep) icon-right="chevron_right"/>
|
||||||
|
</q-btn-group>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {onMounted, reactive} from "vue";
|
||||||
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
|
import {setDefaultData} from "src/composables/form/formHandling";
|
||||||
|
import {defaultValues, isRequestPending} from "src/composables/form/props";
|
||||||
|
import {isRequired} from "src/composables/form/rules";
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
id: null,
|
||||||
|
name: "",
|
||||||
|
model: "",
|
||||||
|
manufacturer: "",
|
||||||
|
description: "",
|
||||||
|
organizationId: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
defaultValues,
|
||||||
|
isRequestPending
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
name: [
|
||||||
|
v => isRequired(v)
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (Object.keys(props.defaultValues).length > 0)
|
||||||
|
setDefaultData(formData, props.defaultValues);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FormWrapper v-model="formData">
|
||||||
|
<q-input type="text" :rules="rules.name" v-model="formData.name" label="Volací značka"/>
|
||||||
|
<q-input type="text" v-model="formData.model" label="Model"/>
|
||||||
|
<q-input type="text" v-model="formData.manufacturer" label="Výrobce"/>
|
||||||
|
<q-input type="textarea" v-model="formData.description" label="Popis" />
|
||||||
|
|
||||||
|
</FormWrapper>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h4>letadla</h4>
|
||||||
|
<q-list bordered separator>
|
||||||
|
<q-item clickable v-ripple v-for="plane of props.items" :key="plane">
|
||||||
|
<q-item-section>
|
||||||
|
<router-link :to="{name: 'aircraftDetail', params: {id: plane.id }}">
|
||||||
|
{{ plane.name }}
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
{{ plane.type }}
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<script setup>
|
||||||
|
import {onMounted, ref, toRefs, watch} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, String]
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
locations: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
prependIcon: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
appendIcon: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
let selectedLocationModel = ref("");
|
||||||
|
onMounted(() => {
|
||||||
|
selectedLocationModel.value = props.modelValue;
|
||||||
|
});
|
||||||
|
watch(selectedLocationModel, (newVal) => {
|
||||||
|
// TODO: rozdelit emit na dva modely - jeden na emit existujici lokality, druhy na emit nove vytvarene
|
||||||
|
if (!newVal) return;
|
||||||
|
|
||||||
|
console.log(newVal);
|
||||||
|
|
||||||
|
let value, eventName;
|
||||||
|
if (newVal instanceof String) {
|
||||||
|
eventName = "update:modelValue";
|
||||||
|
value = newVal;
|
||||||
|
} else {
|
||||||
|
eventName = "update:modelValue";
|
||||||
|
value = newVal.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(eventName, value);
|
||||||
|
})
|
||||||
|
|
||||||
|
const {locations} = toRefs(props);
|
||||||
|
let availableLocations = ref([]);
|
||||||
|
|
||||||
|
const filterFn = (val, update, abort) => {
|
||||||
|
update(() => {
|
||||||
|
if (!val || val === "") {
|
||||||
|
availableLocations.value = locations.value;
|
||||||
|
} else {
|
||||||
|
const needle = val.toLowerCase();
|
||||||
|
availableLocations.value = locations.value.filter(v => {
|
||||||
|
return v.name.toLowerCase().indexOf(needle) > -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-select
|
||||||
|
v-model="selectedLocationModel"
|
||||||
|
:label=label
|
||||||
|
use-input
|
||||||
|
input-debounce="100"
|
||||||
|
:rules="rules"
|
||||||
|
:options="availableLocations"
|
||||||
|
option-label="name"
|
||||||
|
option-value="id"
|
||||||
|
clearable
|
||||||
|
new-value-mode="add"
|
||||||
|
use-chips
|
||||||
|
@filter="filterFn"
|
||||||
|
>
|
||||||
|
<template #before v-if="prependIcon">
|
||||||
|
<q-icon :name="prependIcon"/>
|
||||||
|
</template>
|
||||||
|
<template #after v-if="appendIcon">
|
||||||
|
<q-icon :name="appendIcon"/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:selected v-if="!props.multiple">
|
||||||
|
<q-chip
|
||||||
|
v-if="selectedLocationModel"
|
||||||
|
square
|
||||||
|
>
|
||||||
|
<q-avatar color="primary" text-color="white" :icon="selectedLocationModel.type === 'airport' ? 'flight' : 'marker'"/>
|
||||||
|
{{ selectedLocationModel.name }}
|
||||||
|
</q-chip>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<slot name="append"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-slot:no-option>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section class="text-grey">
|
||||||
|
Add new option
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
|
<!-- @input-value u q-select - nelze pouzit misto watche?-->
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{ props }}
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
tag="router-link"
|
||||||
|
:to="to"
|
||||||
|
>
|
||||||
|
<q-item-section
|
||||||
|
v-if="icon"
|
||||||
|
avatar
|
||||||
|
>
|
||||||
|
<q-icon :name="icon" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ title }}</q-item-label>
|
||||||
|
<q-item-label caption>{{ caption }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
caption: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
to: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const setDefaultData = (formData, defaultData) => {
|
||||||
|
console.log("setting default data ", formData, defaultData);
|
||||||
|
|
||||||
|
const ignoredFields = ['__typename'];
|
||||||
|
|
||||||
|
for (const key in defaultData) {
|
||||||
|
if (ignoredFields.includes(key)) continue;
|
||||||
|
if (typeof formData[key] === 'undefined') {
|
||||||
|
console.error(`Key ${key} not in formData, skipping!`)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
formData[key] = defaultData[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export const isRequestPending = {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
};
|
||||||
|
export const defaultValues = {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const isRequired = (v) => !!v || "Required"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import {useMeta} from "quasar";
|
||||||
|
|
||||||
|
export const setTitle = (title) => {
|
||||||
|
|
||||||
|
// todo: otestovat, nevim proc je tam ta factory
|
||||||
|
const metadata = {
|
||||||
|
titleTemplate: () => `${title} - Polétání.cz`,
|
||||||
|
}
|
||||||
|
useMeta(metadata);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const minutesToHumanReadable = (totalMinutes) => {
|
||||||
|
const hours = Math.round(totalMinutes / 60);
|
||||||
|
const minutes = totalMinutes % 60;
|
||||||
|
return `${hours}h ${minutes}min`
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const ktToKph = (speedInKt) => {
|
||||||
|
return speedInKt;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// app global css in SCSS form
|
||||||
@@ -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,0 +1,47 @@
|
|||||||
|
import {ApolloClient, ApolloLink, HttpLink, InMemoryCache} from '@apollo/client/core'
|
||||||
|
import {createApolloProvider} from "@vue/apollo-option";
|
||||||
|
|
||||||
|
const httpLink = new HttpLink({
|
||||||
|
uri: 'http://localhost:8000/graphql',
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const apolloClient = new ApolloClient({
|
||||||
|
link: ApolloLink.from([httpLink]),
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
connectToDevTools: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const apolloWrapper = {
|
||||||
|
query(query, variables = {}) {
|
||||||
|
return apolloClient.query({
|
||||||
|
query: query,
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
variables: variables
|
||||||
|
})
|
||||||
|
.then((response) => response)
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Error during query: ", err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
mutate(mutation, variables, onUpdate = () => {}) {
|
||||||
|
return apolloClient
|
||||||
|
.mutate({
|
||||||
|
mutation: mutation,
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
variables: variables,
|
||||||
|
update: () => {
|
||||||
|
onUpdate();
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => response)
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Error during mutation: ", err);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// @see https://v4.apollo.vuejs.org/migration/
|
||||||
|
export const apolloProvider = createApolloProvider({
|
||||||
|
defaultClient: apolloClient,
|
||||||
|
})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
export const FRAGMENT_AIRCRAFT_FULL_INFO = gql`
|
||||||
|
fragment aircraftFullInfo on Aircraft {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
model
|
||||||
|
description
|
||||||
|
manufacturer
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
import {FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft";
|
||||||
|
|
||||||
|
export const CREATE_AIRCRAFT = gql`
|
||||||
|
mutation createAircraft($input: CreateAircraftInput!) {
|
||||||
|
createAircraft(input: $input) {
|
||||||
|
...aircraftFullInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EDIT_AIRCRAFT = gql`
|
||||||
|
mutation editAircraft($id: Int!, $input: EditAircraftInput!) {
|
||||||
|
editAircraft(id: $id, input: $input) {
|
||||||
|
...aircraftFullInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
||||||
|
`;
|
||||||
|
export const DELETE_AIRCRAFT = gql`
|
||||||
|
mutation deleteAircraft($id: Int!) {
|
||||||
|
deleteAircraft(id: $id) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// export const CREATE_CAMPAIGN = gql`
|
||||||
|
// mutation createCampaign($input: CreateCampaignMutationInput!) {
|
||||||
|
// createCampaign(input: $input) {
|
||||||
|
// campaign {
|
||||||
|
// id
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// `;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
import {FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft";
|
||||||
|
|
||||||
|
export const AIRCRAFTS_LIST = gql`
|
||||||
|
query AircraftsList {
|
||||||
|
aircrafts {
|
||||||
|
...aircraftFullInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
export const AIRCRAFT_DETAIL = gql`
|
||||||
|
query AircraftDetail($id: Int!) {
|
||||||
|
aircraft(id: $id) {
|
||||||
|
...aircraftFullInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
export const FLIGHTS_LIST = gql`
|
||||||
|
query FlightsList {
|
||||||
|
flights {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<q-layout view="hhh lpR lff">
|
||||||
|
<q-header reveal elevated class="bg-primary text-white">
|
||||||
|
<q-toolbar>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
icon="menu"
|
||||||
|
aria-label="Menu"
|
||||||
|
@click="toggleLeftDrawer"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-toolbar-title>
|
||||||
|
Polétání.cz
|
||||||
|
</q-toolbar-title>
|
||||||
|
|
||||||
|
</q-toolbar>
|
||||||
|
</q-header>
|
||||||
|
|
||||||
|
<q-drawer
|
||||||
|
v-model="leftDrawerOpen"
|
||||||
|
show-if-above
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
|
||||||
|
<q-img class="absolute-top" src="https://galerie.kvacek.cz/_data/i/upload/2022/06/22/20220622102027-b104fc0d-me.jpg" style="height: 150px">
|
||||||
|
<div class="absolute-bottom bg-transparent">
|
||||||
|
<q-avatar size="56px" class="q-mb-sm">
|
||||||
|
<q-img :src="loggedUser.avatarImageUrl" />
|
||||||
|
</q-avatar>
|
||||||
|
|
||||||
|
<div class="text-weight-bold">{{loggedUser.name}}</div>
|
||||||
|
</div>
|
||||||
|
</q-img>
|
||||||
|
<q-scroll-area style="height: calc(100% - 150px); margin-top: 150px; border-right: 1px solid #ddd">
|
||||||
|
<q-list class="q-my-lg">
|
||||||
|
<template v-for="link in linksList" :key="link.title">
|
||||||
|
<q-separator v-if="link.separator"/>
|
||||||
|
<NavigationButton
|
||||||
|
v-if="!link.separator"
|
||||||
|
v-bind="link"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-list>
|
||||||
|
</q-scroll-area>
|
||||||
|
</q-drawer>
|
||||||
|
|
||||||
|
<q-page-container class="q-px-md">
|
||||||
|
<router-view/>
|
||||||
|
</q-page-container>
|
||||||
|
</q-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {computed, onMounted, ref} from 'vue'
|
||||||
|
import NavigationButton from "../components/navigation/NavigationButton.vue";
|
||||||
|
import {useMeta} from "quasar";
|
||||||
|
import {useUserStore} from "stores/User";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
|
||||||
|
const metadata = {
|
||||||
|
titleTemplate: title => `${title} - Polétání.cz`,
|
||||||
|
}
|
||||||
|
useMeta(metadata);
|
||||||
|
|
||||||
|
const linksList = [
|
||||||
|
{
|
||||||
|
title: 'Přidat let',
|
||||||
|
icon: 'airplane_ticket',
|
||||||
|
to: {name: "addFlight"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Moje lety',
|
||||||
|
icon: "flight_takeoff",
|
||||||
|
to: {name: "myFlights"}
|
||||||
|
},
|
||||||
|
{separator: true},
|
||||||
|
{
|
||||||
|
title: 'Letadla',
|
||||||
|
icon: "connecting_airports",
|
||||||
|
to: {name: "aircrafts"}
|
||||||
|
},
|
||||||
|
{separator: true},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'Zajímavá místa',
|
||||||
|
icon: "place",
|
||||||
|
to: {name: "poi"}
|
||||||
|
},
|
||||||
|
{separator: true},
|
||||||
|
{
|
||||||
|
title: 'Spolucestující',
|
||||||
|
icon: "people",
|
||||||
|
to: {name: "pilots"}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const leftDrawerOpen = ref(false)
|
||||||
|
const toggleLeftDrawer = () => {
|
||||||
|
leftDrawerOpen.value = !leftDrawerOpen.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const loggedUserStore = useUserStore();
|
||||||
|
onMounted(() => {
|
||||||
|
loggedUserStore.setupCredentials();
|
||||||
|
})
|
||||||
|
const loggedUser = storeToRefs(loggedUserStore).user;
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<q-layout view="lHh Lpr lFf">
|
||||||
|
<q-header elevated>
|
||||||
|
<q-toolbar>
|
||||||
|
<q-toolbar-title>
|
||||||
|
Polétání.cz
|
||||||
|
</q-toolbar-title>
|
||||||
|
</q-toolbar>
|
||||||
|
</q-header>
|
||||||
|
|
||||||
|
<q-page-container>
|
||||||
|
<router-view/>
|
||||||
|
</q-page-container>
|
||||||
|
</q-layout>W
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<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>
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ErrorNotFound'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
import {useUserStore} from "stores/User";
|
||||||
|
|
||||||
|
const loggedUser = useUserStore().user
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
Profile page
|
||||||
|
|
||||||
|
{{ loggedUser }}
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed, onMounted, reactive, ref} from "vue";
|
||||||
|
import {useAircraftsStore} from "stores/Aircrafts";
|
||||||
|
import {useRoute, useRouter} from "vue-router";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
import {useQuasar} from "quasar";
|
||||||
|
|
||||||
|
const aircraftStore = useAircraftsStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const aircraftId = computed(() => parseInt(route.params.id));
|
||||||
|
let loading = ref(false);
|
||||||
|
let aircraft = reactive({});
|
||||||
|
onMounted(() => {
|
||||||
|
loading.value = true;
|
||||||
|
aircraftStore.fetchDetail(aircraftId.value)
|
||||||
|
.then(respAircraft => aircraft = respAircraft)
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
const confirmDelete = (id) => {
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Potvrzení',
|
||||||
|
message: 'Opravdu chceš smazat toto letadlo?',
|
||||||
|
cancel: true,
|
||||||
|
persistent: true
|
||||||
|
}).onOk(() => {
|
||||||
|
aircraftStore.delete(id).then(() => {
|
||||||
|
router.push({name: "aircrafts"});
|
||||||
|
$q.notify({message: "Letadlo uspesne odstraneno", position: "top", color: "positive"});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-skeleton v-if="loading && aircraft"/>
|
||||||
|
<template v-else>
|
||||||
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el :to="{name: 'aircrafts'}">Letadla</q-breadcrumbs-el>
|
||||||
|
<q-breadcrumbs-el>{{ aircraft.name }}</q-breadcrumbs-el>
|
||||||
|
</q-breadcrumbs>
|
||||||
|
|
||||||
|
<h1>{{ aircraft.name }}</h1>
|
||||||
|
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
Výrobce: {{ aircraft.manufacturer }}
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
Model: {{ aircraft.model }}
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
</q-list>
|
||||||
|
<q-btn :to='{name: "aircraftEdit", params: {id: aircraft.id}}'>Edit {{ aircraft.id }}</q-btn>
|
||||||
|
<q-btn @click="confirmDelete(aircraft.id)">Delete</q-btn>
|
||||||
|
|
||||||
|
<!-- <h2>Limits</h2>-->
|
||||||
|
<!-- Wind: {{ aircraft.limits.wind.frontKt }}/{{ aircraft.limits.wind.sideKt }} kt-->
|
||||||
|
<!-- Neprekrocitelna rychlost: {{ aircraft.limits.speed.vmax }}-->
|
||||||
|
<!-- Cestovni rychlost:-->
|
||||||
|
<!-- Maximalni rychlost s klapkami:-->
|
||||||
|
<!-- Padova rychlost bez klapek:-->
|
||||||
|
<!-- Padova rychlost s klapkami (plne vysunute):-->
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup>
|
||||||
|
import {useFlightsStore} from "stores/Flights";
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
import {computed, onMounted} from "vue";
|
||||||
|
import {minutesToHumanReadable} from "src/composables/timeUtils";
|
||||||
|
|
||||||
|
const flightsStore = useFlightsStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
flightsStore.fetchDetail(route.params.id);
|
||||||
|
})
|
||||||
|
|
||||||
|
const flight = computed(() => flightsStore.getDetail(route.params.id) || {});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{flight.takeOffLocation}} - {{flight.landingLocation}}</h1>
|
||||||
|
|
||||||
|
<q-icon name="calendar">{{flight.date}}</q-icon>
|
||||||
|
<q-icon name="weather">
|
||||||
|
Clouds: few
|
||||||
|
Wind: 220/12kt
|
||||||
|
Temp: 12°C
|
||||||
|
</q-icon>
|
||||||
|
|
||||||
|
Track: {{ flight.turnPoints }}
|
||||||
|
|
||||||
|
{{ minutesToHumanReadable(flight.flightDurationMinutes) }}
|
||||||
|
|
||||||
|
<q-page-sticky location="bottom-right" :offset="[30, 30]">
|
||||||
|
<q-fab color="primary" icon="edit" label="Edit" @click="$router.push({name: 'flightEdit', params:{id: flight.id}})"/>
|
||||||
|
</q-page-sticky>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup>
|
||||||
|
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||||
|
import {computed, onMounted} from "vue";
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const poiId = route.params.id;
|
||||||
|
const poiStore = usePointsOfInterestStore();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
poiStore.fetchDetail(poiId);
|
||||||
|
})
|
||||||
|
|
||||||
|
const poi = computed(() => poiStore.getDetail(poiId));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{ poi.name }}</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<q-page-sticky position="bottom-right" :offset="[30, 30]">
|
||||||
|
<q-fab icon="edit" label="Edit"/>
|
||||||
|
</q-page-sticky>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup>
|
||||||
|
import {useSecondPilotsStore} from "stores/SecondPilots";
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
import {useFlightsStore} from "stores/Flights";
|
||||||
|
import {computed, onMounted} from "vue";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
import {minutesToHumanReadable} from "src/composables/timeUtils";
|
||||||
|
|
||||||
|
const secondPilotStore = useSecondPilotsStore();
|
||||||
|
const flightsStore = useFlightsStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
flightsStore.fetch();
|
||||||
|
})
|
||||||
|
|
||||||
|
const secondPilot = {id: 1, name: "Franta Karel"};
|
||||||
|
const flights = storeToRefs(flightsStore).items;
|
||||||
|
|
||||||
|
const commonFlights = computed(() => flights.value.filter(f => f.secondPilot.id === secondPilot.id))
|
||||||
|
|
||||||
|
const totalFlightTime = computed(() => {
|
||||||
|
return commonFlights.value.reduce((acc, flight) => acc + flight.flightDurationMinutes, 0);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el :to="{name: 'pilots'}">Pilots</q-breadcrumbs-el>
|
||||||
|
<q-breadcrumbs-el>{{secondPilot.name}}</q-breadcrumbs-el>
|
||||||
|
</q-breadcrumbs>
|
||||||
|
|
||||||
|
<h1>{{secondPilot.name}}</h1>
|
||||||
|
|
||||||
|
Flights:
|
||||||
|
{{ commonFlights }}
|
||||||
|
|
||||||
|
Total time together: {{minutesToHumanReadable(totalFlightTime)}}
|
||||||
|
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script setup>
|
||||||
|
import AircraftForm from "components/aircrafts/AircraftForm.vue";
|
||||||
|
import {useAircraftsStore} from "stores/Aircrafts";
|
||||||
|
import {useRoute, useRouter} from "vue-router";
|
||||||
|
import {useQuasar} from "quasar";
|
||||||
|
import {onMounted, reactive, ref} from "vue";
|
||||||
|
|
||||||
|
|
||||||
|
let isRequestPending = ref(false);
|
||||||
|
const $q = useQuasar();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const aircraftId = parseInt(useRoute().params.id);
|
||||||
|
|
||||||
|
const handleSave = (formData) => {
|
||||||
|
isRequestPending.value = true;
|
||||||
|
useAircraftsStore().update(aircraftId, formData).then(() => {
|
||||||
|
$q.notify({message: "Letadlo uspesne upraveno", position: "top", color: "positive"});
|
||||||
|
|
||||||
|
router.push({name: "aircrafts"});
|
||||||
|
}).finally(() => {
|
||||||
|
isRequestPending.value = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let loadedAircraft = ref(null);
|
||||||
|
onMounted(() => {
|
||||||
|
useAircraftsStore().fetchDetail(aircraftId).then((data) => {
|
||||||
|
console.log("data ", data);
|
||||||
|
loadedAircraft.value = data;
|
||||||
|
|
||||||
|
} ).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-skeleton v-if="!loadedAircraft" />
|
||||||
|
<template v-if="loadedAircraft">
|
||||||
|
<h1>Editace letadla {{ loadedAircraft.name}}</h1>
|
||||||
|
|
||||||
|
loaded aircraft: {{ loadedAircraft }}
|
||||||
|
|
||||||
|
<AircraftForm @save="handleSave" :default-values="loadedAircraft" :is-request-pending="isRequestPending"/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
flight edit
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed, onMounted, reactive, ref, watch} from "vue";
|
||||||
|
import LocationInput from "components/inputs/LocationInput.vue";
|
||||||
|
import {useAircraftsStore} from "stores/Aircrafts";
|
||||||
|
import {useFlightsStore} from "stores/Flights";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
import {isRequired} from "src/composables/form/rules";
|
||||||
|
import {useMeta} from "quasar";
|
||||||
|
import TabsStepper from "components/TabsStepper.vue";
|
||||||
|
|
||||||
|
const aircraftsStore = useAircraftsStore();
|
||||||
|
const poiStore = usePointsOfInterestStore();
|
||||||
|
const flightsStore = useFlightsStore();
|
||||||
|
|
||||||
|
useMeta({title: "New flight"});
|
||||||
|
onMounted(() => {
|
||||||
|
aircraftsStore.fetch();
|
||||||
|
poiStore.fetch();
|
||||||
|
flightsStore.fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
aircraft: [isRequired],
|
||||||
|
takeOffLocation: [isRequired],
|
||||||
|
LandingLocation: [isRequired],
|
||||||
|
}
|
||||||
|
|
||||||
|
const aircrafts = storeToRefs(aircraftsStore).items;
|
||||||
|
const locations = storeToRefs(poiStore).items;
|
||||||
|
|
||||||
|
const turnPointsCount = ref(1);
|
||||||
|
const formData = reactive({
|
||||||
|
aircraftId: null,
|
||||||
|
takeOffLocation: null,
|
||||||
|
landingLocation: null,
|
||||||
|
turnPoints: [],
|
||||||
|
solo: true,
|
||||||
|
secondPilot: "",
|
||||||
|
instructor: false,
|
||||||
|
picTime: 0,
|
||||||
|
date: new Date(),
|
||||||
|
takeOffTime: "",
|
||||||
|
landingTime: "",
|
||||||
|
flightDurationMinutes: 0,
|
||||||
|
landings: 1,
|
||||||
|
note: ""
|
||||||
|
})
|
||||||
|
const formSteps = ["aircraft", "basic", "track", "copilot", "photos", "notes"]
|
||||||
|
const formStep = ref(formSteps[0]);
|
||||||
|
|
||||||
|
// watch(() => formData.takeOffLocation, (newVal) => {
|
||||||
|
// if (!formData.landingLocation) {
|
||||||
|
// formData.landingLocation = locations.value.find(l => l.id === newVal);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const saveForm = () => {
|
||||||
|
flightsStore.create(this.formData).then(() => {
|
||||||
|
router.push({name: "myFlights"});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const basicInfoFilled = computed(() => {
|
||||||
|
return formData.aircraftId && formData.takeOffLocation && formData.landingLocation && formData.date && formData.flightDurationMinutes > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const removeTurnPoint = (idx) => {
|
||||||
|
formData.turnPoints.splice(idx, 1);
|
||||||
|
turnPointsCount.value--;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el :to="{name: 'myFlights'}">My Flights</q-breadcrumbs-el>
|
||||||
|
<q-breadcrumbs-el>Add new</q-breadcrumbs-el>
|
||||||
|
</q-breadcrumbs>
|
||||||
|
|
||||||
|
<!-- {{ formData }}-->
|
||||||
|
|
||||||
|
<q-form @submit.prevent="saveForm" class="q-my-lg">
|
||||||
|
<q-tabs
|
||||||
|
v-model="formStep"
|
||||||
|
outside-arrows
|
||||||
|
mobile-arrows
|
||||||
|
left-icon="chevron_left"
|
||||||
|
right-icon="chevron_right"
|
||||||
|
active-color="primary"
|
||||||
|
class="q-mb-lg"
|
||||||
|
>
|
||||||
|
<q-tab name="aircraft" icon="flight" label="Aircraft"/>
|
||||||
|
<q-tab name="basic" icon="flight_takeoff" label="Basic"/>
|
||||||
|
<q-tab name="track" icon="route" label="Route"/>
|
||||||
|
<q-tab name="copilot" icon="person_add" label="Copilot"/>
|
||||||
|
<q-tab name="photos" icon="add_a_photo" label="Photos"/>
|
||||||
|
<q-tab name="notes" icon="edit_note" label="Notes"/>
|
||||||
|
</q-tabs>
|
||||||
|
|
||||||
|
<q-tab-panels v-model="formStep" animated>
|
||||||
|
<q-tab-panel name="aircraft">
|
||||||
|
<q-select
|
||||||
|
v-model="formData.aircraftId"
|
||||||
|
option-label="name"
|
||||||
|
option-value="id"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
:rules="rules.aircraft"
|
||||||
|
:options="aircrafts"
|
||||||
|
label="Aircraft"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon name="flight"></q-icon>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
|
<TabsStepper v-model="formStep" next-step="basic"/>
|
||||||
|
</q-tab-panel>
|
||||||
|
<q-tab-panel name="basic">
|
||||||
|
<q-input type="date" v-model="formData.date" label="Date"/>
|
||||||
|
<q-input type="time" v-model="formData.time" label="Take-off time"/>
|
||||||
|
<LocationInput label="Take-off location" :rules="rules.takeOffLocation" :locations="locations" v-model="formData.takeOffLocation" prepend-icon="flight_takeoff"/>
|
||||||
|
<LocationInput label="Landing location" :rules="rules.landingLocation" :locations="locations" v-model="formData.landingLocation" prepend-icon="flight_land"/>
|
||||||
|
<q-input v-model="formData.landings" label="Landings" type="number" min="1"/>
|
||||||
|
<q-input v-model="formData.flightDurationMinutes" min=0 label="Flight duration (minutes)" type="number"/>
|
||||||
|
|
||||||
|
<TabsStepper v-model="formStep" previous-step="aircraft" next-step="track"/>
|
||||||
|
</q-tab-panel>
|
||||||
|
|
||||||
|
<q-tab-panel name="track">
|
||||||
|
<template v-for="i in turnPointsCount" :key="`${i}-${turnPointsCount}`">
|
||||||
|
<LocationInput label="Turn point" :locations="locations" v-model="formData.turnPoints[i-1]">
|
||||||
|
<template #append v-if="i > 1">
|
||||||
|
<q-btn @click="removeTurnPoint(i - 1)">-</q-btn>
|
||||||
|
</template>
|
||||||
|
</LocationInput>
|
||||||
|
</template>
|
||||||
|
<q-btn @click="turnPointsCount++">
|
||||||
|
Add turn point
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-uploader
|
||||||
|
label="Upload GPX track"
|
||||||
|
url="http://localhost:4444/upload"
|
||||||
|
class="q-mx-sm"
|
||||||
|
style="width: 100%"
|
||||||
|
auto-upload
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TabsStepper v-model="formStep" previous-step="basic" next-step="copilot"/>
|
||||||
|
</q-tab-panel>
|
||||||
|
|
||||||
|
<q-tab-panel name="copilot">
|
||||||
|
<q-btn-toggle spread no-caps v-model="formData.solo" toggle-color="primary" :options="[{label: 'Solo flight', value: true}, {label: 'Flight with someone', value: false}]"/>
|
||||||
|
|
||||||
|
<div v-if="!formData.solo">
|
||||||
|
<q-input type="text" v-model="formData.secondPilot" label="Second pilot"/>
|
||||||
|
<q-checkbox v-model="formData.instructor" v-if="formData.secondPilot !== ''" label="Instructor"/>
|
||||||
|
<q-input v-model="formData.picTime" label="PIC time"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TabsStepper v-model="formStep" previous-step="track" next-step="photos"/>
|
||||||
|
</q-tab-panel>
|
||||||
|
|
||||||
|
<q-tab-panel name="photos">
|
||||||
|
<q-uploader
|
||||||
|
label="Upload photos"
|
||||||
|
url="http://localhost:4444/upload"
|
||||||
|
multiple
|
||||||
|
auto-upload
|
||||||
|
style="width: 100%;"
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TabsStepper v-model="formStep" previous-step="copilot" next-step="notes"/>
|
||||||
|
</q-tab-panel>
|
||||||
|
|
||||||
|
<q-tab-panel name="notes">
|
||||||
|
<q-editor v-model="formData.note" height="60vh" label="Notes"/>
|
||||||
|
|
||||||
|
<TabsStepper v-model="formStep" previous-step="photos"/>
|
||||||
|
</q-tab-panel>
|
||||||
|
</q-tab-panels>
|
||||||
|
|
||||||
|
<q-page-sticky expand position="bottom" :offset="[10, 10]" v-if="basicInfoFilled">
|
||||||
|
<div>
|
||||||
|
<q-btn type="submit" size="lg" class="full-width" color="primary" icon="save" label="Save flight"/>
|
||||||
|
</div>
|
||||||
|
</q-page-sticky>
|
||||||
|
</q-form>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup>
|
||||||
|
import AircraftForm from "components/aircrafts/AircraftForm.vue";
|
||||||
|
import {useAircraftsStore} from "stores/Aircrafts";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
import {useQuasar} from "quasar";
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
|
let isRequestPending = ref(false);
|
||||||
|
const $q = useQuasar();
|
||||||
|
const router = useRouter();
|
||||||
|
const handleSave = (formData) => {
|
||||||
|
isRequestPending.value = true;
|
||||||
|
useAircraftsStore().create(formData).then(() => {
|
||||||
|
$q.notify({message: "Letadlo úspěšně vytvořeno", position: "top", color: "positive"});
|
||||||
|
|
||||||
|
router.push({name: "aircrafts"});
|
||||||
|
}).finally(() => {
|
||||||
|
isRequestPending.value = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- <h1>Nové letadlo</h1>-->
|
||||||
|
|
||||||
|
<AircraftForm @save="handleSave" :is-request-pending="isRequestPending"/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup>
|
||||||
|
import {useAircraftsStore} from "stores/Aircrafts";
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
import AircraftsList from "components/aircrafts/AircraftsList.vue";
|
||||||
|
import AddFAB from "components/AddFAB.vue";
|
||||||
|
|
||||||
|
const aircraftStore = useAircraftsStore();
|
||||||
|
const aircrafts = storeToRefs(aircraftStore).items;
|
||||||
|
const aircraftsComputed = computed(() => aircraftStore.items);
|
||||||
|
|
||||||
|
const refetch = () => {
|
||||||
|
loading.value = true
|
||||||
|
aircraftStore.fetch().then(data => console.log("data", data)).finally(() => {
|
||||||
|
console.log("after fetch");
|
||||||
|
loading.value = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let loading = ref(false);
|
||||||
|
onMounted(() => {
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-skeleton v-if="loading">Loading (sem pridat skeleton)</q-skeleton>
|
||||||
|
|
||||||
|
<AircraftsList :items="aircrafts" v-if="!loading"/>
|
||||||
|
|
||||||
|
<q-btn @click="refetch()">Fetch</q-btn>
|
||||||
|
|
||||||
|
{{ aircrafts }}
|
||||||
|
<br>
|
||||||
|
{{ aircraftsComputed }}
|
||||||
|
<br>
|
||||||
|
{{ aircraftStore.items }}
|
||||||
|
|
||||||
|
<AddFAB :router-link="{name: 'addAircraft'}"/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed, onMounted} from "vue";
|
||||||
|
import {useFlightsStore} from "stores/Flights";
|
||||||
|
import {minutesToHumanReadable} from "src/composables/timeUtils";
|
||||||
|
import {useMeta, useQuasar} from "quasar";
|
||||||
|
|
||||||
|
useMeta({title: "Moje lety"});
|
||||||
|
|
||||||
|
const flightsStore = useFlightsStore();
|
||||||
|
onMounted(() => {
|
||||||
|
flightsStore.fetch();
|
||||||
|
})
|
||||||
|
const totalFlightTime = computed(() => {
|
||||||
|
return flightsStore.items.reduce((acc, flight) => acc + flight.flightDurationMinutes, 0);
|
||||||
|
})
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
const deleteFlight = (flightId) => {
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Potvrzení',
|
||||||
|
message: 'Opravdu chceš smazat tento let?',
|
||||||
|
cancel: true,
|
||||||
|
persistent: true
|
||||||
|
}).onOk(() => {
|
||||||
|
flightsStore.deleteItem(flightId);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-4 col-lg-3" v-for="flight of flightsStore.items" :key="flight">
|
||||||
|
|
||||||
|
<q-card class="q-my-sm q-mx-sm">
|
||||||
|
<q-img :src="flight.titlePhotoUrl" v-if="flight.titlePhotoUrl">
|
||||||
|
<div class="absolute-bottom text-center text-h6">
|
||||||
|
<router-link :to="{name: 'flightDetail', params:{id: flight.id}}" class="text-white">
|
||||||
|
{{ flight.takeOffLocation }} - {{ flight.landingLocation }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</q-img>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-h6" v-if="!flight.titlePhotoUrl">
|
||||||
|
<router-link :to="{name: 'flightDetail', params:{id: flight.id}}">
|
||||||
|
{{ flight.takeOffLocation }} - {{ flight.landingLocation }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="text-subtitle2">{{ minutesToHumanReadable(flight.flightDurationMinutes) }}</div>
|
||||||
|
<div class="text-secondary">
|
||||||
|
<q-icon name="today"/>
|
||||||
|
{{ flight.date }}
|
||||||
|
</div>
|
||||||
|
<div class="text-secondary">
|
||||||
|
<q-icon name="plane"/>
|
||||||
|
{{ flight.aircraft.name }}
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-separator/>
|
||||||
|
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<!-- <q-btn flat icon="visibility"></q-btn>-->
|
||||||
|
<!-- <q-btn flat icon="edit" :to="{name: 'flightEdit', params:{id: flight.id}}"></q-btn>-->
|
||||||
|
<q-btn flat icon="delete" color="red" @click="deleteFlight(flight.id)"></q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
Total: {{ minutesToHumanReadable(totalFlightTime) }}
|
||||||
|
|
||||||
|
<q-page-sticky location="bottom" :offset="[30, 30]">
|
||||||
|
<q-fab icon="add" color="primary" label="Add flight" @click="$router.push({name: 'addFlight'})"/>
|
||||||
|
</q-page-sticky>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup>
|
||||||
|
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
|
||||||
|
const poiStore = usePointsOfInterestStore();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
poiStore.fetch();
|
||||||
|
})
|
||||||
|
|
||||||
|
const pointsOfInterest = computed(() => poiStore.groupByTypes);
|
||||||
|
const availableTypes = computed(() => Object.keys(pointsOfInterest.value));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{ pointsOfInterest }}
|
||||||
|
<div v-for="type of availableTypes" :key="type">
|
||||||
|
<h2>{{ poiStore.pointTypeName(type) }}</h2>
|
||||||
|
|
||||||
|
<q-list bordered separator>
|
||||||
|
<q-item clickable v-ripple v-for="poi of pointsOfInterest[type]" :key="poi">
|
||||||
|
<q-item-section>
|
||||||
|
<router-link :to="{name: 'poiDetail', params: {id: poi.id}}">
|
||||||
|
{{ poi.name }}
|
||||||
|
</router-link>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup>
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
import {useSecondPilotsStore} from "stores/SecondPilots";
|
||||||
|
import {onMounted} from "vue";
|
||||||
|
|
||||||
|
const pilotsStore = useSecondPilotsStore();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
pilotsStore.fetch();
|
||||||
|
})
|
||||||
|
|
||||||
|
const secondPilots = storeToRefs(pilotsStore).items;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-list bordered separator>
|
||||||
|
<q-item clickable v-ripple v-for="pilot of secondPilots" :key="pilot">
|
||||||
|
<q-item-label>
|
||||||
|
<router-link :to="{name: 'pilotDetail', params: {id: pilot.id }}">
|
||||||
|
{{ pilot.name }}
|
||||||
|
</router-link>
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-section>
|
||||||
|
section
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<router-link :to='{"name": "login"}'>Login</router-link>
|
||||||
|
<router-link :to='{"name": "registration"}'>Register</router-link>
|
||||||
|
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup>
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {useUserStore} from "stores/User";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const email = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const logUser = () => {
|
||||||
|
useUserStore().login({email: email.value, password: password.value}).then(() => {
|
||||||
|
console.log("tu ok");
|
||||||
|
router.push({name: "myFlights"});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-page class="window-height window-width row justify-center">
|
||||||
|
<q-card square style="width:95%; max-width: 450px; height:400px;" class="q-mt-lg">
|
||||||
|
<q-card-section class="bg-accent">
|
||||||
|
<h4 class="text-h5 q-my-md text-white">Login</h4>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-form class="q-px-sm q-pt-xl" @submit.prevent="logUser()">
|
||||||
|
<q-input square clearable v-model="email" type="email" label="Email">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="email"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<q-input square clearable v-model="password" type="password" label="Password">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="lock"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions class="q-px-lg">
|
||||||
|
<q-btn unelevated size="lg" class="full-width" label="Sign In" @click.prevent="logUser()"/>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-section class="text-center q-pa-sm">
|
||||||
|
<p class="text-grey-6">Forgot your password?</p>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {useUserStore} from "stores/User";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
import {useQuasar} from "quasar";
|
||||||
|
|
||||||
|
const email = ref("");
|
||||||
|
const name = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const $q = useQuasar();
|
||||||
|
const registerUser = () => {
|
||||||
|
useUserStore().register({
|
||||||
|
email: email.value,
|
||||||
|
name: name.value,
|
||||||
|
password: password.value
|
||||||
|
}).then(() => {
|
||||||
|
$q.notify({message: "Registration Ok, now login", position: "top", color: "positive"});
|
||||||
|
router.push({name: 'login'})
|
||||||
|
}).catch(({response}) => {
|
||||||
|
$q.notify({
|
||||||
|
message: "Registration failed",
|
||||||
|
position: "top",
|
||||||
|
color: "negative",
|
||||||
|
timeout: 0,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: "Dismiss", color: "white"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-page
|
||||||
|
class="window-height window-width row justify-center items-center"
|
||||||
|
>
|
||||||
|
<div class="column q-pa-lg">
|
||||||
|
<div class="row">
|
||||||
|
<q-card square class="shadow-24" style="width:300px;height:485px;">
|
||||||
|
<q-card-section class="bg-deep-purple-7">
|
||||||
|
<h4 class="text-h5 text-white q-my-md">Registration</h4>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-form class="q-px-sm q-pt-xl q-pb-lg" @submit.prevent="registerUser">
|
||||||
|
<q-input square clearable v-model="name" type="text" label="Name">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="person"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-input square clearable v-model="email" type="email" label="E-mail">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="email"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<q-input square clearable v-model="password" type="password" label="Password">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="lock"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions class="q-px-lg">
|
||||||
|
<q-btn @click="registerUser" unelevated size="lg" color="purple-4" class="full-width text-white" label="Get Started"/>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-section class="text-center q-pa-sm">
|
||||||
|
<router-link :to="{name: 'login'}" class="text-grey-6">Return to login</router-link>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { route } from 'quasar/wrappers'
|
||||||
|
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
|
||||||
|
import routes from './routes'
|
||||||
|
import {useUserStore} from "stores/User";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 route(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)
|
||||||
|
})
|
||||||
|
|
||||||
|
Router.beforeEach(async (to) => {
|
||||||
|
if (to.meta?.protected) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
// const $q = useQuasar();
|
||||||
|
|
||||||
|
userStore.setupCredentials().then(()=>{
|
||||||
|
if (!userStore.user.id) {
|
||||||
|
// console.log("neni prihlaseny")
|
||||||
|
// $q.notify("No access token, login again!");
|
||||||
|
Router.push({name: "login"});
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
// console.error("Error in router: ", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Router
|
||||||
|
})
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('layouts/PublicLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{path: '', name: "homepage", component: () => import('pages/public/IndexPage.vue')},
|
||||||
|
{path: 'login', name: "login", component: () => import('pages/public/LoginPage.vue')},
|
||||||
|
{path: 'register', name: "registration", component: () => import('pages/public/RegisterPage.vue')},
|
||||||
|
{path: 'about', name: "about", component: () => import('pages/public/AboutPage.vue')},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/profile',
|
||||||
|
component: () => import('layouts/ProfileLayout.vue'),
|
||||||
|
meta: {
|
||||||
|
protected: true,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{path: '', name: "profile", component: () => import('pages/profile/ProfilePage.vue')},
|
||||||
|
{path: 'my-flights', name: "myFlights", component: () => import('pages/profile/lists/MyFlightsPage.vue')},
|
||||||
|
{path: 'my-flights/add', name: "addFlight", component: () => import('pages/profile/forms/FlightLogPage.vue')},
|
||||||
|
{path: 'my-flights/:id', name: "flightDetail", component: () => import('pages/profile/details/FlightDetailPage.vue')},
|
||||||
|
{path: 'my-flights/:id/edit', name: "flightEdit", component: () => import('pages/profile/forms/FlightEditPage.vue')},
|
||||||
|
{path: 'poi', name: "poi", component: () => import('pages/profile/lists/PointsOfInterestPage.vue')},
|
||||||
|
{path: 'poi/:id', name: "poiDetail", component: () => import('pages/profile/details/PointOfInterestDetailPage.vue')},
|
||||||
|
{path: 'pilots', name: "pilots", component: () => import('pages/profile/lists/SecondPilotPage.vue')},
|
||||||
|
{path: 'pilots/:id', name: "pilotDetail", component: () => import('pages/profile/details/SecondPilotDetailPage.vue')},
|
||||||
|
{path: 'aircrafts', name: "aircrafts", component: () => import('pages/profile/lists/AircraftsPage.vue')},
|
||||||
|
{path: 'aircrafts/new', name: "addAircraft", component: () => import('pages/profile/forms/NewAircraftsPage.vue')},
|
||||||
|
{path: 'aircrafts/:id', name: "aircraftDetail", component: () => import('pages/profile/details/AircraftDetailPage.vue')},
|
||||||
|
{path: 'aircrafts/:id/edit', name: "aircraftEdit", component: () => import('pages/profile/forms/EditAircraftPage.vue')},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Always leave this as last one,
|
||||||
|
// but you can also remove it
|
||||||
|
{
|
||||||
|
path: '/:catchAll(.*)*',
|
||||||
|
component: () => import('pages/ErrorNotFound.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default routes
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {apolloWrapper} from "src/graphql/apollo";
|
||||||
|
import {AIRCRAFT_DETAIL, AIRCRAFTS_LIST} from "src/graphql/queries/aircrafts";
|
||||||
|
import {CREATE_AIRCRAFT, DELETE_AIRCRAFT, EDIT_AIRCRAFT} from "src/graphql/mutations/aircrafts";
|
||||||
|
import {reactive} from "vue";
|
||||||
|
|
||||||
|
export const useAircraftsStore = defineStore('Aircrafts', {
|
||||||
|
state: () => ({
|
||||||
|
items: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
getDetail: (state) => (aircraftId) => state.items.find(a => a.id === aircraftId) || {}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
fetchDetail(id) {
|
||||||
|
return apolloWrapper
|
||||||
|
.query(AIRCRAFT_DETAIL, {id})
|
||||||
|
.then((result) => {
|
||||||
|
return result.data.aircraft;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
create(formData) {
|
||||||
|
delete formData['id'];
|
||||||
|
return apolloWrapper.mutate(CREATE_AIRCRAFT, {input: formData}).then(({data}) => {
|
||||||
|
// this.items.push(data.createAircraft);
|
||||||
|
|
||||||
|
return data.createAircraft;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
delete(id) {
|
||||||
|
return apolloWrapper.mutate(DELETE_AIRCRAFT, {id}).then(({data}) => {
|
||||||
|
// const idx = this.items.findIndex(a => a.id === id);
|
||||||
|
// console.log(idx);
|
||||||
|
// this.items.splice(idx, 1);
|
||||||
|
|
||||||
|
this.items = this.items.filter(a => {
|
||||||
|
console.log(a.id, id);
|
||||||
|
return a.id !== id
|
||||||
|
});
|
||||||
|
console.log(this.items);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
update(id, formData) {
|
||||||
|
console.log(id, formData);
|
||||||
|
return apolloWrapper.mutate(EDIT_AIRCRAFT, {id: id, input: formData}).then(({data}) => {
|
||||||
|
return data.editAircraft;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetch() {
|
||||||
|
/***
|
||||||
|
* {id: id, name: "OK-ABC" + id, private: true, limits: {wind: {frontKt: 22, sideKt: 16}, speed: {vmax: 220}}};
|
||||||
|
*/
|
||||||
|
return apolloWrapper
|
||||||
|
.query(AIRCRAFTS_LIST)
|
||||||
|
.then((result) => {
|
||||||
|
this.items = result.data.aircrafts;
|
||||||
|
return result.data.aircrafts;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('App', {
|
||||||
|
state: () => ({
|
||||||
|
pageTitle: null,
|
||||||
|
fabButton: {icon: null, label: null, action: {}}
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {apolloClient} from "src/graphql/apollo"
|
||||||
|
import {FLIGHTS_LIST} from "src/graphql/queries/flights";
|
||||||
|
|
||||||
|
export const useFlightsStore = defineStore('Flights', {
|
||||||
|
state: () => ({
|
||||||
|
items: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
getDetail: (state) => (flightId) => {
|
||||||
|
console.log(state.items);
|
||||||
|
return state.items.find(f => f.id === flightId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
create(formData) {
|
||||||
|
this.items.push(formData);
|
||||||
|
},
|
||||||
|
fetchDetail(id) {
|
||||||
|
this.items.push({id: id, takeOffLocation: "LKLT", landingLocation: "LKLT", date: "2023-02-01", aircraft: {id: 1, name: "OK-AUR28"}, flightDurationMinutes: 76})
|
||||||
|
},
|
||||||
|
fetch() {
|
||||||
|
apolloClient
|
||||||
|
.query({
|
||||||
|
|
||||||
|
query: FLIGHTS_LIST,
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
.then((result) => console.log(result));
|
||||||
|
// if (this.items.length > 0) return;
|
||||||
|
//
|
||||||
|
// this.$patch({
|
||||||
|
// items: [
|
||||||
|
// {id: 1, takeOffLocation: "LKLT", titlePhotoUrl: null, landingLocation: "LKLC", date: "2023-03-03", aircraft: {id: 1, name: "OK-AUR28"}, flightDurationMinutes: 63},
|
||||||
|
// {id: 2, takeOffLocation: "LKLT", titlePhotoUrl: "https://cdn.quasar.dev/img/mountains.jpg", landingLocation: "LKLT", date: "2023-02-01", aircraft: {id: 1, name: "OK-AUR28"}, flightDurationMinutes: 76},
|
||||||
|
// {id: 3, takeOffLocation: "LKLT", titlePhotoUrl: null, landingLocation: "LKLC", date: "2023-04-03", aircraft: {id: 1, name: "OK-AUR28"}, flightDurationMinutes: 53},
|
||||||
|
// {id: 4, takeOffLocation: "LKLT", titlePhotoUrl: null, landingLocation: "LKMB", date: "2023-03-04", aircraft: {id: 1, name: "OK-AUR28"}, flightDurationMinutes: 75},
|
||||||
|
// {id: 5, takeOffLocation: "LKLT", titlePhotoUrl: null, landingLocation: "LKMH", date: "2023-01-46", aircraft: {id: 1, name: "OK-AUR28"}, flightDurationMinutes: 23},
|
||||||
|
// ]
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
deleteItem(id) {
|
||||||
|
const idx = this.items.indexOf(this.items.find(f => f.id === id));
|
||||||
|
this.items.splice(idx, 1);
|
||||||
|
console.log("maxu let ID " + id + " na indexu " + idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
|
export const usePointsOfInterestStore = defineStore('PointsOfInterest', {
|
||||||
|
state: () => ({
|
||||||
|
items: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
getDetail: (state) => (poiId) => {
|
||||||
|
console.log(poiId, state.items, state.items.length);
|
||||||
|
const item = state.items.find(p => {
|
||||||
|
console.log(p, poiId);
|
||||||
|
return p.id === poiId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log('item', item);
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
pointTypeName() {
|
||||||
|
return (typeName) => {
|
||||||
|
const typeToNameMap = {
|
||||||
|
poi: "General POI",
|
||||||
|
airport: "Airport",
|
||||||
|
}
|
||||||
|
return typeToNameMap[typeName] || "Unknown";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
groupByTypes() {
|
||||||
|
const groupedByType = {};
|
||||||
|
for (const item of this.items) {
|
||||||
|
if (!groupedByType[item.type]) groupedByType[item.type] = [];
|
||||||
|
groupedByType[item.type].push(item);
|
||||||
|
}
|
||||||
|
return groupedByType;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
fetchDetail(poiId) {
|
||||||
|
console.log("fetch poi detail");
|
||||||
|
this.items.push({id: poiId, "name": "Zamek " + poiId, "type": "poi", "location": {"lat": 50, "lng": 10}});
|
||||||
|
},
|
||||||
|
fetch() {
|
||||||
|
console.log("fetch poi");
|
||||||
|
this.items = [
|
||||||
|
{id: 1, "name": "LKLT", private: false, "type": "airport", "location": {"lat": 50, "lng": 10}},
|
||||||
|
{id: 2, "name": "Hrad", "type": "poi", "location": {"lat": 50, "lng": 10}},
|
||||||
|
{id: 3, "name": "Zamek", "type": "poi", "location": {"lat": 50, "lng": 10}},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
|
export const useSecondPilotsStore = defineStore('SecondPilots', {
|
||||||
|
state: () => ({
|
||||||
|
items: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
create(formData) {
|
||||||
|
this.items.push(formData);
|
||||||
|
},
|
||||||
|
fetch() {
|
||||||
|
if (this.items.length > 0) return;
|
||||||
|
|
||||||
|
this.$patch({
|
||||||
|
items: [
|
||||||
|
{id: 1, name: "Karel Franta", isInstructor: false},
|
||||||
|
{id: 2, name: "Franta Pepa", isInstructor: false},
|
||||||
|
{id: 3, name: "Instruktor Letajici", isInstructor: true},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {api} from "boot/axios";
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('LoggedUser', {
|
||||||
|
state: () => ({
|
||||||
|
user: {},
|
||||||
|
accessToken: null
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
setAccessToken(token) {
|
||||||
|
console.log(token);
|
||||||
|
if (token) {
|
||||||
|
localStorage.setItem("accessToken", token);
|
||||||
|
}
|
||||||
|
this.accessToken = localStorage.getItem("accessToken");
|
||||||
|
},
|
||||||
|
getAccessToken() {
|
||||||
|
if (!this.accessToken) {
|
||||||
|
this.setAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.accessToken;
|
||||||
|
},
|
||||||
|
|
||||||
|
async setupCredentials() {
|
||||||
|
await api.get("/me").then(({data}) => {
|
||||||
|
this.user = data;
|
||||||
|
}).catch(e => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async register(formData) {
|
||||||
|
return api.post('/registration', formData).then(response => {
|
||||||
|
const registeredUser = response.data;
|
||||||
|
const isSuccess = response.status === 201
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async login(formData) {
|
||||||
|
|
||||||
|
return api.post("/login", formData).then(response => {
|
||||||
|
const data = response.data;
|
||||||
|
console.log("data", data);
|
||||||
|
this.setAccessToken(data.access_token);
|
||||||
|
this.user = data.user;
|
||||||
|
})
|
||||||
|
|
||||||
|
// console.log("login", username, password);
|
||||||
|
// Object.assign(this.user, {
|
||||||
|
// id: 1,
|
||||||
|
// email: "michal@kvacek.cz",
|
||||||
|
// name: "Michal K.",
|
||||||
|
// accessToken: "abcdef",
|
||||||
|
// avatarImageUrl: "https://via.placeholder.com/50x50"
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
Object.assign(this.user, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { store } from 'quasar/wrappers'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 store((/* { ssrContext } */) => {
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
console.log("Pinia index");
|
||||||
|
// You can add Pinia plugins here
|
||||||
|
// pinia.use(SomePiniaPlugin)
|
||||||
|
|
||||||
|
return pinia
|
||||||
|
})
|
||||||
Vendored
+10
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user