Compare commits
	
		
			1 commit
		
	
	
		
			master
			...
			feature/ad
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8dbf99050b | 
| 
		 Before Width: | Height: | Size: 398 KiB After Width: | Height: | Size: 155 KiB  | 
							
								
								
									
										28
									
								
								.drone.yml
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -2,35 +2,21 @@ kind: pipeline
 | 
			
		|||
name: default
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
- name: install-webapp
 | 
			
		||||
  image: node:lts-alpine
 | 
			
		||||
- name: install
 | 
			
		||||
  image: node:18-alpine
 | 
			
		||||
  commands:
 | 
			
		||||
  - cd webapp
 | 
			
		||||
  - corepack pnpm@8.6.6 install
 | 
			
		||||
  - corepack pnpm@7.5.1 install
 | 
			
		||||
 | 
			
		||||
- name: lint-webapp
 | 
			
		||||
  image: node:lts-alpine
 | 
			
		||||
- name: build
 | 
			
		||||
  image: node:18-alpine
 | 
			
		||||
  commands:
 | 
			
		||||
  - cd webapp
 | 
			
		||||
  - corepack pnpm@8.6.6 lint
 | 
			
		||||
 | 
			
		||||
- name: version-webapp
 | 
			
		||||
  image: node:lts-alpine
 | 
			
		||||
  commands:
 | 
			
		||||
  - cd webapp
 | 
			
		||||
  - corepack pnpm@8.6.6 generate-version
 | 
			
		||||
 | 
			
		||||
- name: build-webapp
 | 
			
		||||
  image: node:lts-alpine
 | 
			
		||||
  commands:
 | 
			
		||||
  - cd webapp
 | 
			
		||||
  - corepack pnpm@8.6.6 build
 | 
			
		||||
  - corepack pnpm@7.5.1 build
 | 
			
		||||
 | 
			
		||||
- name: deploy
 | 
			
		||||
  image: plugins/docker
 | 
			
		||||
  settings:
 | 
			
		||||
    registry: registry.cliffbreak.de
 | 
			
		||||
    repo: registry.cliffbreak.de/hgoe-sas
 | 
			
		||||
    repo: registry.cliffbreak.de/kispi-core
 | 
			
		||||
    username:
 | 
			
		||||
      from_secret: docker_username
 | 
			
		||||
    password:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +0,0 @@
 | 
			
		|||
# Paths
 | 
			
		||||
WEBAPP="../webapp/dist"
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ module.exports = {
 | 
			
		|||
    'eslint:recommended',
 | 
			
		||||
    'plugin:vue/vue3-recommended',
 | 
			
		||||
    'plugin:vue-scoped-css/vue3-recommended',
 | 
			
		||||
    'plugin:tailwindcss/recommended',
 | 
			
		||||
  ],
 | 
			
		||||
  parser: 'vue-eslint-parser',
 | 
			
		||||
  env: {
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +52,7 @@ module.exports = {
 | 
			
		|||
      'asyncArrow': 'always',
 | 
			
		||||
    }],
 | 
			
		||||
    'space-in-parens': ['error', 'never'],
 | 
			
		||||
    'spaced-comment': ['error', 'always', { 'markers': ['/'] }],
 | 
			
		||||
    'spaced-comment': ['error', 'always'],
 | 
			
		||||
  },
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -71,8 +70,7 @@ module.exports = {
 | 
			
		|||
      parserOptions: {
 | 
			
		||||
        parser: '@typescript-eslint/parser',
 | 
			
		||||
        sourceType: 'module',
 | 
			
		||||
        project: 'tsconfig.json',
 | 
			
		||||
        tsconfigRootDir: __dirname,
 | 
			
		||||
        'project': 'tsconfig.json',
 | 
			
		||||
        extraFileExtensions: ['.vue'],
 | 
			
		||||
      },
 | 
			
		||||
      plugins: [
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +138,6 @@ module.exports = {
 | 
			
		|||
          'svg': 'always',
 | 
			
		||||
          'math': 'always',
 | 
			
		||||
        }],
 | 
			
		||||
        // 'tailwindcss/no-custom-classname': 'off',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
							
								
								
									
										9
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -20,12 +20,3 @@ dist-ssr
 | 
			
		|||
*.njsproj
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
 | 
			
		||||
# Pocketbase-Data
 | 
			
		||||
pb_data
 | 
			
		||||
 | 
			
		||||
# Environment variables
 | 
			
		||||
.env
 | 
			
		||||
 | 
			
		||||
*.csv
 | 
			
		||||
*.xlsx
 | 
			
		||||
							
								
								
									
										20
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,32 +1,20 @@
 | 
			
		|||
{
 | 
			
		||||
  "cSpell.words": [
 | 
			
		||||
    "appwrite",
 | 
			
		||||
    "camelcase",
 | 
			
		||||
    "checkin",
 | 
			
		||||
    "classname",
 | 
			
		||||
    "cliffbreak",
 | 
			
		||||
    "corepack",
 | 
			
		||||
    "daisyui",
 | 
			
		||||
    "esnext",
 | 
			
		||||
    "Gitea",
 | 
			
		||||
    "github",
 | 
			
		||||
    "godotenv",
 | 
			
		||||
    "heroicons",
 | 
			
		||||
    "hgoe",
 | 
			
		||||
    "HGOE",
 | 
			
		||||
    "middlewares",
 | 
			
		||||
    "kispi",
 | 
			
		||||
    "mopidy",
 | 
			
		||||
    "Mopidy",
 | 
			
		||||
    "pnpm",
 | 
			
		||||
    "pocketbase",
 | 
			
		||||
    "tailwindcss",
 | 
			
		||||
    "typegen",
 | 
			
		||||
    "vite",
 | 
			
		||||
    "vitejs",
 | 
			
		||||
    "vueuse"
 | 
			
		||||
  ],
 | 
			
		||||
  "eslint.format.enable": true,
 | 
			
		||||
  "eslint.workingDirectories": [
 | 
			
		||||
    {
 | 
			
		||||
        "directory": "webapp",
 | 
			
		||||
        "changeProcessCWD": true
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ docs(changelog): update change log to beta.5
 | 
			
		|||
style(webapp): reorder imports
 | 
			
		||||
```
 | 
			
		||||
```
 | 
			
		||||
fix(server): need to depend on latest rxjs and zone.js
 | 
			
		||||
fix(appwrite): need to depend on latest rxjs and zone.js
 | 
			
		||||
 | 
			
		||||
The version in our package.json gets copied to the one we publish, and users need the latest of these.
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +53,7 @@ The following is the list of supported scopes (more to come):
 | 
			
		|||
 | 
			
		||||
* **core** used for changes to the whole project
 | 
			
		||||
* **webapp** used for changes made in the webapp
 | 
			
		||||
* **server** used for changes made in the server
 | 
			
		||||
* **appwrite** used for changes made in the Appwrite cloud functions
 | 
			
		||||
* **changelog**: used for updating the release notes in CHANGELOG.md
 | 
			
		||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										26
									
								
								Dockerfile
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,25 +1,7 @@
 | 
			
		|||
## Build
 | 
			
		||||
FROM golang:1.20-buster as build
 | 
			
		||||
FROM steebchen/nginx-spa:stable
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY dist/ /app
 | 
			
		||||
 | 
			
		||||
COPY server/go.mod ./
 | 
			
		||||
COPY server/go.sum ./
 | 
			
		||||
COPY server/pkg ./pkg
 | 
			
		||||
RUN go mod download
 | 
			
		||||
EXPOSE 80
 | 
			
		||||
 | 
			
		||||
COPY server/*.go ./
 | 
			
		||||
 | 
			
		||||
RUN CGO_ENABLED=1 go build -o /server
 | 
			
		||||
 | 
			
		||||
## Deploy
 | 
			
		||||
FROM gcr.io/distroless/base-debian10
 | 
			
		||||
 | 
			
		||||
WORKDIR /
 | 
			
		||||
 | 
			
		||||
COPY --from=build /server /server
 | 
			
		||||
COPY webapp/dist /webapp
 | 
			
		||||
 | 
			
		||||
EXPOSE 8090
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/server", "serve", "--http=0.0.0.0:8090"]
 | 
			
		||||
CMD ["nginx"]
 | 
			
		||||
							
								
								
									
										62
									
								
								README.md
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,11 +1,11 @@
 | 
			
		|||
<h1 align="center">🔥 Schule als Staat - Hohenlohe Gymnasium Öhringen 🔥</h1>
 | 
			
		||||
<h3 align="center">User administration, user check and customs, banking system, employer portal and an admin portal</h1>
 | 
			
		||||
<h1 align="center">🔥 Core - Kinderspielstadt Öhringen 🔥</h1>
 | 
			
		||||
<h3 align="center">Verwaltung + Bank + Radio = ❤️</h1>
 | 
			
		||||
<p align="center">
 | 
			
		||||
<img height="600px" src=".docs/screenshot.png" alt="screenshot"/>
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
This is our repository containing all required resources to run the "Core" WebApp from the "Hohenlohe Gymnasium Öhringen - Schule als Staat" project.  
 | 
			
		||||
This repository also contains all the required Pocketbase files to run the backend server.
 | 
			
		||||
This is our repository containing all required resources to run the "Core" WebApp from the "Kinderspielstadt Öhringen".  
 | 
			
		||||
This repository also contains all the required Appwrite cloud functions if any exist.
 | 
			
		||||
 | 
			
		||||
## 🚀 Getting Started
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,70 +15,32 @@ See deployment for notes on how to deploy the project on a live system.
 | 
			
		|||
### 🍽️ Prerequisites
 | 
			
		||||
 | 
			
		||||
`NodeJS (including PNPM)` is required to run this project.  
 | 
			
		||||
Also `Go` is required to run the built in server.
 | 
			
		||||
Also a hosted instance of `Appwrite` is required.  
 | 
			
		||||
 | 
			
		||||
### 📦 Installing
 | 
			
		||||
 | 
			
		||||
At first clone this repository to your local machine by using
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
git clone https://git.cliffbreak.de/SimGie/HGOE-SaS.git
 | 
			
		||||
git clone https://git.cliffbreak.de/Kontast/Core.git
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Change to the cloned repository
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
cd HGOE-Sas
 | 
			
		||||
cd Core
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Copy the example environment file
 | 
			
		||||
To install all required packages run
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
cp .env.example .env
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To install all required webapp packages run
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
cd webapp && pnpm install
 | 
			
		||||
pnpm install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To start the webapp in development mode run the following npm script
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
pnpm dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To build the webapp run the following npm script
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
pnpm build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To install all required server packages run
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
cd ../server && go mod download
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To generate unique VAPID keys run the following Go command copy this keys to the `.env` file
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
go run main.go generate-vapid-keys
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To start the server run the following Go command
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
go run main.go serve
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If you do changes to the database schema keep in mind to `Export collections` via the Pocketbase UI.
 | 
			
		||||
Paste the exported JSON into the `pb_schema.json` file and run the following npm script to update the frontend schema types.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
cd ../webapp && pnpm typegen
 | 
			
		||||
pnpm run dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🧑💻 Configure Visual Studio Code
 | 
			
		||||
| 
						 | 
				
			
			@ -96,9 +58,7 @@ Please refer to our **[COMMIT_CONVENTION](COMMIT_CONVENTION.md)**
 | 
			
		|||
* [PNPM](https://pnpm.io/) - Faster alternative to npm for managing dependencies
 | 
			
		||||
* [Vue.js](https://vuejs.org/) - The Frontend Web Framework
 | 
			
		||||
* [Vite](https://vitejs.dev/) - Used Frontend Tooling
 | 
			
		||||
* [Go](https://go.dev/) - The Backend Programming Language
 | 
			
		||||
* [Pocketbase](https://pocketbase.io/) - The Backend Framework
 | 
			
		||||
 | 
			
		||||
* [Appwrite](https://docs.mongodb.com/) - The hosted Backend used for this application
 | 
			
		||||
 | 
			
		||||
## 🤵 Authors
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,10 +8,10 @@
 | 
			
		|||
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
 | 
			
		||||
  <link rel="manifest" href="/site.webmanifest">
 | 
			
		||||
  <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
 | 
			
		||||
  <meta name="msapplication-TileColor" content="#da532c">
 | 
			
		||||
  <meta name="msapplication-TileColor" content="#ffc40d">
 | 
			
		||||
  <meta name="theme-color" content="#ffffff">
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
  <title>Schule als Staat | Hohenlohe Gymnasium Öhringen</title>
 | 
			
		||||
  <title>Kinderspielstadt Öhringen</title>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
							
								
								
									
										37
									
								
								package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "kispi-core",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "0.0.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build": "vue-tsc --noEmit && vite build",
 | 
			
		||||
    "lint": "eslint -c .eslintrc.js src/",
 | 
			
		||||
    "preview": "vite preview"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@heroicons/vue": "^1.0.6",
 | 
			
		||||
    "@vueuse/core": "^8.9.2",
 | 
			
		||||
    "daisyui": "^2.19.0",
 | 
			
		||||
    "events": "^3.3.0",
 | 
			
		||||
    "mopidy": "^1.3.0",
 | 
			
		||||
    "vue": "^3.2.37",
 | 
			
		||||
    "vue-router": "4"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@tailwindcss/typography": "^0.5.3",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.30.6",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.30.6",
 | 
			
		||||
    "@vitejs/plugin-vue": "^2.3.3",
 | 
			
		||||
    "autoprefixer": "^10.4.7",
 | 
			
		||||
    "eslint": "^8.19.0",
 | 
			
		||||
    "eslint-plugin-vue": "^9.2.0",
 | 
			
		||||
    "eslint-plugin-vue-scoped-css": "^2.2.0",
 | 
			
		||||
    "postcss": "^8.4.14",
 | 
			
		||||
    "sass": "^1.53.0",
 | 
			
		||||
    "tailwindcss": "^3.1.6",
 | 
			
		||||
    "typescript": "^4.7.4",
 | 
			
		||||
    "vite": "^2.9.14",
 | 
			
		||||
    "vue-eslint-parser": "^9.0.3",
 | 
			
		||||
    "vue-tsc": "^0.38.5"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2022
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								public/android-chrome-192x192.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 11 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/android-chrome-512x512.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 29 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-touch-icon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.3 KiB  | 
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
    <msapplication>
 | 
			
		||||
        <tile>
 | 
			
		||||
            <square150x150logo src="/mstile-150x150.png"/>
 | 
			
		||||
            <TileColor>#da532c</TileColor>
 | 
			
		||||
            <TileColor>#b91d47</TileColor>
 | 
			
		||||
        </tile>
 | 
			
		||||
    </msapplication>
 | 
			
		||||
</browserconfig>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								public/favicon-16x16.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-32x32.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/mstile-150x150.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.5 KiB  | 
							
								
								
									
										162
									
								
								public/safari-pinned-tab.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,162 @@
 | 
			
		|||
<?xml version="1.0" standalone="no"?>
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
 | 
			
		||||
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
 | 
			
		||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
 width="1181.000000pt" height="1181.000000pt" viewBox="0 0 1181.000000 1181.000000"
 | 
			
		||||
 preserveAspectRatio="xMidYMid meet">
 | 
			
		||||
<metadata>
 | 
			
		||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
 | 
			
		||||
</metadata>
 | 
			
		||||
<g transform="translate(0.000000,1181.000000) scale(0.100000,-0.100000)"
 | 
			
		||||
fill="#000000" stroke="none">
 | 
			
		||||
<path d="M8929 10966 c-19 -7 -39 -24 -49 -42 -15 -30 -16 -266 -16 -3380 l1
 | 
			
		||||
-3349 310 0 310 0 1 3335 c1 1959 -2 3350 -8 3372 -16 67 -39 73 -295 74 -133
 | 
			
		||||
0 -235 -4 -254 -10z"/>
 | 
			
		||||
<path d="M414 9835 l0 -855 81 0 80 0 0 445 c0 245 3 445 6 445 5 0 541 -544
 | 
			
		||||
769 -781 l105 -109 117 0 117 0 -156 158 c-85 86 -290 293 -454 460 -164 166
 | 
			
		||||
-299 306 -299 310 0 4 42 44 93 89 50 45 112 101 137 124 50 46 175 159 211
 | 
			
		||||
190 13 11 42 37 64 58 22 20 111 101 198 179 l157 142 -112 0 -113 -1 -100
 | 
			
		||||
-91 c-55 -50 -140 -128 -189 -172 -49 -45 -100 -90 -112 -101 -12 -11 -54 -49
 | 
			
		||||
-94 -85 -40 -36 -82 -74 -94 -85 -13 -11 -60 -55 -107 -97 -110 -100 -131
 | 
			
		||||
-118 -138 -118 -3 0 -6 168 -6 373 l0 372 -80 3 -81 3 0 -856z"/>
 | 
			
		||||
<path d="M1825 9835 l0 -855 80 0 80 0 0 855 0 855 -80 0 -80 0 0 -855z"/>
 | 
			
		||||
<path d="M2394 9835 l0 -855 81 0 80 0 0 745 c0 410 2 745 5 745 3 -1 18 -19
 | 
			
		||||
33 -41 16 -23 132 -190 259 -373 127 -182 348 -499 490 -703 l260 -373 104 0
 | 
			
		||||
104 0 0 855 0 855 -80 0 -80 0 -1 -37 c0 -21 0 -356 -1 -745 0 -390 -3 -708
 | 
			
		||||
-7 -708 -3 0 -55 72 -116 160 -113 165 -169 244 -643 925 l-278 400 -105 3
 | 
			
		||||
-105 3 0 -856z"/>
 | 
			
		||||
<path d="M4192 9835 l-1 -857 362 5 c199 2 373 7 387 10 14 4 39 9 55 12 216
 | 
			
		||||
42 424 183 527 359 202 343 155 818 -107 1079 -71 72 -126 110 -228 157 -164
 | 
			
		||||
76 -252 88 -664 89 l-331 2 0 -856z m678 689 c316 -48 523 -219 596 -489 20
 | 
			
		||||
-74 26 -258 12 -343 -24 -143 -84 -257 -184 -354 -102 -98 -214 -151 -419
 | 
			
		||||
-194 -16 -3 -141 -8 -276 -11 l-245 -5 -1 706 0 707 216 -2 c119 -1 254 -8
 | 
			
		||||
301 -15z"/>
 | 
			
		||||
<path d="M5958 10683 c0 -5 -2 -389 -3 -855 l-1 -848 542 0 542 0 3 43 c2 23
 | 
			
		||||
3 57 1 74 l-3 33 -462 2 -462 3 0 328 0 327 417 0 417 0 -1 73 -1 72 -416 5
 | 
			
		||||
-416 5 -1 285 c0 157 2 291 4 298 3 9 101 12 443 12 344 0 439 3 440 13 1 15
 | 
			
		||||
1 119 0 130 -1 4 -235 7 -521 7 -286 0 -521 -3 -522 -7z"/>
 | 
			
		||||
<path d="M7325 9836 l0 -856 80 0 80 0 0 403 0 402 149 0 150 0 71 -120 c300
 | 
			
		||||
-502 356 -595 378 -635 l25 -45 91 -3 c50 -1 91 -1 91 2 0 2 -20 37 -45 78
 | 
			
		||||
-24 40 -57 96 -74 123 -16 28 -106 176 -200 330 l-170 280 86 22 c191 51 305
 | 
			
		||||
157 337 315 26 126 6 254 -54 346 -63 98 -135 146 -275 183 -72 20 -113 22
 | 
			
		||||
-400 26 l-320 5 0 -856z m630 692 c184 -39 289 -169 265 -325 -17 -106 -71
 | 
			
		||||
-177 -166 -219 -82 -36 -162 -45 -374 -42 l-195 3 0 285 c-1 157 1 291 3 298
 | 
			
		||||
6 16 391 16 467 0z"/>
 | 
			
		||||
<path d="M9650 8955 l0 -1601 930 1 930 0 0 230 0 230 -150 -1 c-119 -1 -150
 | 
			
		||||
2 -151 13 0 7 -1 105 -1 218 0 113 1 210 1 215 1 6 58 10 151 10 l150 0 0 228
 | 
			
		||||
0 229 -137 -2 c-76 0 -145 1 -153 3 -13 3 -15 35 -13 225 1 122 2 224 2 227 1
 | 
			
		||||
3 69 5 151 5 l150 -1 0 228 0 228 -32 1 c-18 0 -87 1 -153 2 l-119 2 -1 215
 | 
			
		||||
c0 118 1 221 3 228 3 9 43 12 153 12 l149 0 0 228 0 229 -930 0 -930 0 0
 | 
			
		||||
-1602z m991 476 c1 -3 2 -60 3 -126 l2 -120 126 0 127 0 -2 -230 -2 -230 -122
 | 
			
		||||
1 c-82 0 -123 -3 -124 -10 -1 -6 -2 -63 -3 -126 l-1 -115 -227 -1 c-162 -1
 | 
			
		||||
-228 2 -228 10 -1 6 -2 63 -3 126 l-2 115 -123 0 c-67 0 -123 2 -123 5 -1 3
 | 
			
		||||
-2 105 -3 227 -1 148 2 224 9 226 5 2 57 3 115 2 58 -1 111 0 118 3 9 3 13 21
 | 
			
		||||
12 56 -1 28 -1 83 -1 123 l1 71 225 0 c124 -1 226 -4 226 -7z"/>
 | 
			
		||||
<path d="M8316 8691 c-26 -10 -30 -45 -8 -64 16 -15 20 -15 40 -1 24 17 29 43
 | 
			
		||||
10 62 -13 13 -15 14 -42 3z"/>
 | 
			
		||||
<path d="M7785 8654 c3 -35 7 -37 120 -78 55 -20 165 -60 244 -89 136 -51 142
 | 
			
		||||
-55 115 -64 -130 -47 -425 -157 -451 -169 -30 -14 -35 -25 -24 -61 1 -2 31 9
 | 
			
		||||
69 23 37 14 74 27 82 29 23 6 412 154 417 158 10 11 10 47 0 55 -7 5 -41 19
 | 
			
		||||
-77 32 -36 13 -83 31 -105 40 -37 16 -104 41 -317 120 l-76 29 3 -25z"/>
 | 
			
		||||
<path d="M995 8394 c-11 -2 -45 -9 -75 -15 -246 -49 -425 -214 -469 -436 -14
 | 
			
		||||
-70 -7 -225 13 -294 38 -128 130 -235 264 -307 42 -22 177 -75 300 -116 239
 | 
			
		||||
-82 281 -100 355 -153 89 -66 130 -150 129 -273 -1 -110 -41 -200 -125 -277
 | 
			
		||||
-92 -84 -209 -123 -360 -119 -191 6 -333 78 -427 216 -13 19 -25 37 -26 38 -2
 | 
			
		||||
3 -159 -96 -183 -114 -13 -10 91 -124 162 -177 78 -59 228 -118 341 -135 94
 | 
			
		||||
-13 296 -7 366 12 316 85 490 348 441 666 -27 172 -122 291 -303 379 -57 28
 | 
			
		||||
-146 62 -198 77 -324 89 -478 175 -529 294 -50 116 -44 257 15 357 72 124 202
 | 
			
		||||
190 389 198 165 7 271 -31 375 -135 33 -33 62 -60 65 -60 3 0 41 26 85 59 l80
 | 
			
		||||
59 -58 60 c-87 90 -181 145 -307 178 -58 15 -279 28 -320 18z"/>
 | 
			
		||||
<path d="M2074 7311 l0 -1041 98 0 98 0 0 495 0 495 263 0 c276 1 307 3 399
 | 
			
		||||
26 268 68 422 267 412 536 -6 164 -53 271 -163 371 -76 68 -160 105 -326 143
 | 
			
		||||
-16 4 -199 9 -405 11 l-376 4 0 -1040z m751 844 c98 -19 152 -44 216 -103 82
 | 
			
		||||
-75 104 -129 103 -257 0 -88 -3 -106 -26 -150 -34 -65 -89 -119 -152 -150 -93
 | 
			
		||||
-45 -169 -55 -443 -55 l-253 0 0 365 0 366 243 -1 c158 -1 266 -6 312 -15z"/>
 | 
			
		||||
<path d="M3685 7313 l0 -1038 98 -3 97 -3 0 1041 0 1040 -98 0 -97 0 0 -1037z"/>
 | 
			
		||||
<path d="M4374 7310 l1 -1040 660 0 660 0 0 90 1 90 -563 0 -563 0 0 405 0
 | 
			
		||||
405 505 0 505 0 0 90 0 90 -505 0 -505 0 0 365 0 365 333 0 c182 0 425 0 540
 | 
			
		||||
0 l207 0 0 90 0 90 -638 0 -638 0 0 -1040z"/>
 | 
			
		||||
<path d="M6045 7313 l0 -1038 583 -3 582 -2 0 90 0 90 -485 0 -485 0 0 950 0
 | 
			
		||||
950 -98 0 -97 0 0 -1037z"/>
 | 
			
		||||
<path d="M8307 8138 c-29 -22 -10 -68 28 -68 35 0 46 37 19 64 -19 19 -25 20
 | 
			
		||||
-47 4z"/>
 | 
			
		||||
<path d="M8319 7975 c-1 -3 -2 -74 -3 -157 l-1 -153 -112 0 -113 -1 0 143 0
 | 
			
		||||
143 -25 0 -24 0 -3 -142 -3 -143 -97 0 c-59 0 -99 4 -99 10 -1 5 -3 69 -4 140
 | 
			
		||||
-3 163 -2 154 -22 150 -10 -2 -20 -4 -23 -4 -3 -1 -5 -80 -5 -176 l0 -175 290
 | 
			
		||||
0 290 0 -1 178 c-1 97 -2 180 -3 185 -1 8 -40 10 -42 2z"/>
 | 
			
		||||
<path d="M7789 7267 c0 -7 -2 -20 -4 -29 -3 -16 15 -17 213 -17 120 0 235 0
 | 
			
		||||
257 0 l40 -1 -50 -33 c-120 -80 -451 -312 -457 -321 -3 -5 -5 -21 -4 -35 l3
 | 
			
		||||
-26 289 0 c308 0 293 -2 285 43 0 4 -113 7 -251 7 -184 0 -246 3 -237 11 6 6
 | 
			
		||||
50 37 97 69 399 273 397 271 395 307 l-2 33 -286 2 c-211 1 -287 -2 -288 -10z"/>
 | 
			
		||||
<path d="M8318 6703 c-3 -4 -4 -74 -4 -155 1 -94 -2 -148 -9 -149 -5 -1 -56
 | 
			
		||||
-2 -112 -3 l-103 -1 0 142 0 143 -25 0 -25 0 0 -143 0 -143 -97 2 c-54 1 -100
 | 
			
		||||
2 -103 3 -3 0 -5 64 -5 141 0 78 -2 145 -5 150 -3 5 -14 8 -25 6 -19 -3 -20
 | 
			
		||||
-12 -20 -170 0 -92 2 -172 6 -177 3 -6 121 -8 290 -7 l284 3 0 180 c0 177 0
 | 
			
		||||
180 -22 183 -11 2 -23 0 -25 -5z"/>
 | 
			
		||||
<path d="M8055 6188 c-3 -7 -4 -51 -2 -98 2 -83 3 -85 26 -85 23 0 24 3 23 67
 | 
			
		||||
l-1 66 74 4 c114 5 127 0 141 -50 19 -65 12 -179 -14 -229 -88 -168 -369 -164
 | 
			
		||||
-457 7 -22 42 -28 144 -11 191 7 21 23 49 35 65 l22 28 -21 20 c-21 21 -21 21
 | 
			
		||||
-36 1 -42 -55 -57 -101 -60 -181 -3 -71 0 -87 25 -140 38 -80 112 -144 190
 | 
			
		||||
-162 60 -14 165 -6 219 18 70 30 133 101 157 175 22 70 16 186 -13 261 l-20
 | 
			
		||||
49 -136 3 c-103 2 -138 -1 -141 -10z"/>
 | 
			
		||||
<path d="M7786 5563 c-16 -40 -4 -42 250 -41 135 0 247 -2 249 -3 1 -2 -65
 | 
			
		||||
-50 -149 -107 -330 -225 -349 -239 -352 -264 -5 -49 -5 -48 296 -46 273 3 283
 | 
			
		||||
4 286 23 2 11 -1 22 -6 25 -5 3 -119 5 -254 5 -135 0 -246 1 -246 2 0 2 242
 | 
			
		||||
170 442 307 63 43 67 50 59 105 0 5 -123 9 -286 8 -228 0 -285 -3 -289 -14z"/>
 | 
			
		||||
<path d="M860 5324 c-113 -25 -231 -91 -297 -164 -33 -36 -91 -158 -98 -205
 | 
			
		||||
-18 -121 -2 -236 47 -328 63 -118 165 -182 431 -271 268 -90 338 -131 389
 | 
			
		||||
-229 32 -62 31 -193 -2 -262 -113 -232 -488 -277 -685 -81 -28 28 -58 61 -66
 | 
			
		||||
74 l-14 22 -77 -51 -78 -51 20 -28 c64 -89 191 -173 320 -210 100 -29 278 -36
 | 
			
		||||
375 -14 238 54 395 234 397 458 1 102 -9 153 -42 221 -59 123 -176 203 -390
 | 
			
		||||
268 -300 92 -385 140 -441 249 -31 61 -34 183 -6 256 50 131 194 211 375 209
 | 
			
		||||
126 -1 208 -35 293 -121 l46 -47 63 44 c35 24 65 48 67 53 6 17 -64 90 -120
 | 
			
		||||
128 -90 59 -193 88 -327 92 -77 2 -136 -2 -180 -12z"/>
 | 
			
		||||
<path d="M1620 5225 l0 -75 298 -2 297 -3 1 -795 0 -795 82 -3 82 -3 0 801 0
 | 
			
		||||
800 300 0 300 0 0 75 0 75 -680 0 -680 0 0 -75z"/>
 | 
			
		||||
<path d="M3381 4923 c-91 -208 -175 -400 -187 -428 -12 -27 -108 -248 -214
 | 
			
		||||
-490 -105 -242 -194 -443 -197 -447 -2 -5 35 -8 84 -8 l88 0 97 228 96 227
 | 
			
		||||
465 3 464 2 94 -230 94 -230 93 0 92 0 -16 38 c-8 20 -38 91 -66 157 -48 116
 | 
			
		||||
-70 168 -89 210 -5 11 -46 110 -92 220 -46 110 -171 408 -278 662 l-194 463
 | 
			
		||||
-85 0 -85 0 -164 -377z m444 -298 c43 -104 102 -248 132 -318 29 -71 53 -133
 | 
			
		||||
53 -138 0 -5 -160 -9 -395 -9 -217 0 -395 3 -395 6 0 6 292 695 331 781 10 23
 | 
			
		||||
19 44 19 46 0 3 12 31 26 63 l26 59 63 -150 c34 -82 97 -235 140 -340z"/>
 | 
			
		||||
<path d="M4619 5278 c-2 -12 -4 -405 -3 -873 l1 -850 344 0 c401 0 447 4 589
 | 
			
		||||
53 130 45 229 107 322 204 170 174 255 455 224 733 -22 195 -127 412 -253 526
 | 
			
		||||
-39 35 -157 119 -167 119 -3 0 -29 11 -58 25 -47 21 -140 50 -233 71 -16 4
 | 
			
		||||
-195 9 -396 11 l-366 5 -4 -24z m669 -140 c110 -17 188 -40 277 -83 150 -73
 | 
			
		||||
244 -166 310 -310 45 -96 62 -183 63 -311 2 -326 -140 -548 -426 -664 -127
 | 
			
		||||
-51 -204 -62 -479 -68 l-253 -4 0 727 0 726 226 -2 c124 -1 251 -6 282 -11z"/>
 | 
			
		||||
<path d="M6200 5225 l0 -75 298 0 298 0 0 -797 0 -798 82 -3 82 -3 0 158 c0
 | 
			
		||||
87 0 447 0 801 l0 642 300 0 300 0 0 75 0 75 -680 0 -680 0 0 -75z"/>
 | 
			
		||||
<path d="M7785 4938 l-1 -28 292 0 291 0 -2 27 -1 28 -290 0 -289 0 0 -27z"/>
 | 
			
		||||
<path d="M8235 4740 c-71 -43 -136 -79 -142 -80 -7 0 -13 8 -13 18 0 59 -69
 | 
			
		||||
122 -133 122 -75 0 -130 -39 -148 -104 -12 -41 -20 -220 -11 -243 3 -10 71
 | 
			
		||||
-13 293 -13 269 0 289 1 286 18 -5 35 -15 38 -146 37 l-131 -1 0 51 0 51 123
 | 
			
		||||
72 c67 40 128 76 136 81 10 6 26 71 17 71 0 0 -59 -36 -131 -80z m-243 -11
 | 
			
		||||
c32 -17 49 -76 46 -159 l-3 -75 -90 -1 c-49 0 -95 2 -101 3 -14 5 -11 147 4
 | 
			
		||||
183 24 61 84 81 144 49z"/>
 | 
			
		||||
<path d="M7790 4300 c-14 -9 -5 -43 13 -49 9 -3 66 -6 127 -5 l110 1 -2 -161
 | 
			
		||||
-3 -161 -115 -1 c-146 -2 -136 0 -136 -29 l0 -25 291 0 292 0 -2 28 -1 27
 | 
			
		||||
-137 0 -137 -1 0 162 0 161 128 1 c70 0 132 1 138 1 5 1 10 12 10 26 0 24 -3
 | 
			
		||||
25 -60 26 -365 3 -509 3 -516 -1z"/>
 | 
			
		||||
<path d="M8620 4053 c-58 -29 -60 -36 -60 -313 0 -184 4 -262 13 -280 27 -55
 | 
			
		||||
26 -55 608 -55 497 0 537 2 563 18 15 10 31 29 36 43 13 33 13 505 0 538 -5
 | 
			
		||||
14 -21 33 -36 43 -25 16 -67 18 -564 18 -396 0 -542 -3 -560 -12z"/>
 | 
			
		||||
<path d="M7994 3760 c-32 -9 -70 -22 -84 -30 -39 -21 -91 -81 -114 -130 -23
 | 
			
		||||
-52 -31 -176 -14 -218 31 -76 41 -92 79 -127 61 -55 120 -78 204 -78 83 -1
 | 
			
		||||
109 4 162 31 108 56 164 173 148 312 -21 177 -200 290 -381 240z m202 -70 c93
 | 
			
		||||
-46 141 -136 131 -246 -13 -137 -126 -223 -279 -212 -88 7 -150 43 -193 112
 | 
			
		||||
-26 44 -30 58 -30 125 0 88 16 129 69 180 71 69 206 87 302 41z"/>
 | 
			
		||||
<path d="M7646 3571 c-17 -18 -17 -20 3 -40 26 -26 33 -26 53 -3 11 12 13 24
 | 
			
		||||
7 40 -10 27 -40 29 -63 3z"/>
 | 
			
		||||
<path d="M7652 3418 c-16 -16 -15 -43 3 -58 32 -26 78 25 49 54 -19 19 -36 20
 | 
			
		||||
-52 4z"/>
 | 
			
		||||
<path d="M8719 3271 c-22 -7 -1217 -1209 -1233 -1238 -14 -28 -30 -93 -30
 | 
			
		||||
-123 1 -27 16 -84 32 -115 8 -16 358 -373 777 -792 579 -580 772 -767 806
 | 
			
		||||
-782 59 -27 149 -27 208 -1 34 16 230 206 807 783 628 628 767 771 785 812 27
 | 
			
		||||
60 27 140 0 200 -14 32 -172 196 -628 651 l-608 609 -452 0 c-249 0 -458 -2
 | 
			
		||||
-464 -4z m892 -895 c239 -238 441 -441 448 -450 11 -13 -44 -72 -435 -463
 | 
			
		||||
l-449 -448 -442 442 c-244 244 -444 448 -445 454 -3 9 868 894 883 898 3 0
 | 
			
		||||
201 -194 440 -433z"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
    "name": "",
 | 
			
		||||
    "short_name": "",
 | 
			
		||||
    "name": "Kinderspielstadt Öhringen",
 | 
			
		||||
    "short_name": "KiSpi Öhringen",
 | 
			
		||||
    "icons": [
 | 
			
		||||
        {
 | 
			
		||||
            "src": "/android-chrome-192x192.png",
 | 
			
		||||
| 
						 | 
				
			
			@ -1,99 +0,0 @@
 | 
			
		|||
module cliffbreak.de/hgoe-sas-server
 | 
			
		||||
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/joho/godotenv v1.5.1
 | 
			
		||||
	github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198
 | 
			
		||||
	github.com/pocketbase/pocketbase v0.16.8
 | 
			
		||||
	github.com/pterm/pterm v0.12.62
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	atomicgo.dev/cursor v0.1.2 // indirect
 | 
			
		||||
	atomicgo.dev/keyboard v0.2.9 // indirect
 | 
			
		||||
	atomicgo.dev/schedule v0.0.2 // indirect
 | 
			
		||||
	github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
 | 
			
		||||
	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go v1.44.299 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2 v1.18.1 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/config v1.18.27 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/credentials v1.13.26 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.71 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/s3 v1.36.0 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 // indirect
 | 
			
		||||
	github.com/aws/smithy-go v1.13.5 // indirect
 | 
			
		||||
	github.com/containerd/console v1.0.3 // indirect
 | 
			
		||||
	github.com/disintegration/imaging v1.6.2 // indirect
 | 
			
		||||
	github.com/domodwyer/mailyak/v3 v3.6.0 // indirect
 | 
			
		||||
	github.com/dustin/go-humanize v1.0.1 // indirect
 | 
			
		||||
	github.com/fatih/color v1.15.0 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 | 
			
		||||
	github.com/ganigeorgiev/fexpr v0.3.0 // indirect
 | 
			
		||||
	github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
 | 
			
		||||
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.3 // indirect
 | 
			
		||||
	github.com/google/uuid v1.3.0 // indirect
 | 
			
		||||
	github.com/google/wire v0.5.0 // indirect
 | 
			
		||||
	github.com/googleapis/gax-go/v2 v2.12.0 // indirect
 | 
			
		||||
	github.com/gookit/color v1.5.3 // indirect
 | 
			
		||||
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 | 
			
		||||
	github.com/jmespath/go-jmespath v0.4.0 // indirect
 | 
			
		||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 | 
			
		||||
	github.com/lithammer/fuzzysearch v1.1.8 // indirect
 | 
			
		||||
	github.com/mattn/go-colorable v0.1.13 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.19 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.14 // indirect
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.17 // indirect
 | 
			
		||||
	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
 | 
			
		||||
	github.com/pocketbase/dbx v1.10.0 // indirect
 | 
			
		||||
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 | 
			
		||||
	github.com/rivo/uniseg v0.4.4 // indirect
 | 
			
		||||
	github.com/spf13/cast v1.5.1 // indirect
 | 
			
		||||
	github.com/spf13/cobra v1.7.0 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
	github.com/valyala/bytebufferpool v1.0.0 // indirect
 | 
			
		||||
	github.com/valyala/fasttemplate v1.2.2 // indirect
 | 
			
		||||
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
 | 
			
		||||
	go.opencensus.io v0.24.0 // indirect
 | 
			
		||||
	gocloud.dev v0.30.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.11.0 // indirect
 | 
			
		||||
	golang.org/x/image v0.9.0 // indirect
 | 
			
		||||
	golang.org/x/mod v0.12.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.12.0 // indirect
 | 
			
		||||
	golang.org/x/oauth2 v0.10.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.10.0 // indirect
 | 
			
		||||
	golang.org/x/term v0.10.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.11.0 // indirect
 | 
			
		||||
	golang.org/x/time v0.3.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.11.0 // indirect
 | 
			
		||||
	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 | 
			
		||||
	google.golang.org/api v0.130.0 // indirect
 | 
			
		||||
	google.golang.org/appengine v1.6.7 // indirect
 | 
			
		||||
	google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
 | 
			
		||||
	google.golang.org/grpc v1.56.2 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.31.0 // indirect
 | 
			
		||||
	lukechampine.com/uint128 v1.3.0 // indirect
 | 
			
		||||
	modernc.org/cc/v3 v3.41.0 // indirect
 | 
			
		||||
	modernc.org/ccgo/v3 v3.16.14 // indirect
 | 
			
		||||
	modernc.org/libc v1.24.1 // indirect
 | 
			
		||||
	modernc.org/mathutil v1.6.0 // indirect
 | 
			
		||||
	modernc.org/memory v1.6.0 // indirect
 | 
			
		||||
	modernc.org/opt v0.1.3 // indirect
 | 
			
		||||
	modernc.org/sqlite v1.24.0 // indirect
 | 
			
		||||
	modernc.org/strutil v1.1.3 // indirect
 | 
			
		||||
	modernc.org/token v1.1.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										3398
									
								
								server/go.sum
									
										
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,40 +0,0 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cliffbreak.de/hgoe-sas-server/pkg/middlewares"
 | 
			
		||||
	"cliffbreak.de/hgoe-sas-server/pkg/utils"
 | 
			
		||||
 | 
			
		||||
	"github.com/labstack/echo/v5"
 | 
			
		||||
	"github.com/pocketbase/pocketbase"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pterm/pterm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	pterm.Info.Println("Loading environment variables...")
 | 
			
		||||
	utils.LoadEnv()
 | 
			
		||||
	pterm.Info.Println("Starting PocketBase server...")
 | 
			
		||||
	app := pocketbase.New()
 | 
			
		||||
 | 
			
		||||
	app.OnBeforeServe().Add(middlewares.ServeSPA(utils.GetEnv("WEBAPP", "./webapp")))
 | 
			
		||||
 | 
			
		||||
	app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
 | 
			
		||||
		e.Router.AddRoute(echo.Route{
 | 
			
		||||
			Method: http.MethodGet,
 | 
			
		||||
			Path:   "/api/time",
 | 
			
		||||
			Handler: func(c echo.Context) error {
 | 
			
		||||
				return c.String(http.StatusOK, time.Now().Format(time.RFC3339))
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if err := app.Start(); err != nil {
 | 
			
		||||
		pterm.Fatal.Println(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,458 +0,0 @@
 | 
			
		|||
[
 | 
			
		||||
  {
 | 
			
		||||
    "id": "s854d2w72fvyl54",
 | 
			
		||||
    "name": "companies",
 | 
			
		||||
    "type": "auth",
 | 
			
		||||
    "system": false,
 | 
			
		||||
    "schema": [
 | 
			
		||||
      {
 | 
			
		||||
        "id": "h5ogbj93",
 | 
			
		||||
        "name": "accountNumber",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "cvcgnf4x",
 | 
			
		||||
        "name": "name",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "ujqd3vik",
 | 
			
		||||
        "name": "earlyShiftPayed",
 | 
			
		||||
        "type": "date",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": "",
 | 
			
		||||
          "max": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "t1kakf3f",
 | 
			
		||||
        "name": "lateShiftPayed",
 | 
			
		||||
        "type": "date",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": "",
 | 
			
		||||
          "max": ""
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "indexes": [
 | 
			
		||||
      "CREATE INDEX `idx_eX1Vqqz` ON `companies` (`accountNumber`)"
 | 
			
		||||
    ],
 | 
			
		||||
    "listRule": "",
 | 
			
		||||
    "viewRule": null,
 | 
			
		||||
    "createRule": null,
 | 
			
		||||
    "updateRule": "(@request.data.username = null && @request.data.email = null && @request.data.accountNumber = null && @request.data.created = null && @request.data.updated = null && @request.data.name = null && @request.data.emailVisibility = null && @request.data.verified = null) && id = @request.auth.id",
 | 
			
		||||
    "deleteRule": null,
 | 
			
		||||
    "options": {
 | 
			
		||||
      "allowEmailAuth": true,
 | 
			
		||||
      "allowOAuth2Auth": true,
 | 
			
		||||
      "allowUsernameAuth": true,
 | 
			
		||||
      "exceptEmailDomains": null,
 | 
			
		||||
      "manageRule": null,
 | 
			
		||||
      "minPasswordLength": 8,
 | 
			
		||||
      "onlyEmailDomains": null,
 | 
			
		||||
      "requireEmail": false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "id": "c3zz98wpbn7m6zw",
 | 
			
		||||
    "name": "accountsList",
 | 
			
		||||
    "type": "view",
 | 
			
		||||
    "system": false,
 | 
			
		||||
    "schema": [
 | 
			
		||||
      {
 | 
			
		||||
        "id": "ne8rkvlr",
 | 
			
		||||
        "name": "accountNumber",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "paqieqwu",
 | 
			
		||||
        "name": "name",
 | 
			
		||||
        "type": "json",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {}
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "7sij0xcm",
 | 
			
		||||
        "name": "grade",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "txpycyit",
 | 
			
		||||
        "name": "lastCheckIn",
 | 
			
		||||
        "type": "date",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": "",
 | 
			
		||||
          "max": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "og7zs0ox",
 | 
			
		||||
        "name": "balance",
 | 
			
		||||
        "type": "json",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {}
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "indexes": [],
 | 
			
		||||
    "listRule": null,
 | 
			
		||||
    "viewRule": null,
 | 
			
		||||
    "createRule": null,
 | 
			
		||||
    "updateRule": null,
 | 
			
		||||
    "deleteRule": null,
 | 
			
		||||
    "options": {
 | 
			
		||||
      "query": "SELECT\n  a.id AS id,\n  a.accountNumber AS accountNumber,\n  (a.firstName || ' ' || a.lastName) AS name,\n  a.grade AS grade,\n  a.lastCheckIn AS lastCheckIn,\n  SUM(t.amount) AS balance\nFROM\n  accounts AS a\nLEFT JOIN\n  transactions AS t\nON\n  a.id = t.account\nGROUP BY\n  a.id\nORDER BY\n  a.grade, a.lastName"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "id": "t4gewf713jqhz3i",
 | 
			
		||||
    "name": "settings",
 | 
			
		||||
    "type": "base",
 | 
			
		||||
    "system": false,
 | 
			
		||||
    "schema": [
 | 
			
		||||
      {
 | 
			
		||||
        "id": "z7pmr7wm",
 | 
			
		||||
        "name": "minWage",
 | 
			
		||||
        "type": "number",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": 0,
 | 
			
		||||
          "max": null
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "7wzv9qum",
 | 
			
		||||
        "name": "incomeTax",
 | 
			
		||||
        "type": "number",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": 0,
 | 
			
		||||
          "max": null
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "3pgjcfai",
 | 
			
		||||
        "name": "incomeTaxRecipient",
 | 
			
		||||
        "type": "relation",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "collectionId": "s854d2w72fvyl54",
 | 
			
		||||
          "cascadeDelete": false,
 | 
			
		||||
          "minSelect": null,
 | 
			
		||||
          "maxSelect": 1,
 | 
			
		||||
          "displayFields": []
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "dawtvakx",
 | 
			
		||||
        "name": "maxWage",
 | 
			
		||||
        "type": "number",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": 0,
 | 
			
		||||
          "max": null
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "ftzmu1na",
 | 
			
		||||
        "name": "radioUrl",
 | 
			
		||||
        "type": "url",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "exceptDomains": [],
 | 
			
		||||
          "onlyDomains": []
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "indexes": [],
 | 
			
		||||
    "listRule": "",
 | 
			
		||||
    "viewRule": "",
 | 
			
		||||
    "createRule": null,
 | 
			
		||||
    "updateRule": null,
 | 
			
		||||
    "deleteRule": null,
 | 
			
		||||
    "options": {}
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "id": "74ftooxenpeq14b",
 | 
			
		||||
    "name": "accounts",
 | 
			
		||||
    "type": "base",
 | 
			
		||||
    "system": false,
 | 
			
		||||
    "schema": [
 | 
			
		||||
      {
 | 
			
		||||
        "id": "as1gvc1r",
 | 
			
		||||
        "name": "accountNumber",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "vxeq20lk",
 | 
			
		||||
        "name": "firstName",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "bsinpdk7",
 | 
			
		||||
        "name": "lastName",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "4lw6gmtc",
 | 
			
		||||
        "name": "grade",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "syfivtki",
 | 
			
		||||
        "name": "lastCheckIn",
 | 
			
		||||
        "type": "date",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": "",
 | 
			
		||||
          "max": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "gwrzizpu",
 | 
			
		||||
        "name": "company",
 | 
			
		||||
        "type": "relation",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "collectionId": "s854d2w72fvyl54",
 | 
			
		||||
          "cascadeDelete": false,
 | 
			
		||||
          "minSelect": null,
 | 
			
		||||
          "maxSelect": 1,
 | 
			
		||||
          "displayFields": []
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "rtvjzpxw",
 | 
			
		||||
        "name": "shift",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "ggx1gyeq",
 | 
			
		||||
        "name": "wage",
 | 
			
		||||
        "type": "number",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "indexes": [
 | 
			
		||||
      "CREATE INDEX `idx_mPdvrAQ` ON `accounts` (`accountNumber`)"
 | 
			
		||||
    ],
 | 
			
		||||
    "listRule": "",
 | 
			
		||||
    "viewRule": null,
 | 
			
		||||
    "createRule": null,
 | 
			
		||||
    "updateRule": "(@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.grade = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.data.shift = null && @request.data.wage = null) || (@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.auth.id = company.id)",
 | 
			
		||||
    "deleteRule": null,
 | 
			
		||||
    "options": {}
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "id": "w1au07idupp27qv",
 | 
			
		||||
    "name": "bankers",
 | 
			
		||||
    "type": "auth",
 | 
			
		||||
    "system": false,
 | 
			
		||||
    "schema": [],
 | 
			
		||||
    "indexes": [],
 | 
			
		||||
    "listRule": null,
 | 
			
		||||
    "viewRule": null,
 | 
			
		||||
    "createRule": null,
 | 
			
		||||
    "updateRule": null,
 | 
			
		||||
    "deleteRule": null,
 | 
			
		||||
    "options": {
 | 
			
		||||
      "allowEmailAuth": true,
 | 
			
		||||
      "allowOAuth2Auth": true,
 | 
			
		||||
      "allowUsernameAuth": true,
 | 
			
		||||
      "exceptEmailDomains": null,
 | 
			
		||||
      "manageRule": null,
 | 
			
		||||
      "minPasswordLength": 8,
 | 
			
		||||
      "onlyEmailDomains": null,
 | 
			
		||||
      "requireEmail": false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "id": "w85pgrtrmovf916",
 | 
			
		||||
    "name": "transactions",
 | 
			
		||||
    "type": "base",
 | 
			
		||||
    "system": false,
 | 
			
		||||
    "schema": [
 | 
			
		||||
      {
 | 
			
		||||
        "id": "5aeh5giq",
 | 
			
		||||
        "name": "account",
 | 
			
		||||
        "type": "relation",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "collectionId": "74ftooxenpeq14b",
 | 
			
		||||
          "cascadeDelete": false,
 | 
			
		||||
          "minSelect": null,
 | 
			
		||||
          "maxSelect": 1,
 | 
			
		||||
          "displayFields": [
 | 
			
		||||
            "firstName",
 | 
			
		||||
            "lastName"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "2bzqyeuk",
 | 
			
		||||
        "name": "label",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "zsx9i8w7",
 | 
			
		||||
        "name": "amount",
 | 
			
		||||
        "type": "number",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "indexes": [],
 | 
			
		||||
    "listRule": "@request.auth.id != \"\" && @collection.bankers.id ?= @request.auth.id",
 | 
			
		||||
    "viewRule": "@request.auth.id != \"\" && @collection.bankers.id ?= @request.auth.id",
 | 
			
		||||
    "createRule": "(@request.auth.id != \"\" && @collection.bankers.id ?= @request.auth.id) || (@request.auth.id != \"\" && @collection.companies.id ?= @request.auth.id && amount > 0)",
 | 
			
		||||
    "updateRule": null,
 | 
			
		||||
    "deleteRule": null,
 | 
			
		||||
    "options": {}
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "id": "5msnfxat1sc2c1r",
 | 
			
		||||
    "name": "companyTransactions",
 | 
			
		||||
    "type": "base",
 | 
			
		||||
    "system": false,
 | 
			
		||||
    "schema": [
 | 
			
		||||
      {
 | 
			
		||||
        "id": "7rnyupog",
 | 
			
		||||
        "name": "account",
 | 
			
		||||
        "type": "relation",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "collectionId": "s854d2w72fvyl54",
 | 
			
		||||
          "cascadeDelete": false,
 | 
			
		||||
          "minSelect": null,
 | 
			
		||||
          "maxSelect": 1,
 | 
			
		||||
          "displayFields": []
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "ruby9fp9",
 | 
			
		||||
        "name": "label",
 | 
			
		||||
        "type": "text",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": true,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null,
 | 
			
		||||
          "pattern": ""
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "id": "4hiu46ib",
 | 
			
		||||
        "name": "amount",
 | 
			
		||||
        "type": "number",
 | 
			
		||||
        "system": false,
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "options": {
 | 
			
		||||
          "min": null,
 | 
			
		||||
          "max": null
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "indexes": [],
 | 
			
		||||
    "listRule": "",
 | 
			
		||||
    "viewRule": "",
 | 
			
		||||
    "createRule": "(@request.auth.id != \"\" && @collection.bankers.id ?= @request.auth.id) || (@request.auth.id != \"\" && @collection.companies.id ?= @request.auth.id)",
 | 
			
		||||
    "updateRule": null,
 | 
			
		||||
    "deleteRule": null,
 | 
			
		||||
    "options": {}
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +0,0 @@
 | 
			
		|||
package middlewares
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/labstack/echo/v5"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/apis"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pterm/pterm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ServeSPA(webAppPath string) func(e *core.ServeEvent) error {
 | 
			
		||||
	return func(e *core.ServeEvent) error {
 | 
			
		||||
		pterm.Info.Print("Serving SPA from ", webAppPath)
 | 
			
		||||
		subFs := echo.MustSubFS(e.Router.Filesystem, webAppPath)
 | 
			
		||||
		e.Router.GET("/*", apis.StaticDirectoryHandler(subFs, false))
 | 
			
		||||
		originalErrorHandler := e.Router.HTTPErrorHandler
 | 
			
		||||
		e.Router.HTTPErrorHandler = func(c echo.Context, err error) {
 | 
			
		||||
			if c.Path() == "/*" && err == echo.ErrNotFound {
 | 
			
		||||
				err = c.FileFS("index.html", subFs)
 | 
			
		||||
			}
 | 
			
		||||
			originalErrorHandler(c, err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,33 +0,0 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/joho/godotenv"
 | 
			
		||||
	"github.com/pterm/pterm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetEnv returns the value of the environment variable named by the key.
 | 
			
		||||
// If the env key is not present, it returns the default value.
 | 
			
		||||
func GetEnv(key, def string) string {
 | 
			
		||||
	if value, ok := os.LookupEnv(key); ok {
 | 
			
		||||
		return value
 | 
			
		||||
	}
 | 
			
		||||
	return def
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadEnv() {
 | 
			
		||||
	err := godotenv.Load("../.env")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		pterm.Info.Println("No .env file found. Proceeding...")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add env variables without "VITE_" prefix.
 | 
			
		||||
	for _, e := range os.Environ() {
 | 
			
		||||
		pair := strings.Split(e, "=")
 | 
			
		||||
		if strings.HasPrefix(pair[0], "VITE_") {
 | 
			
		||||
			os.Setenv(pair[0][5:], pair[1])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								src/App.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <MoleculeNavigationDrawer
 | 
			
		||||
    :drawer-id="drawerId"
 | 
			
		||||
    :navigation-entries="navigationEntries"
 | 
			
		||||
  >
 | 
			
		||||
    <RouterView />
 | 
			
		||||
  </MoleculeNavigationDrawer>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
  IdentificationIcon,
 | 
			
		||||
  LibraryIcon,
 | 
			
		||||
  LogoutIcon,
 | 
			
		||||
  MusicNoteIcon,
 | 
			
		||||
  TrendingUpIcon,
 | 
			
		||||
  UserIcon,
 | 
			
		||||
} from '@heroicons/vue/outline';
 | 
			
		||||
import { INavigationEntry } from './interfaces/navigation-entry.interface';
 | 
			
		||||
import MoleculeNavigationDrawer from './components/molecules/MoleculeNavigationDrawer.vue';
 | 
			
		||||
 | 
			
		||||
const drawerId = 'default-drawer';
 | 
			
		||||
const navigationEntries: INavigationEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Stammdaten',
 | 
			
		||||
    icon: UserIcon,
 | 
			
		||||
    to: '/data',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Einstempeln',
 | 
			
		||||
    icon: IdentificationIcon,
 | 
			
		||||
    to: '/checkin',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Bank',
 | 
			
		||||
    icon: LibraryIcon,
 | 
			
		||||
    to: '/bank',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Börse',
 | 
			
		||||
    icon: TrendingUpIcon,
 | 
			
		||||
    to: '/stocks',
 | 
			
		||||
    disabled: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Radio',
 | 
			
		||||
    icon: MusicNoteIcon,
 | 
			
		||||
    to: '/radio',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'divider',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Abmelden',
 | 
			
		||||
    icon: LogoutIcon,
 | 
			
		||||
    to: '/logout',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/logo.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 57 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/logo_white.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 47 KiB  | 
							
								
								
									
										20
									
								
								src/components/atoms/AtomCurrencyInput.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <AtomInput
 | 
			
		||||
      ref="input"
 | 
			
		||||
      type="number"
 | 
			
		||||
      class="text-right pr-16"
 | 
			
		||||
      step="1"
 | 
			
		||||
    />
 | 
			
		||||
    <span class="text-sm opacity-50 absolute -ml-16 top-2/4 -mt-8 pointer-events-none">,00 Öro</span>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import AtomInput from './AtomInput.vue';
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										40
									
								
								src/components/atoms/AtomInput.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <input
 | 
			
		||||
    ref="input"
 | 
			
		||||
    :type="type"
 | 
			
		||||
    :placeholder="placeholder"
 | 
			
		||||
    :value="modelValue"
 | 
			
		||||
    class="input input-bordered w-full"
 | 
			
		||||
    @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
defineProps({
 | 
			
		||||
  type: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'text',
 | 
			
		||||
  },
 | 
			
		||||
  placeholder: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  modelValue: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineEmits(['update:modelValue']);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
input::-webkit-outer-spin-button,
 | 
			
		||||
input::-webkit-inner-spin-button {
 | 
			
		||||
  -webkit-appearance: none;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
input[type=number] {
 | 
			
		||||
  -moz-appearance: textfield;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										18
									
								
								src/components/atoms/AtomLogo.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <img
 | 
			
		||||
    v-if="isDark"
 | 
			
		||||
    src="../../assets/logo_white.png"
 | 
			
		||||
  />
 | 
			
		||||
  <img
 | 
			
		||||
    v-else
 | 
			
		||||
    src="../../assets/logo.png"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { isDark } from '../../utils/darkMode';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										38
									
								
								src/components/atoms/AtomModal.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <Teleport to="body">
 | 
			
		||||
    <input
 | 
			
		||||
      :id="id"
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
      class="modal-toggle"
 | 
			
		||||
      @change="$emit('open', {id, open: ($event.target as HTMLInputElement).checked})"
 | 
			
		||||
    />
 | 
			
		||||
    <div class="modal">
 | 
			
		||||
      <div class="modal-box">
 | 
			
		||||
        <h3 class="font-bold text-lg">{{ title }}</h3>
 | 
			
		||||
        <p class="py-4"><slot /></p>
 | 
			
		||||
        <div class="modal-action">
 | 
			
		||||
          <slot name="action" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </Teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineEmits(['open']);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <select
 | 
			
		||||
    class="select-bordered select w-full"
 | 
			
		||||
    @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
 | 
			
		||||
  >
 | 
			
		||||
  <select class="select select-bordered w-full">
 | 
			
		||||
    <option
 | 
			
		||||
      disabled
 | 
			
		||||
      selected
 | 
			
		||||
| 
						 | 
				
			
			@ -25,13 +22,7 @@ defineProps({
 | 
			
		|||
    type: Array as PropType<string[]>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  modelValue: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineEmits(['update:modelValue']);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,11 +20,11 @@ import { FunctionalComponent, PropType } from 'vue';
 | 
			
		|||
 | 
			
		||||
defineProps({
 | 
			
		||||
  onIcon: {
 | 
			
		||||
    type: Function as PropType<FunctionalComponent>,
 | 
			
		||||
    type: Object as PropType<FunctionalComponent>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  offIcon: {
 | 
			
		||||
    type: Function as PropType<FunctionalComponent>,
 | 
			
		||||
    type: Object as PropType<FunctionalComponent>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  checked: {
 | 
			
		||||
							
								
								
									
										91
									
								
								src/components/molecules/MoleculeAddAccountModal.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,91 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <AtomModal
 | 
			
		||||
    :id="id"
 | 
			
		||||
    title="Account hinzufügen"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex flex-col gap-4">
 | 
			
		||||
      <h4 class="text-md">Daten</h4>
 | 
			
		||||
      <div class="flex gap-4">
 | 
			
		||||
        <AtomInput
 | 
			
		||||
          v-model="firstName"
 | 
			
		||||
          placeholder="Vorname"
 | 
			
		||||
        />
 | 
			
		||||
        <AtomInput placeholder="Nachname" />
 | 
			
		||||
      </div>
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        placeholder="Kontonummer"
 | 
			
		||||
        type="number"
 | 
			
		||||
      />
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        placeholder="Geburtsdatum"
 | 
			
		||||
        type="number"
 | 
			
		||||
      />
 | 
			
		||||
      <div class="flex gap-4">
 | 
			
		||||
        <AtomInput placeholder="Straße" />
 | 
			
		||||
        <AtomInput
 | 
			
		||||
          placeholder="Hnr."
 | 
			
		||||
          class="w-3/12"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="flex gap-4">
 | 
			
		||||
        <AtomInput
 | 
			
		||||
          placeholder="Postleitzahl"
 | 
			
		||||
          class="w-6/12"
 | 
			
		||||
        />
 | 
			
		||||
        <AtomInput placeholder="Stadt" />
 | 
			
		||||
      </div>
 | 
			
		||||
      <h4 class="text-md">Kontakt</h4>
 | 
			
		||||
      <div class="flex gap-4">
 | 
			
		||||
        <AtomSelect
 | 
			
		||||
          placeholder="Label"
 | 
			
		||||
          :options="['Festnetz', 'Mobil', 'Arbeit']"
 | 
			
		||||
          class="w-4/12"
 | 
			
		||||
        />
 | 
			
		||||
        <AtomInput placeholder="Telefonnummer" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #action>
 | 
			
		||||
      <label
 | 
			
		||||
        ref="abortButton"
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        :for="id"
 | 
			
		||||
      >
 | 
			
		||||
        <XCircleIcon class="w-6 h-6" />
 | 
			
		||||
        Abbrechen
 | 
			
		||||
      </label>
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        @click="handleSubmit()"
 | 
			
		||||
      >
 | 
			
		||||
        <CheckCircleIcon class="w-6 h-6" />
 | 
			
		||||
        Speichern
 | 
			
		||||
      </button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </AtomModal>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/outline';
 | 
			
		||||
import AtomModal from '../atoms/AtomModal.vue';
 | 
			
		||||
import AtomInput from '../atoms/AtomInput.vue';
 | 
			
		||||
import AtomSelect from '../atoms/AtomSelect.vue';
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
const firstName = ref('');
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function handleSubmit() {
 | 
			
		||||
  console.log('TODO: Submit');
 | 
			
		||||
  console.log(firstName.value);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										52
									
								
								src/components/molecules/MoleculeDataTable.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="overflow-x-auto">
 | 
			
		||||
    <table class="table w-full table-zebra">
 | 
			
		||||
      <thead class="sticky top-0">
 | 
			
		||||
        <tr>
 | 
			
		||||
          <th v-for="(_, key) of data[0]">{{ key }}</th>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        <tr v-for="entries in data">
 | 
			
		||||
          <td
 | 
			
		||||
            v-for="(entry, _, key) of entries"
 | 
			
		||||
            :class="key >= 6 ? 'text-center' : null"
 | 
			
		||||
          >
 | 
			
		||||
            <span v-if="key < 6">{{ entry }}</span>
 | 
			
		||||
            <button
 | 
			
		||||
              v-if="key == 6"
 | 
			
		||||
              class="btn btn-sm btn-ghost p-1"
 | 
			
		||||
            >
 | 
			
		||||
              <DocumentTextIcon
 | 
			
		||||
                class="h-6 w-6"
 | 
			
		||||
              />
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              v-if="key == 7"
 | 
			
		||||
              class="btn btn-sm btn-ghost p-1"
 | 
			
		||||
            >
 | 
			
		||||
              <CurrencyDollarIcon
 | 
			
		||||
                class="h-6 w-6"
 | 
			
		||||
              />
 | 
			
		||||
            </button>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>import { PropType } from 'vue';
 | 
			
		||||
import { CurrencyDollarIcon, DocumentTextIcon } from '@heroicons/vue/outline';
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  data: {
 | 
			
		||||
    type: Array as PropType<object[]>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,33 +1,32 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <AtomModal
 | 
			
		||||
    :id="id"
 | 
			
		||||
    ref="modal"
 | 
			
		||||
    :title="title"
 | 
			
		||||
    @close="handleClose"
 | 
			
		||||
    @open="handleOpen"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex place-items-center justify-between">
 | 
			
		||||
    <div class="flex justify-between place-items-center">
 | 
			
		||||
      <span>{{ inputLabel }}</span>
 | 
			
		||||
      <AtomCurrencyInput
 | 
			
		||||
        v-model="amount"
 | 
			
		||||
        ref="input"
 | 
			
		||||
        class="w-4/12"
 | 
			
		||||
        :max="1000"
 | 
			
		||||
        @keyup.esc="modal?.close()"
 | 
			
		||||
        @keyup.enter="handleSubmit()"
 | 
			
		||||
        @keyup.esc="abortButton.click()"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #action>
 | 
			
		||||
      <label
 | 
			
		||||
        ref="abortButton"
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        :for="id"
 | 
			
		||||
        @click="modal?.close()"
 | 
			
		||||
      >
 | 
			
		||||
        <XCircleIcon class="h-6 w-6" />
 | 
			
		||||
        <XCircleIcon class="w-6 h-6" />
 | 
			
		||||
        Abbrechen
 | 
			
		||||
      </label>
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        @click="handleSubmit()"
 | 
			
		||||
      >
 | 
			
		||||
        <CheckCircleIcon class="h-6 w-6" />
 | 
			
		||||
        <CheckCircleIcon class="w-6 h-6" />
 | 
			
		||||
        {{ actionLabel }}
 | 
			
		||||
      </button>
 | 
			
		||||
    </template>
 | 
			
		||||
| 
						 | 
				
			
			@ -35,13 +34,13 @@
 | 
			
		|||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
 | 
			
		||||
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/outline';
 | 
			
		||||
import AtomModal from '../atoms/AtomModal.vue';
 | 
			
		||||
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
const modal = ref<InstanceType<typeof AtomModal>>();
 | 
			
		||||
const amount = ref();
 | 
			
		||||
const input = ref();
 | 
			
		||||
const abortButton = ref();
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
| 
						 | 
				
			
			@ -64,22 +63,18 @@ defineProps({
 | 
			
		|||
 | 
			
		||||
const emit = defineEmits(['submit']);
 | 
			
		||||
 | 
			
		||||
function handleClose() {
 | 
			
		||||
  amount.value = '';
 | 
			
		||||
function handleOpen(event: {id: string, open: boolean}) {
 | 
			
		||||
  if(event.open) {
 | 
			
		||||
    // This is (currently) the only method to focus a input field inside a custom component.
 | 
			
		||||
    input.value.$refs.input.$refs.input.value = '';
 | 
			
		||||
    input.value.$refs.input.$refs.input.focus();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleSubmit() {
 | 
			
		||||
  if(amount.value <= 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  emit('submit', input.value.$refs.input.$refs.input.value);
 | 
			
		||||
  abortButton.value.click();
 | 
			
		||||
}
 | 
			
		||||
  emit('submit', amount.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show: () => modal.value?.show(),
 | 
			
		||||
  close: () => modal.value?.close(),
 | 
			
		||||
  isOpen: () => modal.value?.isOpen(),
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
							
								
								
									
										97
									
								
								src/components/molecules/MoleculeNavigationDrawer.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,97 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="drawer drawer-mobile">
 | 
			
		||||
    <input
 | 
			
		||||
      :id="drawerId"
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
      class="drawer-toggle"
 | 
			
		||||
    />
 | 
			
		||||
    <div class="drawer-content flex flex-col bg-base-300 h-screen">
 | 
			
		||||
      <div
 | 
			
		||||
        class="navbar shadow-lg bg-neutral-focus text-neutral-content lg:hidden sticky top-0 z-50 max-w-7xl place-self-center"
 | 
			
		||||
      >
 | 
			
		||||
        <div class="flex-none">
 | 
			
		||||
          <label
 | 
			
		||||
            :for="drawerId"
 | 
			
		||||
            class="btn btn-ghost rounded-btn lg:hidden"
 | 
			
		||||
          >
 | 
			
		||||
            <MenuIcon class="h-7 w-7" />
 | 
			
		||||
          </label>
 | 
			
		||||
          <label
 | 
			
		||||
            tabindex="0"
 | 
			
		||||
            class="btn btn-ghost avatar"
 | 
			
		||||
          >
 | 
			
		||||
            <AtomLogo class="w-12" />
 | 
			
		||||
          </label>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <slot />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="drawer-side">
 | 
			
		||||
      <label
 | 
			
		||||
        :for="drawerId"
 | 
			
		||||
        class="drawer-overlay"
 | 
			
		||||
      />
 | 
			
		||||
      <ul class="menu p-4 overflow-y-auto w-64">
 | 
			
		||||
        <li class="pointer-events-none">
 | 
			
		||||
          <a>
 | 
			
		||||
            <AtomLogo class="w-28" />
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li
 | 
			
		||||
          v-for="navigationEntry of navigationEntries"
 | 
			
		||||
          :class="getNavigationEntryClass(navigationEntry)"
 | 
			
		||||
        >
 | 
			
		||||
          <component
 | 
			
		||||
            :is="!navigationEntry.to || navigationEntry.disabled ? 'span' : 'router-link'"
 | 
			
		||||
            v-if="navigationEntry.name !== 'divider'"
 | 
			
		||||
            :to="navigationEntry.to"
 | 
			
		||||
            active-class="active"
 | 
			
		||||
          >
 | 
			
		||||
            <component
 | 
			
		||||
              :is="navigationEntry.icon"
 | 
			
		||||
              class="h-6 w-6"
 | 
			
		||||
            />{{ navigationEntry.name }}
 | 
			
		||||
          </component>
 | 
			
		||||
        </li>
 | 
			
		||||
        <AtomSwap
 | 
			
		||||
          class="mt-auto place-self-end"
 | 
			
		||||
          :on-icon="SunIcon"
 | 
			
		||||
          :off-icon="MoonIcon"
 | 
			
		||||
          :checked="isDark"
 | 
			
		||||
          @change="toggleDark()"
 | 
			
		||||
        />
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { PropType } from 'vue';
 | 
			
		||||
import { isDark, toggleDark } from '../../utils/darkMode';
 | 
			
		||||
import { INavigationEntry } from '../../interfaces/navigation-entry.interface';
 | 
			
		||||
import { MenuIcon, MoonIcon, SunIcon } from '@heroicons/vue/outline';
 | 
			
		||||
import AtomLogo from '../atoms/AtomLogo.vue';
 | 
			
		||||
import AtomSwap from '../atoms/AtomSwap.vue';
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  drawerId: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  navigationEntries: {
 | 
			
		||||
    type: Array as PropType<INavigationEntry[]>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function getNavigationEntryClass(navigationEntry: INavigationEntry) {
 | 
			
		||||
  return {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/naming-convention
 | 
			
		||||
    'menu-title': navigationEntry.name === 'divider',
 | 
			
		||||
    disabled: navigationEntry.disabled,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +1,18 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <table class="table-zebra table overflow-hidden rounded-lg">
 | 
			
		||||
    <thead class="bg-base-200">
 | 
			
		||||
  <table class="table table-zebra">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <th />
 | 
			
		||||
        <th class="text-right text-lg normal-case text-base-content">{{ CurrencyService.toString(balance) }}</th>
 | 
			
		||||
        <th class="normal-case text-lg text-right">{{ CurrencyService.toString(balance) }}</th>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody class="bg-base-100">
 | 
			
		||||
    <tbody>
 | 
			
		||||
      <tr v-for="transaction in transactions">
 | 
			
		||||
        <td>
 | 
			
		||||
          <div class="flex items-center">
 | 
			
		||||
            <div>
 | 
			
		||||
              <div class="font-bold">{{ transaction.label }}</div>
 | 
			
		||||
              <div>{{ DateService.toString(parseISO(transaction.created)) }}</div>
 | 
			
		||||
              <div class="font-bold">{{ transaction.type }}</div>
 | 
			
		||||
              <div>{{ DateService.toString(transaction.date) }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </td>
 | 
			
		||||
| 
						 | 
				
			
			@ -35,14 +35,13 @@
 | 
			
		|||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { PropType } from 'vue';
 | 
			
		||||
import { parseISO } from 'date-fns';
 | 
			
		||||
import { CurrencyService } from '../../services/currency.service';
 | 
			
		||||
import { DateService } from '../../services/date.service';
 | 
			
		||||
import { TransactionsResponse } from '../../types/pocketbase.types';
 | 
			
		||||
import { ITransaction } from '../../interfaces/transaction.interface';
 | 
			
		||||
import { CurrencyService } from '../services/currency.service';
 | 
			
		||||
import { DateService } from '../services/date.service';
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  transactions: {
 | 
			
		||||
    type: Array as PropType<TransactionsResponse[]>,
 | 
			
		||||
    type: Array as PropType<ITransaction[]>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  balance: {
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +1,14 @@
 | 
			
		|||
export class CurrencyService {
 | 
			
		||||
  public static toString(value: number, suffix = true): string {
 | 
			
		||||
  public static toString(value: number): string {
 | 
			
		||||
    return `${(value / 100).toLocaleString('de-DE', {
 | 
			
		||||
      minimumFractionDigits: 2,
 | 
			
		||||
      maximumFractionDigits: 2,
 | 
			
		||||
    })}${suffix ? ' Batzen' : ''}`;
 | 
			
		||||
    })} Öro`;
 | 
			
		||||
  }
 | 
			
		||||
  public static toSignedString(value: number): string {
 | 
			
		||||
    return `${(value / 100) > 0 ? '+' : ''}${(value / 100).toLocaleString('de', {
 | 
			
		||||
    return `${value > 0 ? '+' : ''}${(value / 100).toLocaleString('de', {
 | 
			
		||||
      minimumFractionDigits: 2,
 | 
			
		||||
      maximumFractionDigits: 2,
 | 
			
		||||
    })} Batzen`;
 | 
			
		||||
    })} Öro`;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/components/services/date.service.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
export class DateService {
 | 
			
		||||
  public static toString(date: Date): string {
 | 
			
		||||
    return date.toLocaleDateString('de-DE', {
 | 
			
		||||
      day: '2-digit',
 | 
			
		||||
      month: '2-digit',
 | 
			
		||||
      year: 'numeric',
 | 
			
		||||
      hour: '2-digit',
 | 
			
		||||
      minute: '2-digit',
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										161
									
								
								src/components/views/BankView.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,161 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="hero-content flex-col text-center lg:p-6 pt-0">
 | 
			
		||||
    <h1 class="text-8xl mt-5">{{ CurrencyService.toString(balance) }}</h1>
 | 
			
		||||
    <h3 class="text-5xl font-bold mt-10">Kontoinhaber: John Doe</h3>
 | 
			
		||||
    <h4 class="text-4xl mb-10">Kontonummer: 0000123456</h4>
 | 
			
		||||
    <MoleculeTransactionTable
 | 
			
		||||
      class="w-[42rem]"
 | 
			
		||||
      :balance="balance"
 | 
			
		||||
      :transactions="transactions"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="sticky flex place-content-center lg:gap-32 gap-16 bg-base-100 bottom-0 w-100 p-5">
 | 
			
		||||
    <MoleculeInputModal
 | 
			
		||||
      :id="depositModalId"
 | 
			
		||||
      title="Einzahlung"
 | 
			
		||||
      action-label="Einzahlen"
 | 
			
		||||
      input-label="Einzuzahlender Betrag:"
 | 
			
		||||
      @submit="handleDeposit"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="btn gap-2"
 | 
			
		||||
      :for="depositModalId"
 | 
			
		||||
    >
 | 
			
		||||
      <ChevronUpIcon class="w-6 h-6 text-success" />
 | 
			
		||||
      Einzahlen
 | 
			
		||||
    </label>
 | 
			
		||||
    <MoleculeInputModal
 | 
			
		||||
      :id="salaryModalId"
 | 
			
		||||
      title="Gehalt"
 | 
			
		||||
      action-label="Gehalt hinzufügen"
 | 
			
		||||
      input-label="Gehalt Betrag:"
 | 
			
		||||
      @submit="handleSalary"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="btn gap-2"
 | 
			
		||||
      :for="salaryModalId"
 | 
			
		||||
    >
 | 
			
		||||
      <CurrencyDollarIcon class="w-6 h-6 text-warning" />
 | 
			
		||||
      Gehalt
 | 
			
		||||
    </label>
 | 
			
		||||
    <MoleculeInputModal
 | 
			
		||||
      :id="withdrawModalId"
 | 
			
		||||
      title="Auszahlung"
 | 
			
		||||
      action-label="Auszahlen"
 | 
			
		||||
      input-label="Auszuzahlender Betrag:"
 | 
			
		||||
      @submit="handleWithdraw"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="btn gap-2"
 | 
			
		||||
      :for="withdrawModalId"
 | 
			
		||||
    >
 | 
			
		||||
      <ChevronDownIcon class="w-6 h-6 text-error" />
 | 
			
		||||
      Auszahlen
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ITransaction } from '../../interfaces/transaction.interface';
 | 
			
		||||
import { CurrencyService } from '../services/currency.service';
 | 
			
		||||
import { CurrencyDollarIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/outline';
 | 
			
		||||
import MoleculeTransactionTable from '../molecules/MoleculeTransactionTable.vue';
 | 
			
		||||
import MoleculeInputModal from '../molecules/MoleculeInputModal.vue';
 | 
			
		||||
import { onKeyStroke } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
const depositModalId = 'deposit-modal';
 | 
			
		||||
const salaryModalId = 'salary-modal';
 | 
			
		||||
const withdrawModalId = 'withdraw-modal';
 | 
			
		||||
const balance = 13500;
 | 
			
		||||
const transactions: ITransaction[] = [
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Kontoeröffnung',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 30, 0),
 | 
			
		||||
    amount: 500,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Bargeldeinzahlung',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 30, 10),
 | 
			
		||||
    amount: 10000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Gehalt',
 | 
			
		||||
    date: new Date(2022, 7, 12, 17, 34, 10),
 | 
			
		||||
    amount: 5000,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    type: 'Bargeldauszahlung',
 | 
			
		||||
    date: new Date(2022, 7, 15, 18, 26, 0),
 | 
			
		||||
    amount: -2000,
 | 
			
		||||
  },
 | 
			
		||||
].sort((a, b) => b.date.getTime() - a.date.getTime());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
onKeyStroke('e', (e) => {
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
  console.log('e pressed');
 | 
			
		||||
  // TODO: open deposit modal
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function handleDeposit(amount: number) {
 | 
			
		||||
  console.log('Deposit:', amount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleSalary(amount: number) {
 | 
			
		||||
  console.log('Salary:', amount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleWithdraw(amount: number) {
 | 
			
		||||
  console.log('Withdraw:', amount);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,8 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="min-h-full lg:p-6">
 | 
			
		||||
    <div class="hero min-h-full rounded-lg bg-base-200">
 | 
			
		||||
    <div class="hero min-h-full bg-base-200 rounded-lg">
 | 
			
		||||
      <div class="hero-content flex-col text-center">
 | 
			
		||||
        <h1 class="text-6xl font-bold"><slot /></h1>
 | 
			
		||||
        <h1 class="text-6xl font-bold">John Doe um 17:39:33</h1>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
							
								
								
									
										199
									
								
								src/components/views/DataView.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,199 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="lg:p-6">
 | 
			
		||||
    <MoleculeDataTable :data="data" />
 | 
			
		||||
    <MoleculeAddAccountModal :id="addAccountModalId" />
 | 
			
		||||
    <label
 | 
			
		||||
      class="btn btn-primary btn-lg btn-circle shadow-2xl fixed bottom-8 right-8"
 | 
			
		||||
      :for="addAccountModalId"
 | 
			
		||||
    >
 | 
			
		||||
      <PlusIcon class="w-7 h-7" />
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import MoleculeDataTable from '../molecules/MoleculeDataTable.vue';
 | 
			
		||||
import { PlusIcon } from '@heroicons/vue/outline';
 | 
			
		||||
import MoleculeAddAccountModal from '../molecules/MoleculeAddAccountModal.vue';
 | 
			
		||||
 | 
			
		||||
const addAccountModalId = 'add-account-modal';
 | 
			
		||||
/* eslint-disable @typescript-eslint/naming-convention */
 | 
			
		||||
const data = [
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    'Kontonummer': '0000123456',
 | 
			
		||||
    'Name': 'John Doe',
 | 
			
		||||
    'Geburtsdatum': '13.11.1998',
 | 
			
		||||
    'Kontostand': '150,00 Öro',
 | 
			
		||||
    'Adresse': 'Test-Straße 1, 74613 Öhringen',
 | 
			
		||||
    'Letzter': '12.07.2022 17:39',
 | 
			
		||||
    'Kontakt': null,
 | 
			
		||||
    'Trans.': null,
 | 
			
		||||
  },
 | 
			
		||||
  /* eslint-enable @typescript-eslint/naming-convention */
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										59
									
								
								src/components/views/RadioView.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <button @click="getTrack()">GetTrack</button>
 | 
			
		||||
  <button @click="setPlayPause()">Play/Pause</button>
 | 
			
		||||
  <div>Currently Playing: {{ `${artist} - ${track}` }}</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import Mopidy from 'mopidy';
 | 
			
		||||
import { onBeforeUnmount, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
const artist = ref('');
 | 
			
		||||
const track = ref('');
 | 
			
		||||
 | 
			
		||||
const mopidy = new Mopidy({
 | 
			
		||||
  webSocketUrl: 'ws://localhost:6680/mopidy/ws/',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
mopidy.on('state', console.log);
 | 
			
		||||
mopidy.on('event', console.log);
 | 
			
		||||
 | 
			
		||||
mopidy.on('state:online', () => {
 | 
			
		||||
  getTrack();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
mopidy.on('event:trackPlaybackStarted', (event) => {
 | 
			
		||||
  track.value = event.tl_track.track.name;
 | 
			
		||||
  artist.value = event.tl_track.track.artists.map((artist) => artist.name).join(', ');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function getTrack() {
 | 
			
		||||
  if(mopidy.playback) {
 | 
			
		||||
    const currentTrack = await mopidy.playback.getCurrentTrack();
 | 
			
		||||
    console.log(await mopidy.playback.getTimePosition());
 | 
			
		||||
    if(currentTrack) {
 | 
			
		||||
      track.value = currentTrack.name;
 | 
			
		||||
      artist.value = currentTrack.artists.map((artist) => artist.name).join(', ');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function setPlayPause() {
 | 
			
		||||
  const currentState = await mopidy.playback?.getState();
 | 
			
		||||
  if(currentState === 'playing') {
 | 
			
		||||
    await mopidy.playback?.pause();
 | 
			
		||||
  } else {
 | 
			
		||||
    await mopidy.playback?.resume();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  mopidy.close();
 | 
			
		||||
  mopidy.off();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <iframe
 | 
			
		||||
    src="/_"
 | 
			
		||||
    frameborder="0"
 | 
			
		||||
    class="h-full w-full"
 | 
			
		||||
  />
 | 
			
		||||
  #Stocks
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
							
								
								
									
										0
									
								
								webapp/src/env.d.ts → src/env.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										3
									
								
								src/index.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
							
								
								
									
										5
									
								
								src/interfaces/transaction.interface.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
export interface ITransaction {
 | 
			
		||||
  type: string;
 | 
			
		||||
  date: Date;
 | 
			
		||||
  amount: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/router/router.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
import * as VueRouter from 'vue-router';
 | 
			
		||||
import DataView from '../components/views/DataView.vue';
 | 
			
		||||
import CheckInView from '../components/views/CheckInView.vue';
 | 
			
		||||
import BankView from '../components/views/BankView.vue';
 | 
			
		||||
import StocksView from '../components/views/StocksView.vue';
 | 
			
		||||
import RadioView from '../components/views/RadioView.vue';
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
  {
 | 
			
		||||
    path: '/data',
 | 
			
		||||
    component: DataView,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/checkin',
 | 
			
		||||
    component: CheckInView,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/bank',
 | 
			
		||||
    component: BankView,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/stocks',
 | 
			
		||||
    component: StocksView,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/radio',
 | 
			
		||||
    component: RadioView,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const router = VueRouter.createRouter({
 | 
			
		||||
  history: VueRouter.createWebHistory(),
 | 
			
		||||
  routes,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default router;
 | 
			
		||||
							
								
								
									
										10
									
								
								tailwind.config.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
module.exports = {
 | 
			
		||||
  content: [
 | 
			
		||||
    "./index.html",
 | 
			
		||||
    "./src/**/*.{vue,js,ts,jsx,tsx}",
 | 
			
		||||
  ],
 | 
			
		||||
  theme: {
 | 
			
		||||
    extend: {},
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [require('@tailwindcss/typography'), require('daisyui')],
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +1,7 @@
 | 
			
		|||
import { defineConfig } from 'vite'
 | 
			
		||||
import vue from '@vitejs/plugin-vue'
 | 
			
		||||
 | 
			
		||||
// https://vitejs.dev/config/
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  plugins: [vue({
 | 
			
		||||
    script: {
 | 
			
		||||
      defineModel: true,
 | 
			
		||||
    }
 | 
			
		||||
  })]
 | 
			
		||||
  plugins: [vue()]
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
src/types/pocketbase.types.ts
 | 
			
		||||
| 
						 | 
				
			
			@ -1,47 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "hgoe-sas",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "author": "Simon Giesel",
 | 
			
		||||
  "version": "1.3.6",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build": "vue-tsc --noEmit && vite build",
 | 
			
		||||
    "lint": "eslint -c .eslintrc.js src/",
 | 
			
		||||
    "preview": "vite preview",
 | 
			
		||||
    "typegen": "pocketbase-typegen --json ../server/pb_schema.json --out ./src/types/pocketbase.types.ts",
 | 
			
		||||
    "optimize-svg": "svgo -f ./src/assets/svg/ --config=./src/assets/svg/svgo.config.js",
 | 
			
		||||
    "generate-version": "node ./scripts/generate-version.js"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@heroicons/vue": "^2.0.18",
 | 
			
		||||
    "@vueuse/core": "^10.2.1",
 | 
			
		||||
    "canvas-confetti": "^1.6.0",
 | 
			
		||||
    "daisyui": "^3.2.1",
 | 
			
		||||
    "date-fns": "^2.30.0",
 | 
			
		||||
    "pocketbase": "^0.15.3",
 | 
			
		||||
    "vue": "^3.3.4",
 | 
			
		||||
    "vue-router": "4.2.4"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@tailwindcss/container-queries": "^0.1.1",
 | 
			
		||||
    "@tailwindcss/typography": "^0.5.9",
 | 
			
		||||
    "@types/canvas-confetti": "^1.6.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^6.0.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^6.0.0",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.2.3",
 | 
			
		||||
    "autoprefixer": "^10.4.14",
 | 
			
		||||
    "eslint": "^8.44.0",
 | 
			
		||||
    "eslint-plugin-tailwindcss": "^3.13.0",
 | 
			
		||||
    "eslint-plugin-vue": "^9.15.1",
 | 
			
		||||
    "eslint-plugin-vue-scoped-css": "^2.5.0",
 | 
			
		||||
    "pocketbase-typegen": "^1.1.11",
 | 
			
		||||
    "postcss": "^8.4.25",
 | 
			
		||||
    "sass": "^1.63.6",
 | 
			
		||||
    "svgo": "^3.0.2",
 | 
			
		||||
    "tailwindcss": "^3.3.2",
 | 
			
		||||
    "typescript": "^5.1.6",
 | 
			
		||||
    "vite": "^4.4.3",
 | 
			
		||||
    "vue-eslint-parser": "^9.3.1",
 | 
			
		||||
    "vue-tsc": "^1.8.4"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3096
									
								
								webapp/pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 19 KiB  | 
| 
		 Before Width: | Height: | Size: 54 KiB  | 
| 
		 Before Width: | Height: | Size: 18 KiB  | 
| 
		 Before Width: | Height: | Size: 1.3 KiB  | 
| 
		 Before Width: | Height: | Size: 2.4 KiB  | 
| 
		 Before Width: | Height: | Size: 15 KiB  | 
| 
		 Before Width: | Height: | Size: 2.4 KiB  | 
| 
		 Before Width: | Height: | Size: 13 KiB  | 
| 
		 Before Width: | Height: | Size: 2.6 KiB  | 
| 
		 Before Width: | Height: | Size: 5 KiB  | 
| 
		 Before Width: | Height: | Size: 1.6 KiB  | 
| 
						 | 
				
			
			@ -1,2 +0,0 @@
 | 
			
		|||
User-Agent: *
 | 
			
		||||
Disallow: /
 | 
			
		||||
| 
						 | 
				
			
			@ -1,94 +0,0 @@
 | 
			
		|||
<?xml version="1.0" standalone="no"?>
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
 | 
			
		||||
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
 | 
			
		||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
 width="3407.000000pt" height="3407.000000pt" viewBox="0 0 3407.000000 3407.000000"
 | 
			
		||||
 preserveAspectRatio="xMidYMid meet">
 | 
			
		||||
<metadata>
 | 
			
		||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
 | 
			
		||||
</metadata>
 | 
			
		||||
<g transform="translate(0.000000,3407.000000) scale(0.100000,-0.100000)"
 | 
			
		||||
fill="#000000" stroke="none">
 | 
			
		||||
<path d="M4410 33513 c-380 -51 -730 -280 -929 -609 -138 -227 -304 -646 -428
 | 
			
		||||
-1079 -35 -124 -98 -371 -106 -415 -3 -14 -10 -43 -16 -65 -15 -58 -98 -472
 | 
			
		||||
-106 -529 -3 -27 -8 -52 -9 -55 -2 -3 -6 -28 -10 -56 -3 -27 -8 -61 -11 -75
 | 
			
		||||
-3 -14 -10 -57 -16 -97 -5 -39 -12 -80 -14 -91 -2 -10 -6 -43 -10 -73 -3 -30
 | 
			
		||||
-8 -65 -10 -79 -10 -70 -15 -109 -20 -145 -10 -82 -24 -212 -30 -275 -3 -36
 | 
			
		||||
-8 -81 -10 -100 -2 -19 -7 -62 -10 -95 -3 -33 -7 -85 -10 -115 -12 -132 -15
 | 
			
		||||
-170 -20 -255 -3 -49 -8 -108 -10 -130 -2 -22 -7 -98 -10 -170 -4 -71 -8 -161
 | 
			
		||||
-10 -200 -26 -490 -26 -1706 0 -2155 2 -30 6 -122 10 -205 3 -82 8 -175 10
 | 
			
		||||
-205 21 -356 32 -516 40 -620 9 -107 14 -176 20 -255 9 -116 13 -173 20 -235
 | 
			
		||||
3 -30 8 -80 10 -110 3 -30 7 -77 10 -105 3 -27 7 -77 10 -110 12 -129 14 -151
 | 
			
		||||
20 -195 5 -46 9 -88 20 -195 3 -33 8 -73 10 -90 2 -16 7 -57 10 -90 3 -33 10
 | 
			
		||||
-94 15 -135 5 -41 12 -97 15 -125 3 -27 7 -66 10 -85 2 -19 7 -60 10 -90 3
 | 
			
		||||
-30 8 -66 10 -80 2 -14 7 -49 10 -79 4 -30 8 -61 10 -70 1 -9 6 -43 10 -76 3
 | 
			
		||||
-33 8 -76 11 -95 5 -41 23 -174 29 -210 3 -14 7 -50 11 -80 5 -55 7 -63 18
 | 
			
		||||
-130 3 -19 8 -53 11 -75 3 -22 7 -53 10 -70 3 -16 7 -48 10 -70 3 -22 7 -53
 | 
			
		||||
10 -70 3 -16 7 -48 10 -70 3 -22 12 -80 20 -130 8 -49 17 -108 20 -130 3 -22
 | 
			
		||||
10 -65 15 -95 5 -30 11 -68 14 -85 10 -67 19 -121 22 -140 2 -11 6 -36 9 -55
 | 
			
		||||
3 -19 7 -46 9 -60 2 -14 10 -59 16 -100 7 -41 14 -84 16 -95 4 -22 24 -138 29
 | 
			
		||||
-170 6 -34 16 -97 21 -119 2 -11 18 -100 34 -196 31 -179 57 -318 90 -495 11
 | 
			
		||||
-55 22 -115 25 -134 3 -19 7 -37 9 -40 2 -3 7 -28 10 -56 4 -27 11 -68 16 -90
 | 
			
		||||
5 -22 12 -56 15 -75 3 -19 17 -91 31 -160 14 -69 28 -136 30 -150 3 -14 13
 | 
			
		||||
-68 24 -120 20 -97 26 -128 35 -177 3 -16 10 -48 15 -73 13 -57 18 -82 30
 | 
			
		||||
-145 6 -27 14 -68 19 -90 18 -84 43 -201 47 -224 3 -12 21 -91 40 -175 20 -83
 | 
			
		||||
41 -178 49 -211 12 -55 74 -298 140 -550 14 -55 42 -154 61 -220 19 -66 37
 | 
			
		||||
-127 39 -135 16 -68 180 -596 260 -835 68 -202 201 -582 245 -695 11 -30 27
 | 
			
		||||
-71 34 -90 41 -110 109 -288 116 -305 5 -11 47 -114 94 -230 98 -243 277 -666
 | 
			
		||||
331 -780 34 -73 95 -207 95 -210 0 -2 25 -57 56 -122 30 -65 79 -167 107 -228
 | 
			
		||||
61 -130 433 -870 502 -1000 26 -49 70 -129 96 -177 27 -48 49 -89 49 -92 0 -5
 | 
			
		||||
269 -490 320 -576 133 -227 186 -319 200 -345 16 -29 294 -488 369 -608 20
 | 
			
		||||
-32 54 -87 76 -123 130 -210 627 -964 818 -1239 107 -156 301 -430 306 -435 4
 | 
			
		||||
-3 26 -34 51 -70 24 -36 50 -72 57 -80 6 -8 74 -100 150 -205 161 -222 635
 | 
			
		||||
-849 653 -865 3 -3 21 -25 40 -50 19 -25 40 -52 47 -60 7 -8 67 -85 135 -170
 | 
			
		||||
67 -85 128 -162 135 -170 7 -8 47 -58 89 -110 85 -105 105 -130 226 -276 45
 | 
			
		||||
-55 98 -119 116 -142 19 -23 42 -50 52 -62 10 -12 33 -39 52 -62 18 -23 38
 | 
			
		||||
-46 43 -52 6 -6 35 -40 65 -76 30 -36 57 -67 60 -70 3 -3 27 -32 54 -65 28
 | 
			
		||||
-33 68 -80 90 -105 23 -25 64 -72 92 -105 103 -120 136 -158 147 -170 7 -7 37
 | 
			
		||||
-41 67 -76 74 -86 220 -250 239 -270 9 -9 41 -44 71 -79 30 -34 66 -75 80 -90
 | 
			
		||||
72 -79 209 -228 235 -256 17 -17 47 -51 69 -75 43 -50 253 -273 396 -424 52
 | 
			
		||||
-55 106 -112 120 -126 224 -237 866 -885 1149 -1158 131 -127 410 -394 461
 | 
			
		||||
-441 19 -18 67 -62 106 -99 154 -145 372 -346 420 -388 12 -10 62 -55 112
 | 
			
		||||
-101 50 -45 109 -99 133 -120 23 -20 54 -48 69 -62 15 -14 56 -50 91 -80 35
 | 
			
		||||
-30 70 -62 79 -70 9 -8 47 -42 85 -75 39 -33 72 -63 75 -66 9 -9 411 -351 535
 | 
			
		||||
-454 66 -55 125 -104 131 -110 6 -5 28 -23 49 -40 21 -16 66 -53 99 -81 81
 | 
			
		||||
-67 432 -343 476 -374 19 -13 44 -33 55 -43 11 -10 55 -44 99 -75 43 -31 117
 | 
			
		||||
-85 165 -119 396 -286 662 -386 1006 -377 71 2 141 5 155 8 79 14 97 17 112
 | 
			
		||||
22 9 3 25 6 35 8 38 8 172 56 229 83 102 48 305 172 420 258 22 16 53 39 68
 | 
			
		||||
50 105 75 526 399 656 506 19 16 82 67 140 115 116 95 125 102 325 269 77 64
 | 
			
		||||
156 131 176 148 20 18 62 55 94 82 32 28 63 55 69 60 6 6 43 37 81 70 39 33
 | 
			
		||||
75 64 81 70 6 5 39 35 74 65 35 30 74 64 86 75 12 11 55 49 95 85 40 36 77 70
 | 
			
		||||
83 75 6 6 56 51 111 100 198 178 279 252 340 310 18 17 83 77 145 135 62 58
 | 
			
		||||
127 119 145 135 653 619 1260 1225 1825 1825 149 158 277 295 292 313 20 22
 | 
			
		||||
183 200 204 221 5 6 55 61 109 121 114 126 100 112 186 207 37 40 88 98 114
 | 
			
		||||
128 26 30 74 84 105 120 31 36 108 124 170 195 62 72 125 144 139 160 14 17
 | 
			
		||||
76 89 136 160 60 72 142 168 181 215 75 88 323 388 336 405 4 6 22 28 40 50
 | 
			
		||||
26 31 163 202 245 305 7 8 50 62 96 120 200 252 769 1001 857 1130 8 12 32 45
 | 
			
		||||
54 73 40 53 234 324 273 380 12 18 69 100 127 182 141 201 605 895 751 1125
 | 
			
		||||
202 317 566 913 677 1110 14 25 60 104 102 177 42 73 76 137 76 143 0 5 5 10
 | 
			
		||||
10 10 6 0 10 4 10 9 0 5 17 40 39 78 21 37 53 95 71 128 18 33 41 74 50 90 33
 | 
			
		||||
59 179 332 244 455 79 150 423 840 496 995 28 61 85 184 127 275 41 91 86 189
 | 
			
		||||
99 218 59 126 300 697 387 917 211 533 407 1083 552 1550 20 66 43 140 52 165
 | 
			
		||||
28 85 186 640 219 770 87 344 164 661 178 733 2 12 16 75 30 139 31 143 40
 | 
			
		||||
188 50 238 2 14 16 81 31 150 26 123 70 341 79 390 2 14 7 36 10 50 3 14 8 36
 | 
			
		||||
10 50 3 14 12 61 21 105 9 44 18 95 21 114 3 18 8 43 10 55 4 19 26 131 39
 | 
			
		||||
201 3 17 8 41 10 55 9 43 13 66 39 213 15 78 30 165 36 192 5 28 12 66 15 85
 | 
			
		||||
3 19 8 42 10 50 3 8 9 44 14 80 5 36 22 133 36 215 15 83 28 159 30 170 1 11
 | 
			
		||||
6 36 9 55 4 19 9 46 11 60 1 14 8 57 15 95 13 82 17 106 25 160 3 22 10 63 15
 | 
			
		||||
90 5 28 15 84 21 125 16 116 28 195 54 365 8 50 17 110 20 135 6 41 20 140 30
 | 
			
		||||
200 2 14 9 63 15 110 6 47 13 101 16 120 21 150 31 220 79 610 9 69 18 141 20
 | 
			
		||||
160 2 19 7 58 10 85 3 28 8 66 10 85 3 19 7 60 10 90 6 63 15 143 20 180 2 14
 | 
			
		||||
7 59 11 100 10 116 11 121 18 185 3 33 8 85 11 115 3 30 8 75 10 100 2 25 7
 | 
			
		||||
72 10 105 3 33 7 83 10 111 7 97 16 209 20 244 3 19 7 76 10 125 4 50 8 110
 | 
			
		||||
10 135 4 50 14 181 20 280 2 36 7 106 10 155 3 50 8 128 10 175 11 233 16 341
 | 
			
		||||
20 400 19 327 26 1432 12 1850 -12 349 -24 590 -42 815 -2 33 -7 94 -10 135
 | 
			
		||||
-3 41 -7 95 -10 120 -2 25 -7 74 -10 110 -5 65 -13 135 -20 200 -3 19 -7 62
 | 
			
		||||
-10 95 -3 33 -7 69 -9 80 -2 11 -7 49 -11 85 -4 36 -9 74 -11 85 -2 11 -6 43
 | 
			
		||||
-9 70 -3 28 -7 59 -9 70 -2 11 -9 61 -15 110 -7 50 -13 97 -15 105 -12 61 -18
 | 
			
		||||
101 -37 214 -11 72 -22 132 -24 135 -2 3 -6 31 -10 61 -4 30 -9 58 -11 61 -1
 | 
			
		||||
3 -17 74 -34 158 -34 165 -124 535 -160 652 -160 525 -301 869 -449 1094 -70
 | 
			
		||||
107 -221 268 -306 326 -27 19 -55 39 -62 46 -7 6 -68 39 -135 72 -122 60 -246
 | 
			
		||||
102 -343 116 -27 4 -61 9 -75 12 -14 3 -5675 6 -12581 7 -10052 1 -12574 -1
 | 
			
		||||
-12649 -11z"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 6.4 KiB  | 
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
const fs = require('fs');
 | 
			
		||||
const packageJson = require('../package.json');
 | 
			
		||||
 | 
			
		||||
const version = `${packageJson.version}+${fs.readFileSync('../.git/refs/heads/master').toString().trim().substring(0, 7)}`;
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-console
 | 
			
		||||
console.log(`Writing version ${version} to local .env file.`);
 | 
			
		||||
 | 
			
		||||
fs.writeFileSync('.env', `VITE_APP_VERSION=${version}`);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,67 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <MoleculeNavigationDrawer
 | 
			
		||||
    :drawer-id="drawerId"
 | 
			
		||||
    :navigation-entries="navigationEntries"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex min-h-screen flex-col items-stretch">
 | 
			
		||||
      <RouterView
 | 
			
		||||
        v-if="route.name != 'radio'"
 | 
			
		||||
        class="shrink-0 grow"
 | 
			
		||||
      />
 | 
			
		||||
      <ViewRadio
 | 
			
		||||
        v-show="route.name == 'radio'"
 | 
			
		||||
        class="shrink-0 grow"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <AtomFooter class="shrink-0" />
 | 
			
		||||
  </MoleculeNavigationDrawer>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { INavigationEntry } from './interfaces/navigation-entry.interface';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import {
 | 
			
		||||
  IdentificationIcon,
 | 
			
		||||
  BuildingLibraryIcon,
 | 
			
		||||
  MusicalNoteIcon,
 | 
			
		||||
  AdjustmentsHorizontalIcon,
 | 
			
		||||
  BriefcaseIcon,
 | 
			
		||||
} from '@heroicons/vue/24/outline';
 | 
			
		||||
import MoleculeNavigationDrawer from './components/molecules/MoleculeNavigationDrawer.vue';
 | 
			
		||||
import ViewRadio from './components/views/ViewRadio.vue';
 | 
			
		||||
import AtomFooter from './components/atoms/AtomFooter.vue';
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const drawerId = 'default-drawer';
 | 
			
		||||
const navigationEntries: INavigationEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Arbeitgeberportal',
 | 
			
		||||
    icon: BriefcaseIcon,
 | 
			
		||||
    to: '/employer',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Zoll',
 | 
			
		||||
    icon: IdentificationIcon,
 | 
			
		||||
    to: '/checkin',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Bank',
 | 
			
		||||
    icon: BuildingLibraryIcon,
 | 
			
		||||
    to: '/bank',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Radio',
 | 
			
		||||
    icon: MusicalNoteIcon,
 | 
			
		||||
    to: '/radio',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Staatsportal',
 | 
			
		||||
    icon: AdjustmentsHorizontalIcon,
 | 
			
		||||
    to: '/settings',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 6.8 KiB  | 
| 
						 | 
				
			
			@ -1,6 +0,0 @@
 | 
			
		|||
module.exports = {
 | 
			
		||||
  plugins: [
 | 
			
		||||
    'preset-default',
 | 
			
		||||
    'prefixIds',
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="card bg-base-100 shadow-xl">
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
      <h2 class="card-title"><slot name="title" /></h2>
 | 
			
		||||
      <p>
 | 
			
		||||
        <slot />
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,83 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="relative">
 | 
			
		||||
    <input
 | 
			
		||||
      v-model="modelValue"
 | 
			
		||||
      type="number"
 | 
			
		||||
      class="input w-full border-primary text-right"
 | 
			
		||||
      :class="{
 | 
			
		||||
        'pr-[5.5rem]': !perDay,
 | 
			
		||||
        'pr-[8rem]': perDay,
 | 
			
		||||
        '!border-error': error,
 | 
			
		||||
      }"
 | 
			
		||||
      step="1"
 | 
			
		||||
      :min="min"
 | 
			
		||||
      :max="max"
 | 
			
		||||
      @keyup="onKeyUp"
 | 
			
		||||
    />
 | 
			
		||||
    <span
 | 
			
		||||
      class="pointer-events-none absolute top-2/4 text-[1rem] opacity-50"
 | 
			
		||||
      :class="{
 | 
			
		||||
        '-mt-3': !suffixMargin,
 | 
			
		||||
        'mt-[-0.625rem]': suffixMargin,
 | 
			
		||||
        'ml-[-5.5rem]': !perDay,
 | 
			
		||||
        'ml-[-8rem]': perDay,
 | 
			
		||||
      }"
 | 
			
		||||
    >,00 Batzen{{ perDay ? ' / Tag' : '' }}</span>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
const input = ref<InstanceType<typeof HTMLInputElement>>();
 | 
			
		||||
const error = ref(false);
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
  suffixMargin?: boolean,
 | 
			
		||||
  perDay?: boolean,
 | 
			
		||||
  min?: number,
 | 
			
		||||
  max?: number,
 | 
			
		||||
  value?: number,
 | 
			
		||||
}>(), {
 | 
			
		||||
  suffixMargin: false,
 | 
			
		||||
  perHour: false,
 | 
			
		||||
  min: 1,
 | 
			
		||||
  max: 1_000_000,
 | 
			
		||||
  value: undefined,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/* global defineModel */
 | 
			
		||||
const modelValue = defineModel();
 | 
			
		||||
 | 
			
		||||
function onKeyUp(event: KeyboardEvent) {
 | 
			
		||||
  error.value = false;
 | 
			
		||||
  // eslint-disable-next-line vue/no-mutating-props
 | 
			
		||||
  const value = parseInt((event.target as HTMLInputElement).value);
 | 
			
		||||
 | 
			
		||||
  if(isNaN(value)) {
 | 
			
		||||
    error.value = true;
 | 
			
		||||
  } else if(value < props.min) {
 | 
			
		||||
    error.value = true;
 | 
			
		||||
  } else if(value > props.max) {
 | 
			
		||||
    error.value = true;
 | 
			
		||||
  } else {
 | 
			
		||||
    emit('change', value);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function reset() {
 | 
			
		||||
  if(input.value) {
 | 
			
		||||
    input.value.value = '';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  change: [value: number],
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  reset,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="relative">
 | 
			
		||||
    <AtomInput
 | 
			
		||||
      v-model="modelValue"
 | 
			
		||||
      type="number"
 | 
			
		||||
      class="pr-7 text-right"
 | 
			
		||||
      step="1"
 | 
			
		||||
      min="1"
 | 
			
		||||
    />
 | 
			
		||||
    <span class="pointer-events-none absolute top-2/4 -ml-6 -mt-3 opacity-50">x</span>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import AtomInput from './AtomInput.vue';
 | 
			
		||||
 | 
			
		||||
/* global defineModel */
 | 
			
		||||
const modelValue = defineModel();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <input
 | 
			
		||||
    type="file"
 | 
			
		||||
    class="file-input-bordered file-input-primary file-input"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,19 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <footer class="footer footer-center bg-base-100 p-4 text-base-content">
 | 
			
		||||
    <div>
 | 
			
		||||
      <p>Version: {{ version }}</p>
 | 
			
		||||
      <p>Made with ❤️ by Simon Giesel</p>
 | 
			
		||||
      <p>Copyright © 2023 - All rights reserved</p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </footer>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
const version = (import.meta as any).env.VITE_APP_VERSION as string ?? 'local+dev';
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <input
 | 
			
		||||
    v-model="modelValue"
 | 
			
		||||
    :type="type"
 | 
			
		||||
    :required="required"
 | 
			
		||||
    :placeholder="placeholder"
 | 
			
		||||
    class="input w-full border-primary"
 | 
			
		||||
    :class="{
 | 
			
		||||
      '!border-error': error,
 | 
			
		||||
    }"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
defineProps({
 | 
			
		||||
  type: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'text',
 | 
			
		||||
  },
 | 
			
		||||
  required: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  placeholder: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  error: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/* global defineModel */
 | 
			
		||||
const modelValue = defineModel();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,92 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <Teleport to="body">
 | 
			
		||||
    <input
 | 
			
		||||
      :id="id"
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
      class="modal-toggle"
 | 
			
		||||
      @change="($event.target as HTMLInputElement).checked ? $emit('close') : null"
 | 
			
		||||
    />
 | 
			
		||||
    <div class="modal">
 | 
			
		||||
      <form
 | 
			
		||||
        method="dialog"
 | 
			
		||||
        class="modal-box"
 | 
			
		||||
        :class="{
 | 
			
		||||
          'bg-base-300': darker,
 | 
			
		||||
          'w-auto': wAuto,
 | 
			
		||||
        }"
 | 
			
		||||
      >
 | 
			
		||||
        <button
 | 
			
		||||
          v-if="closeButton"
 | 
			
		||||
          class="btn-ghost btn-sm btn-circle btn absolute right-2 top-2"
 | 
			
		||||
        >
 | 
			
		||||
          ✕
 | 
			
		||||
        </button>
 | 
			
		||||
        <h3
 | 
			
		||||
          v-if="title"
 | 
			
		||||
          class="text-lg font-bold"
 | 
			
		||||
        >
 | 
			
		||||
          {{ title }}
 | 
			
		||||
        </h3>
 | 
			
		||||
        <p class="py-4"><slot /></p>
 | 
			
		||||
        <div class="modal-action">
 | 
			
		||||
          <slot name="action" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
      <form
 | 
			
		||||
        method="dialog"
 | 
			
		||||
        class="modal-backdrop"
 | 
			
		||||
      >
 | 
			
		||||
        <button>close</button>
 | 
			
		||||
      </form>
 | 
			
		||||
    </div>
 | 
			
		||||
  </Teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  darker: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  closeButton: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  wAuto: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function show(): void {
 | 
			
		||||
  (document.getElementById(props.id) as HTMLInputElement).checked = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function close(): void {
 | 
			
		||||
  (document.getElementById(props.id) as HTMLInputElement).checked = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isOpen(): boolean {
 | 
			
		||||
  return (document.getElementById(props.id) as HTMLInputElement).checked;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineEmits(['close']);
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show,
 | 
			
		||||
  close,
 | 
			
		||||
  isOpen,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <img
 | 
			
		||||
    src="../../assets/svg/logo.svg"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
// import { isDark } from '../../utils/darkMode';
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,97 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <Teleport to="body">
 | 
			
		||||
    <dialog
 | 
			
		||||
      :id="id"
 | 
			
		||||
      class="modal"
 | 
			
		||||
    >
 | 
			
		||||
      <form
 | 
			
		||||
        method="dialog"
 | 
			
		||||
        class="modal-box"
 | 
			
		||||
        :class="{
 | 
			
		||||
          'bg-base-300': darker,
 | 
			
		||||
          'w-auto': wAuto,
 | 
			
		||||
        }"
 | 
			
		||||
      >
 | 
			
		||||
        <button
 | 
			
		||||
          v-if="closeButton"
 | 
			
		||||
          class="btn-ghost btn-sm btn-circle btn absolute right-2 top-2"
 | 
			
		||||
        >
 | 
			
		||||
          ✕
 | 
			
		||||
        </button>
 | 
			
		||||
        <h3
 | 
			
		||||
          v-if="title"
 | 
			
		||||
          class="text-lg font-bold"
 | 
			
		||||
        >
 | 
			
		||||
          {{ title }}
 | 
			
		||||
        </h3>
 | 
			
		||||
        <p class="py-4"><slot /></p>
 | 
			
		||||
        <div class="modal-action">
 | 
			
		||||
          <slot name="action" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
      <form
 | 
			
		||||
        method="dialog"
 | 
			
		||||
        class="modal-backdrop"
 | 
			
		||||
      >
 | 
			
		||||
        <button>close</button>
 | 
			
		||||
      </form>
 | 
			
		||||
    </dialog>
 | 
			
		||||
  </Teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted } from 'vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  darker: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  closeButton: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  wAuto: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function show(): void {
 | 
			
		||||
  (document.getElementById(props.id) as HTMLDialogElement)?.showModal();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function close(): void {
 | 
			
		||||
  (document.getElementById(props.id) as HTMLDialogElement)?.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isOpen(): boolean {
 | 
			
		||||
  return (document.getElementById(props.id) as HTMLDialogElement)?.open;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  (document.getElementById(props.id) as HTMLDialogElement).addEventListener('close', () => {
 | 
			
		||||
    emit('close');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['close']);
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show,
 | 
			
		||||
  close,
 | 
			
		||||
  isOpen,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="relative">
 | 
			
		||||
    <AtomInput
 | 
			
		||||
      v-model="modelValue"
 | 
			
		||||
      type="number"
 | 
			
		||||
      class="pr-14 text-right"
 | 
			
		||||
      step="1"
 | 
			
		||||
      min="1"
 | 
			
		||||
    />
 | 
			
		||||
    <span class="pointer-events-none absolute top-2/4 -ml-14 -mt-3 opacity-50">,00 %</span>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import AtomInput from './AtomInput.vue';
 | 
			
		||||
 | 
			
		||||
/* global defineModel */
 | 
			
		||||
const modelValue = defineModel();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <textarea
 | 
			
		||||
    v-model="modelValue"
 | 
			
		||||
    class="textarea-primary textarea h-auto"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
 | 
			
		||||
/* global defineModel */
 | 
			
		||||
const modelValue = defineModel();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,44 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="hero min-h-full">
 | 
			
		||||
    <form
 | 
			
		||||
      class="hero-content mx-auto flex-col text-center"
 | 
			
		||||
      @submit.prevent="handleAuth"
 | 
			
		||||
    >
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        v-model="email"
 | 
			
		||||
        placeholder="Admin-Email"
 | 
			
		||||
        type="email"
 | 
			
		||||
        required
 | 
			
		||||
      />
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        v-model="password"
 | 
			
		||||
        placeholder="Passwort"
 | 
			
		||||
        type="password"
 | 
			
		||||
        required
 | 
			
		||||
      />
 | 
			
		||||
      <button class="btn-primary btn gap-2 self-end">
 | 
			
		||||
        <ArrowRightOnRectangleIcon class="h-6 w-6" />
 | 
			
		||||
        Login
 | 
			
		||||
      </button>
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { AuthService } from '../../services/auth.service';
 | 
			
		||||
import { ArrowRightOnRectangleIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomInput from '../atoms/AtomInput.vue';
 | 
			
		||||
 | 
			
		||||
const email = ref('');
 | 
			
		||||
const password = ref('');
 | 
			
		||||
 | 
			
		||||
async function handleAuth() {
 | 
			
		||||
  await AuthService.loginAsAdmin(email.value, password.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,63 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="hero min-h-full">
 | 
			
		||||
    <form
 | 
			
		||||
      class="hero-content mx-auto flex-col text-center"
 | 
			
		||||
      @submit.prevent="handleAuth"
 | 
			
		||||
    >
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        v-model="username"
 | 
			
		||||
        placeholder="Nutzername"
 | 
			
		||||
        type="text"
 | 
			
		||||
        :error="error"
 | 
			
		||||
        required
 | 
			
		||||
      />
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        v-model="password"
 | 
			
		||||
        placeholder="Passwort"
 | 
			
		||||
        type="password"
 | 
			
		||||
        :error="error"
 | 
			
		||||
        required
 | 
			
		||||
      />
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="error"
 | 
			
		||||
        class="alert alert-error flex w-[211px] items-center gap-2"
 | 
			
		||||
      >
 | 
			
		||||
        <XCircleIcon class="h-12 w-12" />
 | 
			
		||||
        <span class="text-base font-normal">Nutzername oder Passwort falsch.</span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <button class="btn-primary btn gap-2 self-end">
 | 
			
		||||
        <ArrowRightOnRectangleIcon class="h-6 w-6" />
 | 
			
		||||
        Login
 | 
			
		||||
      </button>
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, watch } from 'vue';
 | 
			
		||||
import { AuthService } from '../../services/auth.service';
 | 
			
		||||
import { ArrowRightOnRectangleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomInput from '../atoms/AtomInput.vue';
 | 
			
		||||
 | 
			
		||||
const username = ref('');
 | 
			
		||||
const password = ref('');
 | 
			
		||||
const error = ref(false);
 | 
			
		||||
 | 
			
		||||
async function handleAuth() {
 | 
			
		||||
  try {
 | 
			
		||||
    await AuthService.loginAsBanker(username.value, password.value);
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    error.value = true;
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch([username, password], () => {
 | 
			
		||||
  error.value = false;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,84 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <AtomModal
 | 
			
		||||
    :id="id"
 | 
			
		||||
    ref="modal"
 | 
			
		||||
    :title="title"
 | 
			
		||||
    @close="handleClose"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex place-items-center justify-between">
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        v-model="accountNumber"
 | 
			
		||||
        class="w-4/12"
 | 
			
		||||
        @keyup.esc="modal?.close()"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #action>
 | 
			
		||||
      <label
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        :for="id"
 | 
			
		||||
        @click="modal?.close()"
 | 
			
		||||
      >
 | 
			
		||||
        <XCircleIcon class="h-6 w-6" />
 | 
			
		||||
        Abbrechen
 | 
			
		||||
      </label>
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        @click="handleSubmit()"
 | 
			
		||||
      >
 | 
			
		||||
        <CheckCircleIcon class="h-6 w-6" />
 | 
			
		||||
        Speichern
 | 
			
		||||
      </button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </AtomModal>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomModal from '../atoms/AtomModal.vue';
 | 
			
		||||
import AtomInput from '../atoms/AtomInput.vue';
 | 
			
		||||
 | 
			
		||||
const modal = ref<InstanceType<typeof AtomModal>>();
 | 
			
		||||
const accountNumber = ref('');
 | 
			
		||||
const accountId = ref('');
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['submit']);
 | 
			
		||||
 | 
			
		||||
function handleClose() {
 | 
			
		||||
  accountNumber.value = '';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleSubmit() {
 | 
			
		||||
  if(!accountNumber.value) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  emit('submit', {
 | 
			
		||||
    id: accountId.value,
 | 
			
		||||
    accountNumber: accountNumber.value,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show: (id: string) => {
 | 
			
		||||
    accountId.value = id;
 | 
			
		||||
    modal.value?.show();
 | 
			
		||||
  },
 | 
			
		||||
  close: () => modal.value?.close(),
 | 
			
		||||
  isOpen: () => modal.value?.isOpen(),
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,166 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    v-if="settings"
 | 
			
		||||
    class="rounded-lg"
 | 
			
		||||
  >
 | 
			
		||||
    <table class="table-pin-rows table-zebra table">
 | 
			
		||||
      <thead
 | 
			
		||||
        v-if="!hideHeader"
 | 
			
		||||
        class="uppercase"
 | 
			
		||||
      >
 | 
			
		||||
        <tr>
 | 
			
		||||
          <th v-for="header in tableHeaders">{{ header.title }}</th>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody class="bg-base-100">
 | 
			
		||||
        <tr
 | 
			
		||||
          v-for="entry in data"
 | 
			
		||||
          @click="$emit('click', entry.id)"
 | 
			
		||||
        >
 | 
			
		||||
          <td
 | 
			
		||||
            v-for="header in tableHeaders"
 | 
			
		||||
            :class="{
 | 
			
		||||
              'text-center': [TableHeaderType.BUTTON_ACCOUNT, TableHeaderType.BUTTON_CHANGE_ACCOUNT_NUMBER].includes(header.type),
 | 
			
		||||
            }"
 | 
			
		||||
          >
 | 
			
		||||
            <span v-if="header.type === TableHeaderType.STRING">
 | 
			
		||||
              {{ entry[header.key] }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <span v-else-if="header.type === TableHeaderType.DATE">
 | 
			
		||||
              {{ DateService.toShortString(entry[header.key]) }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <span v-else-if="header.type === TableHeaderType.DATETIME">
 | 
			
		||||
              {{ entry[header.key] ? DateService.toString(parseISO(entry[header.key])) : 'N/A' }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <span v-else-if="header.type === TableHeaderType.CURRENCY">
 | 
			
		||||
              {{ CurrencyService.toString(entry[header.key]) }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <span v-else-if="header.type === TableHeaderType.SHIFT">
 | 
			
		||||
              <div class="dropdown-bottom dropdown">
 | 
			
		||||
                <label
 | 
			
		||||
                  tabindex="0"
 | 
			
		||||
                  class="btn bg-base-300 normal-case"
 | 
			
		||||
                >{{ entry[header.key] }}
 | 
			
		||||
                  <ChevronDownIcon class="h-4 w-4" />
 | 
			
		||||
                </label>
 | 
			
		||||
                <ul
 | 
			
		||||
                  tabindex="0"
 | 
			
		||||
                  class="dropdown-content menu rounded-box z-[1] w-52 bg-base-100 p-2 shadow"
 | 
			
		||||
                >
 | 
			
		||||
                  <li @click="$emit('selectShift', { id: entry.id, shift: Shifts.NONE }); removeFocus();"><a>{{
 | 
			
		||||
                    Shifts.NONE
 | 
			
		||||
                  }}</a></li>
 | 
			
		||||
                  <li><a @click="$emit('selectShift', { id: entry.id, shift: Shifts.EARLY }); removeFocus();">{{
 | 
			
		||||
                    Shifts.EARLY }}</a></li>
 | 
			
		||||
                  <li><a @click="$emit('selectShift', { id: entry.id, shift: Shifts.LATE }); removeFocus();">{{
 | 
			
		||||
                    Shifts.LATE
 | 
			
		||||
                  }}</a></li>
 | 
			
		||||
                </ul>
 | 
			
		||||
              </div>
 | 
			
		||||
            </span>
 | 
			
		||||
            <RouterLink
 | 
			
		||||
              v-if="header.type === TableHeaderType.BUTTON_ACCOUNT"
 | 
			
		||||
              :to="`/bank?accountNumber=${entry.accountNumber}`"
 | 
			
		||||
              class="btn-ghost btn-sm btn p-1"
 | 
			
		||||
            >
 | 
			
		||||
              <div
 | 
			
		||||
                class="tooltip normal-case"
 | 
			
		||||
                data-tip="Transaktionen anzeigen"
 | 
			
		||||
              >
 | 
			
		||||
                <CurrencyDollarIcon class="h-6 w-6" />
 | 
			
		||||
              </div>
 | 
			
		||||
            </RouterLink>
 | 
			
		||||
            <span
 | 
			
		||||
              v-if="header.type === TableHeaderType.BUTTON_CHANGE_ACCOUNT_NUMBER"
 | 
			
		||||
              class="btn-ghost btn-sm btn p-1"
 | 
			
		||||
              @click="$emit('changeAccountNumber', entry.id)"
 | 
			
		||||
            >
 | 
			
		||||
              <div
 | 
			
		||||
                class="tooltip normal-case"
 | 
			
		||||
                data-tip="Kontonummer ändern"
 | 
			
		||||
              >
 | 
			
		||||
                <CreditCardIcon class="h-6 w-6" />
 | 
			
		||||
              </div>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span
 | 
			
		||||
              v-if="header.type === TableHeaderType.WAGE"
 | 
			
		||||
              class="flex items-center gap-2"
 | 
			
		||||
            >
 | 
			
		||||
              <AtomCurrencyInput
 | 
			
		||||
                v-model="entry.wage"
 | 
			
		||||
                suffix-margin
 | 
			
		||||
                per-day
 | 
			
		||||
                class="min-w-[11rem]"
 | 
			
		||||
                :min="settings.minWage / 100"
 | 
			
		||||
                :max="settings.maxWage / 100"
 | 
			
		||||
                @change="$emit('selectWage', { id: entry.id, wage: $event })"
 | 
			
		||||
              />
 | 
			
		||||
            </span>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { PropType, onMounted, ref } from 'vue';
 | 
			
		||||
import { parseISO } from 'date-fns';
 | 
			
		||||
import { SettingsResponse } from '../../types/pocketbase.types';
 | 
			
		||||
import { DateService } from '../../services/date.service';
 | 
			
		||||
import { CurrencyService } from '../../services/currency.service';
 | 
			
		||||
import { SettingsService } from '../../services/settings.service';
 | 
			
		||||
import { Shifts } from '../../enums/shift.enum';
 | 
			
		||||
import { ChevronDownIcon, CreditCardIcon, CurrencyDollarIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
 | 
			
		||||
 | 
			
		||||
const settings = ref<SettingsResponse>();
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  tableHeaders: {
 | 
			
		||||
    type: Array as PropType<{ title: string, key: string, type: TableHeaderType }[]>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  data: {
 | 
			
		||||
    // Explicit allow any types as this is a dynamic list of data
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    type: Array as PropType<any[]>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  hideHeader: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    required: false,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  settings.value = await SettingsService.getSettings();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineEmits<{
 | 
			
		||||
  click: [id: string],
 | 
			
		||||
  selectShift: [{ id: string, shift: Shifts }],
 | 
			
		||||
  selectWage: [{ id: string, wage: number }],
 | 
			
		||||
  changeAccountNumber: [id: string],
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
function removeFocus() {
 | 
			
		||||
  (document.activeElement as HTMLElement).blur();
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
export enum TableHeaderType {
 | 
			
		||||
  STRING,
 | 
			
		||||
  DATE,
 | 
			
		||||
  CURRENCY,
 | 
			
		||||
  DATETIME,
 | 
			
		||||
  BUTTON_ACCOUNT,
 | 
			
		||||
  BUTTON_CHANGE_ACCOUNT_NUMBER,
 | 
			
		||||
  WAGE,
 | 
			
		||||
  SHIFT,
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,54 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <AtomModal
 | 
			
		||||
    :id="id"
 | 
			
		||||
    ref="modal"
 | 
			
		||||
    :title="title"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex flex-col gap-2">
 | 
			
		||||
      <AtomTextarea v-model="modelValue" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #action>
 | 
			
		||||
      <label
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        :for="id"
 | 
			
		||||
        @click="modal?.close()"
 | 
			
		||||
      >
 | 
			
		||||
        <XCircleIcon class="h-6 w-6" />
 | 
			
		||||
        Schließen
 | 
			
		||||
      </label>
 | 
			
		||||
    </template>
 | 
			
		||||
  </AtomModal>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { XCircleIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomModal from '../atoms/AtomModal.vue';
 | 
			
		||||
import AtomTextarea from '../atoms/AtomTextarea.vue';
 | 
			
		||||
 | 
			
		||||
const modal = ref<InstanceType<typeof AtomModal>>();
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show: () => modal.value?.show(),
 | 
			
		||||
  close: () => modal.value?.close(),
 | 
			
		||||
  isOpen: () => modal.value?.isOpen(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/* global defineModel */
 | 
			
		||||
const modelValue = defineModel();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,73 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <AtomModal
 | 
			
		||||
    :id="id"
 | 
			
		||||
    ref="modal"
 | 
			
		||||
    :title="title"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex flex-col gap-2">
 | 
			
		||||
      <AtomFileInput @change="file = ($event.target as HTMLInputElement)?.files?.[0]" />
 | 
			
		||||
      <p>Datei für den Import der Stammdaten im CSV-Format auswählen.<br /><b>! Achtung !</b> Die Datei muss eine Kopfzeile besitzen.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #action>
 | 
			
		||||
      <label
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        :for="id"
 | 
			
		||||
        @click="modal?.close()"
 | 
			
		||||
      >
 | 
			
		||||
        <XCircleIcon class="h-6 w-6" />
 | 
			
		||||
        Abbrechen
 | 
			
		||||
      </label>
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        @click="handleSubmit()"
 | 
			
		||||
      >
 | 
			
		||||
        <CheckCircleIcon class="h-6 w-6" />
 | 
			
		||||
        {{ actionLabel }}
 | 
			
		||||
      </button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </AtomModal>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomModal from '../atoms/AtomModal.vue';
 | 
			
		||||
import AtomFileInput from '../atoms/AtomFileInput.vue';
 | 
			
		||||
 | 
			
		||||
const modal = ref<InstanceType<typeof AtomModal>>();
 | 
			
		||||
const file = ref<File>();
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  actionLabel: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['submit']);
 | 
			
		||||
 | 
			
		||||
function handleSubmit() {
 | 
			
		||||
  if(!file.value) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  emit('submit', file.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show: () => modal.value?.show(),
 | 
			
		||||
  close: () => modal.value?.close(),
 | 
			
		||||
  isOpen: () => modal.value?.isOpen(),
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,86 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <AtomModal
 | 
			
		||||
    :id="id"
 | 
			
		||||
    ref="modal"
 | 
			
		||||
    :title="title"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex flex-col gap-4">
 | 
			
		||||
      <p>{{ description }}</p>
 | 
			
		||||
      <AtomInput
 | 
			
		||||
        v-model="modelValue"
 | 
			
		||||
        placeholder="Passwort"
 | 
			
		||||
        type="password"
 | 
			
		||||
        @keydown.prevent.enter="$emit('login')"
 | 
			
		||||
      />
 | 
			
		||||
      <p
 | 
			
		||||
        v-if="error"
 | 
			
		||||
        class="alert alert-error"
 | 
			
		||||
      >
 | 
			
		||||
        {{ error }}
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #action>
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        :for="id"
 | 
			
		||||
        @click="modal?.close()"
 | 
			
		||||
      >
 | 
			
		||||
        <XCircleIcon class="h-6 w-6" />
 | 
			
		||||
        Schließen
 | 
			
		||||
      </button>
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn gap-2"
 | 
			
		||||
        :for="id"
 | 
			
		||||
        @click.prevent="$emit('login')"
 | 
			
		||||
      >
 | 
			
		||||
        <ArrowRightOnRectangleIcon class="h-6 w-6" />
 | 
			
		||||
        Login
 | 
			
		||||
      </button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </AtomModal>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { ArrowRightOnRectangleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomModal from '../atoms/AtomLegacyModal.vue';
 | 
			
		||||
import AtomInput from '../atoms/AtomInput.vue';
 | 
			
		||||
 | 
			
		||||
const modal = ref<InstanceType<typeof AtomModal>>();
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  id: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  description: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  error: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show: () => modal.value?.show(),
 | 
			
		||||
  close: () => modal.value?.close(),
 | 
			
		||||
  isOpen: () => modal.value?.isOpen(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/* global defineModel */
 | 
			
		||||
const modelValue = defineModel();
 | 
			
		||||
 | 
			
		||||
defineEmits<{
 | 
			
		||||
  login: [],
 | 
			
		||||
}>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,157 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="drawer"
 | 
			
		||||
    :class="{
 | 
			
		||||
      'lg:drawer-open': !hideNavigation,
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <input
 | 
			
		||||
      :id="drawerId"
 | 
			
		||||
      ref="drawerCheckbox"
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
      class="drawer-toggle"
 | 
			
		||||
    />
 | 
			
		||||
    <div
 | 
			
		||||
      class="drawer-content flex flex-col bg-base-300"
 | 
			
		||||
      :class="{'lg:pl-64': !hideNavigation}"
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        class="navbar top-0 place-self-center shadow-lg"
 | 
			
		||||
        :class="{
 | 
			
		||||
          'lg:hidden': !hideNavigation,
 | 
			
		||||
        }"
 | 
			
		||||
      >
 | 
			
		||||
        <div class="flex-none">
 | 
			
		||||
          <label
 | 
			
		||||
            :for="drawerId"
 | 
			
		||||
            class="btn-ghost rounded-btn btn"
 | 
			
		||||
            :class="{
 | 
			
		||||
              'lg:hidden': !hideNavigation,
 | 
			
		||||
            }"
 | 
			
		||||
          >
 | 
			
		||||
            <Bars3Icon class="h-7 w-7" />
 | 
			
		||||
          </label>
 | 
			
		||||
          <AtomLogo class="h-10 px-4" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <slot />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="drawer-side !fixed z-50">
 | 
			
		||||
      <label
 | 
			
		||||
        :for="drawerId"
 | 
			
		||||
        class="drawer-overlay"
 | 
			
		||||
      />
 | 
			
		||||
      <ul class="menu h-full w-64 overflow-y-auto bg-base-100 p-4">
 | 
			
		||||
        <li class="pointer-events-none">
 | 
			
		||||
          <AtomLogo class="w-full" />
 | 
			
		||||
        </li>
 | 
			
		||||
        <template v-if="isAdmin">
 | 
			
		||||
          <li class="pointer-events-none rounded-md bg-warning uppercase text-warning-content">
 | 
			
		||||
            <span>
 | 
			
		||||
              <ExclamationTriangleIcon class="h-6 w-6" />
 | 
			
		||||
              Admin-Modus
 | 
			
		||||
              <ExclamationTriangleIcon class="h-6 w-6" />
 | 
			
		||||
            </span>
 | 
			
		||||
          </li>
 | 
			
		||||
          <li>
 | 
			
		||||
            <span @click="AuthService.logout()">
 | 
			
		||||
              <ArrowRightOnRectangleIcon class="h-6 w-6" />
 | 
			
		||||
              Abmelden
 | 
			
		||||
            </span>
 | 
			
		||||
          </li>
 | 
			
		||||
          <li class="menu-title !p-0" />
 | 
			
		||||
        </template>
 | 
			
		||||
        <template v-if="isBanker">
 | 
			
		||||
          <li class="pointer-events-none rounded-md bg-info text-warning-content">
 | 
			
		||||
            <span>
 | 
			
		||||
              Angemeldet als Banker
 | 
			
		||||
              <BuildingLibraryIcon class="h-6 w-6" />
 | 
			
		||||
            </span>
 | 
			
		||||
          </li>
 | 
			
		||||
          <li>
 | 
			
		||||
            <span @click="AuthService.logout()">
 | 
			
		||||
              <ArrowRightOnRectangleIcon class="h-6 w-6" />
 | 
			
		||||
              Abmelden
 | 
			
		||||
            </span>
 | 
			
		||||
          </li>
 | 
			
		||||
          <li class="menu-title !p-0" />
 | 
			
		||||
        </template>
 | 
			
		||||
        <li
 | 
			
		||||
          v-for="navigationEntry of navigationEntries"
 | 
			
		||||
          :class="getNavigationEntryClass(navigationEntry)"
 | 
			
		||||
        >
 | 
			
		||||
          <component
 | 
			
		||||
            :is="!navigationEntry.to || navigationEntry.disabled ? 'span' : 'router-link'"
 | 
			
		||||
            v-if="navigationEntry.name !== 'divider'"
 | 
			
		||||
            :to="navigationEntry.to"
 | 
			
		||||
            active-class="active"
 | 
			
		||||
          >
 | 
			
		||||
            <component
 | 
			
		||||
              :is="navigationEntry.icon"
 | 
			
		||||
              class="h-6 w-6"
 | 
			
		||||
            />{{ navigationEntry.name }}
 | 
			
		||||
          </component>
 | 
			
		||||
        </li>
 | 
			
		||||
        <AtomSwap
 | 
			
		||||
          class="mt-auto place-self-end"
 | 
			
		||||
          :on-icon="SunIcon"
 | 
			
		||||
          :off-icon="MoonIcon"
 | 
			
		||||
          :checked="isDark"
 | 
			
		||||
          @change="toggleDark()"
 | 
			
		||||
        />
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { PropType, onMounted, ref } from 'vue';
 | 
			
		||||
import { useEventBus } from '@vueuse/core';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { INavigationEntry } from '../../interfaces/navigation-entry.interface';
 | 
			
		||||
import { AuthService } from '../../services/auth.service';
 | 
			
		||||
import { isDark, toggleDark } from '../../utils/darkMode';
 | 
			
		||||
import { ArrowRightOnRectangleIcon, Bars3Icon, BuildingLibraryIcon, ExclamationTriangleIcon, MoonIcon, SunIcon } from '@heroicons/vue/24/outline';
 | 
			
		||||
import AtomLogo from '../atoms/AtomLogo.vue';
 | 
			
		||||
import AtomSwap from '../atoms/AtomSwap.vue';
 | 
			
		||||
 | 
			
		||||
const isAdmin = ref(false);
 | 
			
		||||
const isBanker = ref(false);
 | 
			
		||||
const hideNavigation = ref(false);
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const drawerCheckbox = ref();
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  drawerId: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  navigationEntries: {
 | 
			
		||||
    type: Array as PropType<INavigationEntry[]>,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function getNavigationEntryClass(navigationEntry: INavigationEntry) {
 | 
			
		||||
  return {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/naming-convention
 | 
			
		||||
    'menu-title !p-0': navigationEntry.name === 'divider',
 | 
			
		||||
    disabled: navigationEntry.disabled,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
useEventBus<boolean>('isAdmin').on(state => (isAdmin.value = state));
 | 
			
		||||
useEventBus<boolean>('isBanker').on(state => (isBanker.value = state));
 | 
			
		||||
useEventBus<boolean>('hideNavigation').on(state => (hideNavigation.value = state));
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  isAdmin.value = AuthService.isAdmin();
 | 
			
		||||
  isBanker.value = AuthService.isBanker();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.afterEach(() => {
 | 
			
		||||
  drawerCheckbox.value.checked = false;
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||