Compare commits

..

22 commits

Author SHA1 Message Date
caf1919930 1.4.1 2025-04-18 11:10:49 +02:00
d25fa07512 Merge pull request 'patches v1.4.1' (#85) from develop into main
Reviewed-on: #85
2025-04-18 09:10:26 +00:00
48502efc1d Merge branch 'main' into develop 2025-04-18 09:10:15 +00:00
9a9742597a fix: calendar height after custom calendar header 2025-04-17 15:34:27 +02:00
ea38b1835c 1.4.0 2025-04-16 17:02:28 +02:00
802b7d25f0 Merge pull request 'minor v1.4.0' (#84) from develop into main
Reviewed-on: #84
2025-04-16 15:01:07 +00:00
d1bde66e1e Merge branch 'main' into develop 2025-04-16 15:00:49 +00:00
dea2a1c40f Merge pull request 'feature/#41-query-builder-joins' (#83) from feature/#41-query-builder-joins into develop
Reviewed-on: #83
2025-04-16 14:39:42 +00:00
f94cc8b365 deactivate custom join switch
deactivated custom join switch as data is not passed down
2025-04-16 16:37:24 +02:00
238a35da9f extend query builder by custom join 2025-04-16 16:11:10 +02:00
d39ebc5029 Merge pull request '#40-query-builder-sorting' (#82) from #40-query-builder-sorting into develop
Reviewed-on: #82
2025-04-15 08:47:14 +00:00
9a7785917c show changes detected 2025-04-15 10:45:15 +02:00
fc1185d1c8 upgrade query to ids by default 2025-04-15 10:29:25 +02:00
68b0aeffa8 show correct columns to table 2025-04-15 10:02:30 +02:00
8087108b90 change order of sorts 2025-04-15 09:35:08 +02:00
5ce7aa8a17 add global sort 2025-04-15 09:26:25 +02:00
d018f97274 Merge pull request 'feature/#69-calendar-view' (#81) from feature/#69-calendar-view into develop
Reviewed-on: #81
2025-04-14 07:18:01 +00:00
6d45325543 add styling option for wide calendar view 2025-04-14 09:17:32 +02:00
1296331796 extend calendar by list 2025-04-14 09:12:00 +02:00
303ce7a58d Merge pull request 'version-update' (#80) from version-update into develop
Reviewed-on: #80
2025-04-13 14:39:34 +00:00
e4c2f47eb0 1.3.6 2025-04-10 12:48:38 +02:00
387736721f Merge pull request 'patches v1.3.6' (#78) from develop into main
Reviewed-on: #78
2025-04-10 10:47:10 +00:00
128 changed files with 649 additions and 7317 deletions

78
package-lock.json generated
View file

@ -1,17 +1,18 @@
{
"name": "ff-admin",
"version": "1.3.5",
"version": "1.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ff-admin",
"version": "1.3.5",
"version": "1.4.1",
"license": "AGPL-3.0-only",
"dependencies": {
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/list": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.15",
"@fullcalendar/vue3": "^6.1.15",
"@headlessui/vue": "^1.7.23",
@ -40,7 +41,6 @@
"unplugin-vue-markdown": "^28.3.1",
"uuid": "^11.1.0",
"vue": "^3.4.29",
"vue-qrcode-reader": "^5.7.1",
"vue-router": "^4.3.3"
},
"devDependencies": {
@ -2380,6 +2380,15 @@
"@fullcalendar/core": "~6.1.17"
}
},
"node_modules/@fullcalendar/list": {
"version": "6.1.17",
"resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.17.tgz",
"integrity": "sha512-fkyK49F9IxwlGUBVhJGsFpd/LTi/vRVERLIAe1HmBaGkjwpxnynm8TMLb9mZip97wvDk3CmZWduMe6PxscAlow==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.17"
}
},
"node_modules/@fullcalendar/timegrid": {
"version": "6.1.17",
"resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.17.tgz",
@ -3692,18 +3701,6 @@
"@types/underscore": "*"
}
},
"node_modules/@types/dom-webcodecs": {
"version": "0.1.14",
"resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.14.tgz",
"integrity": "sha512-ba9aF0qARLLQpLihONIRbj8VvAdUxO+5jIxlscVcDAQTcJmq5qVr781+ino5qbQUJUmO21cLP2eLeXYWzao5Vg==",
"license": "MIT"
},
"node_modules/@types/emscripten": {
"version": "1.40.1",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.40.1.tgz",
"integrity": "sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg==",
"license": "MIT"
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@ -4719,16 +4716,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/barcode-detector": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.2.2.tgz",
"integrity": "sha512-JcSekql+EV93evfzF9zBr+Y6aRfkR+QFvgyzbwQ0dbymZXoAI9+WgT7H1E429f+3RKNncHz2CW98VQtaaKpmfQ==",
"license": "MIT",
"dependencies": {
"@types/dom-webcodecs": "^0.1.11",
"zxing-wasm": "1.1.3"
}
},
"node_modules/birpc": {
"version": "0.2.19",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz",
@ -9264,12 +9251,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sdp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
"license": "MIT"
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@ -10787,19 +10768,6 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/vue-qrcode-reader": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.7.1.tgz",
"integrity": "sha512-7QBu3PqaPJHxobiDLqgcrp6wsjdTk9GJWhRCd4CgQYi93gBw/sIXNNWtbjeKz8d3QYj13n9dyPvcPMUcGOsBHw==",
"license": "MIT",
"dependencies": {
"barcode-detector": "2.2.2",
"webrtc-adapter": "8.2.3"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/vue-router": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz",
@ -10845,19 +10813,6 @@
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"license": "MIT"
},
"node_modules/webrtc-adapter": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz",
"integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==",
"license": "BSD-3-Clause",
"dependencies": {
"sdp": "^3.2.0"
},
"engines": {
"node": ">=6.0.0",
"npm": ">=3.10.0"
}
},
"node_modules/whatwg-url": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
@ -11564,15 +11519,6 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zxing-wasm": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-1.1.3.tgz",
"integrity": "sha512-MYm9k/5YVs4ZOTIFwlRjfFKD0crhefgbnt1+6TEpmKUDFp3E2uwqGSKwQOd2hOIsta/7Usq4hnpNRYTLoljnfA==",
"license": "MIT",
"dependencies": {
"@types/emscripten": "^1.39.10"
}
}
}
}

View file

@ -1,6 +1,6 @@
{
"name": "ff-admin",
"version": "1.3.5",
"version": "1.4.1",
"description": "Feuerwehr/Verein Mitgliederverwaltung UI",
"type": "module",
"scripts": {
@ -27,6 +27,7 @@
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/list": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.15",
"@fullcalendar/vue3": "^6.1.15",
"@headlessui/vue": "^1.7.23",
@ -55,7 +56,6 @@
"unplugin-vue-markdown": "^28.3.1",
"uuid": "^11.1.0",
"vue": "^3.4.29",
"vue-qrcode-reader": "^5.7.1",
"vue-router": "^4.3.3"
},
"devDependencies": {

View file

@ -1,77 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<XMarkIcon class="ml-auto mb-2 w-5 h-5 cursor-pointer" @click="closeModal" />
<qrcode-stream
:constraints="selectedCamera?.constraints"
:track="trackFunctionOptions[4].value"
:formats="barcodeFormats"
:paused="paused"
@error="onError"
@detect="onDetect"
@camera-on="onCameraReady"
/>
<br />
<select v-model="selectedCamera">
<option v-for="c in selecteableCameras" :value="c">{{ c.label }}</option>
</select>
<div class="flex flex-row justify-end gap-4 py-2">
<button primary-outline @click="paused = false" :disabled="!paused">weiter scannen</button>
<button primary-outline @click="commit">bestätigen</button>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "../stores/modal";
import {
barcodeFormats,
defaultConstraintOptions,
getAvailableCameras,
handleScannerError,
trackFunctionOptions,
type Camera,
} from "../helpers/codeScanner";
import { QrcodeStream, type DetectedBarcode } from "vue-qrcode-reader";
import { XMarkIcon } from "@heroicons/vue/24/outline";
</script>
<script lang="ts">
export default defineComponent({
props: {
callback: {
type: Function,
default: (result: string) => {},
},
},
data() {
return {
selecteableCameras: defaultConstraintOptions,
selectedCamera: undefined as undefined | Camera,
paused: false,
detected: "",
};
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
async onCameraReady() {
this.selecteableCameras = await getAvailableCameras();
if (!this.selectedCamera) {
this.selectedCamera = this.selecteableCameras[0];
}
},
onDetect(result: Array<DetectedBarcode>) {
this.paused = true;
this.detected = result.map((r) => r.rawValue)[0];
},
onError(err: Error) {
console.log(handleScannerError(err));
},
commit() {
this.callback(this.detected);
this.closeModal();
},
},
});
</script>

View file

@ -0,0 +1,181 @@
<template>
<div class="flex flex-col w-full h-full gap-2 justify-between overflow-hidden">
<div
class="flex flex-row gap-2 justify-between max-sm:justify-center"
:class="smallStyling ? 'max-lg:flex-wrap' : 'max-xl:flex-wrap'"
>
<div class="flex flex-row" :class="smallStyling ? 'max-lg:order-2' : 'max-xl:order-2'">
<button
:primary="view == 'dayGridMonth'"
:primary-outline="view != 'dayGridMonth'"
class="rounded-r-none!"
@click="setView('dayGridMonth')"
>
Monat
</button>
<button
:primary="view == 'timeGridWeek'"
:primary-outline="view != 'timeGridWeek'"
class="rounded-none! border-x-0!"
@click="setView('timeGridWeek')"
>
Woche
</button>
<button
:primary="view == 'listMonth'"
:primary-outline="view != 'listMonth'"
class="rounded-l-none!"
@click="setView('listMonth')"
>
Liste
</button>
</div>
<p class="text-3xl w-full text-center" :class="smallStyling ? 'max-lg:order-1' : 'max-xl:order-1'">
{{ currentTitle }}
</p>
<div class="flex flex-row" :class="smallStyling ? 'max-lg:order-3' : 'max-xl:order-3'">
<button primary-outline class="rounded-r-none!" @click="navigateView('prev')">
<ChevronLeftIcon />
</button>
<button
:primary="containsToday"
:primary-outline="!containsToday"
class="rounded-none! border-x-0!"
@click="
calendarApi?.today();
containsToday = true;
"
>
heute
</button>
<button primary-outline class="rounded-l-none!" @click="navigateView('next')">
<ChevronRightIcon />
</button>
</div>
</div>
<div class="flex flex-col w-full grow overflow-hidden">
<FullCalendar ref="fullCalendar" :options="calendarOptions" class="max-h-full h-full" />
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import FullCalendar from "@fullcalendar/vue3";
import deLocale from "@fullcalendar/core/locales/de";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from "@fullcalendar/list";
import interactionPlugin from "@fullcalendar/interaction";
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/vue/24/outline";
import type { CalendarOptions } from "@fullcalendar/core/index.js";
</script>
<script lang="ts">
export default defineComponent({
props: {
items: {
type: Array as PropType<
{
id: string;
title: string;
start: string;
end: string;
backgroundColor: string;
}[]
>,
default: [],
},
allowInteraction: {
type: Boolean,
default: true,
},
smallStyling: {
type: Boolean,
default: false,
},
},
emits: {
dateSelect: ({ start, end, allDay }: { start: string; end: string; allDay: boolean }) => {
return typeof start == "string" && typeof end == "string" && typeof allDay === "boolean";
},
eventSelect: (id: string) => {
return typeof id == "string";
},
},
data() {
return {
view: "dayGridMonth" as "dayGridMonth" | "timeGridWeek" | "listMonth",
calendarApi: null as null | typeof FullCalendar,
currentTitle: "" as string,
containsToday: false as boolean,
};
},
computed: {
calendarOptions() {
return {
timeZone: "local",
locale: deLocale,
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
initialView: "dayGridMonth",
eventDisplay: "block",
headerToolbar: false,
weekends: true,
editable: this.allowInteraction,
selectable: this.allowInteraction,
selectMirror: false,
dayMaxEvents: true,
weekNumbers: true,
displayEventTime: true,
nowIndicator: true,
weekText: "KW",
allDaySlot: false,
events: this.items,
select: this.select,
eventClick: this.eventClick,
} as CalendarOptions;
},
},
mounted() {
this.calendarApi = (this.$refs.fullCalendar as typeof FullCalendar).getApi();
this.setTitle();
this.setContainsToday();
},
methods: {
setTitle() {
this.currentTitle = this.calendarApi?.view.title ?? "";
},
setView(view: "dayGridMonth" | "timeGridWeek" | "listMonth") {
this.calendarApi?.changeView(view);
this.view = view;
this.setTitle();
this.setContainsToday();
},
navigateView(change: "prev" | "next") {
if (change == "prev") {
this.calendarApi?.prev();
} else {
this.calendarApi?.next();
}
this.setTitle();
this.setContainsToday();
},
setContainsToday() {
const start = this.calendarApi?.view.currentStart;
const end = this.calendarApi?.view.currentEnd;
const today = new Date();
this.containsToday = today >= start && today < end;
},
select(e: any) {
this.$emit("dateSelect", {
start: e?.startStr ?? new Date().toISOString(),
end: e?.endStr ?? new Date().toISOString(),
allDay: e?.allDay ?? false,
});
},
eventClick(e: any) {
this.$emit("eventSelect", e.event.id);
},
},
});
</script>

View file

@ -9,7 +9,6 @@
<component
:is="component_ref"
:data="data"
:callback="callback"
@click.stop
class="p-4 bg-white rounded-lg max-h-[95%] overflow-y-auto"
/>
@ -24,7 +23,7 @@ import { useModalStore } from "@/stores/modal";
<script lang="ts">
export default {
computed: {
...mapState(useModalStore, ["show", "component_ref", "data", "callback"]),
...mapState(useModalStore, ["show", "component_ref", "data"]),
},
methods: {
...mapActions(useModalStore, ["closeModal"]),

View file

@ -2,7 +2,6 @@
<div class="grow flex flex-col gap-2 overflow-hidden">
<div v-if="useSearch" class="relative self-end flex flex-row items-center gap-2">
<Spinner v-if="deferingSearch" />
<QrCodeIcon v-if="useScanner" class="h-7 cursor-pointer" @click="scanCode" />
<input
type="text"
class="max-w-64! w-64! rounded-md shadow-xs relative block px-3 py-2 pr-5 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-hidden focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
@ -67,12 +66,10 @@
</template>
<script setup lang="ts" generic="T extends { id: FieldType }">
import { computed, defineAsyncComponent, markRaw, ref, watch } from "vue";
import { computed, ref, watch } from "vue";
import { ChevronRightIcon, ChevronLeftIcon, XMarkIcon } from "@heroicons/vue/20/solid";
import Spinner from "./Spinner.vue";
import type { FieldType } from "@/types/dynamicQueries";
import { QrCodeIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "../stores/modal";
const props = defineProps({
items: { type: Array<T>, default: [] },
@ -82,7 +79,6 @@ const props = defineProps({
useSearch: { type: Boolean, default: false },
enablePreSearch: { type: Boolean, default: false },
indicateLoading: { type: Boolean, default: false },
useScanner: { type: Boolean, default: false },
});
const slots = defineSlots<{
@ -187,14 +183,84 @@ const filterData = (array: Array<any>, searchString: string, start: number, end:
)
.filter((elem, index) => (elem?.tab_pos ?? index) >= start && (elem?.tab_pos ?? index) < end);
};
function scanCode() {
useModalStore().openModal(
markRaw(defineAsyncComponent(() => import("@/components/CodeDetector.vue"))),
"pagination",
(result: string) => {
searchString.value = result;
}
);
}
</script>
<!--
<script lang="ts">
export default defineComponent({
computed: {
entryCount() {
return this.totalCount ?? this.items.length;
},
showingStart() {
return this.currentPage * this.maxEntriesPerPage;
},
showingEnd() {
let max = this.currentPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
if (max > this.entryCount) max = this.entryCount;
return max;
},
showingText() {
return `${this.entryCount != 0 ? this.showingStart + 1 : 0} - ${this.showingEnd}`;
},
countOfPages() {
return Math.ceil(this.entryCount / this.maxEntriesPerPage);
},
displayedPagesNumbers(): Array<number | "."> {
//indicate if "." or page number gets pushed
let stateOfPush = false;
return [...new Array(this.countOfPages)].reduce((acc, curr, index) => {
if (
// always display first 2 pages
index <= 1 ||
// always display last 2 pages
index >= this.countOfPages - 2 ||
// always display 1 pages around current page
(this.currentPage - 1 <= index && index <= this.currentPage + 1)
) {
acc.push(index);
stateOfPush = false;
return acc;
}
// abort if placeholder already added to array
if (stateOfPush == true) return acc;
// show placeholder if pagenumber is not actively rendered
acc.push(".");
stateOfPush = true;
return acc;
}, []);
},
visibleRows() {
return this.filterData(this.items, this.searchString, this.showingStart, this.showingEnd);
},
},
methods: {
loadPage(newPage: number | ".") {
if (newPage == ".") return;
if (newPage < 0 || newPage >= this.countOfPages) return;
let pageStart = newPage * this.maxEntriesPerPage;
let pageEnd = newPage * this.maxEntriesPerPage + this.maxEntriesPerPage;
if (pageEnd > this.entryCount) pageEnd = this.entryCount;
let loadedElementCount = this.filterData(this.items, this.searchString, pageStart, pageEnd).length;
if (loadedElementCount < this.maxEntriesPerPage)
this.$emit("loadData", { offset: pageStart, count: this.maxEntriesPerPage, search: this.searchString });
this.currentPage = newPage;
},
filterData(array: Array<any>, searchString: string, start: number, end: number): Array<any> {
return array
.filter(
(elem) =>
!this.enablePreSearch ||
searchString.trim() == "" ||
this.config.some((col) => typeof elem?.[col.key] == "string" && elem[col.key].includes(searchString.trim()))
)
.filter((elem, index) => (elem?.tab_pos ?? index) >= start && (elem?.tab_pos ?? index) < end);
},
},
});
</script> -->

View file

@ -1,57 +0,0 @@
<template>
<div>
<label :for="name">{{ label }}{{ required ? "" : " (optional)" }}</label>
<div class="relative flex flex-row items-center gap-2">
<input ref="resultInput" class="pl-9!" :id="name" type="text" v-model="value" :required="required" />
<QrCodeIcon class="absolute h-6 stroke-1 left-2 top-1/2 -translate-y-1/2 cursor-pointer z-10" @click="scanCode" />
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent, markRaw, defineAsyncComponent } from "vue";
import { QrCodeIcon } from "@heroicons/vue/24/outline";
import { useModalStore } from "../stores/modal";
import { mapActions } from "pinia";
</script>
<script lang="ts">
export default defineComponent({
props: {
label: String,
name: String,
required: {
type: Boolean,
default: true,
},
modelValue: {
type: String,
default: "",
required: false,
},
},
emits: ["update:model-value"],
computed: {
value: {
get() {
return this.modelValue;
},
set(val: String) {
this.$emit("update:model-value", val);
},
},
},
methods: {
...mapActions(useModalStore, ["openModal"]),
scanCode() {
this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/CodeDetector.vue"))),
"codeScanInput",
(result: string) => {
(this.$refs.resultInput as HTMLInputElement).value = result;
}
);
},
},
});
</script>

View file

@ -1,31 +0,0 @@
<template>
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ damageReport.affectedEquipment.name }}
</p>
</div>
<div class="p-2">
<p v-if="damageReport.affectedEquipment">Code: {{ damageReport.affectedEquipment.code }}</p>
<p v-if="damageReport.description">Beschreibung: {{ damageReport.description }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport/damageReport.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
damageReport: { type: Object as PropType<DamageReportViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,33 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-equipment-overview', params: { equipmentId: equipment.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ equipment.name }}
</p>
</div>
<div class="p-2">
<p v-if="equipment.code">Code: {{ equipment.code }}</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { EquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
equipment: { type: Object as PropType<EquipmentViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,82 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Typ erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
<div>
<label for="type">Typ</label>
<input type="text" id="type" required />
</div>
<div>
<label for="description">Beschreibung (optional)</label>
<textarea id="description" class="h-18"></textarea>
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipmentType";
import type { CreateEquipmentTypeViewModel } from "@/viewmodels/admin/unit/equipmentType/equipmentType.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
};
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useEquipmentTypeStore, ["createEquipmentType"]),
triggerCreate(e: any) {
let formData = e.target.elements;
let createEquipmentType: CreateEquipmentTypeViewModel = {
type: formData.type.value,
description: formData.description.value,
};
this.status = "loading";
this.createEquipmentType(createEquipmentType)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,84 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Ausrüstung-Type löschen</p>
</div>
<br />
<p class="text-center">Type {{ equipmentType?.type }} löschen?</p>
<br />
<div class="flex flex-row gap-2">
<button
primary
type="submit"
:disabled="status == 'loading' || status?.status == 'success'"
@click="triggerDelete"
>
löschen
</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipmentType";
import type { CreateEquipmentTypeViewModel } from "@/viewmodels/admin/unit/equipmentType/equipmentType.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
};
},
computed: {
...mapState(useModalStore, ["data"]),
...mapState(useEquipmentTypeStore, ["equipmentTypes"]),
equipmentType() {
return this.equipmentTypes.find((m) => m.id == this.data);
},
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useEquipmentTypeStore, ["deleteEquipmentType"]),
triggerDelete() {
this.status = "loading";
this.deleteEquipmentType(this.data)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.$router.push({ name: "admin-unit-equipment_type" });
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,30 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-equipment_type-overview', params: { equipmentTypeId: equipmentType.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ equipmentType.type }}
</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { EquipmentTypeViewModel } from "@/viewmodels/admin/unit/equipmentType/equipmentType.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
equipmentType: { type: Object as PropType<EquipmentTypeViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,33 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-inspection_plan-overview', params: { inspectionPlanId: inspectionPlan.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ inspectionPlan.title }}
</p>
</div>
<div class="p-2">
<p v-if="inspectionPlan">Code: {{ inspectionPlan }}</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { InspectionPlanViewModel } from "@/viewmodels/admin/unit/inspectionPlan/inspectionPlan.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
inspectionPlan: { type: Object as PropType<InspectionPlanViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,155 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Mitglied erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
<div>
<Listbox v-model="selectedSalutation" name="salutation" by="id">
<ListboxLabel>Anrede</ListboxLabel>
<div class="relative mt-1">
<ListboxButton
class="rounded-md shadow-xs relative block w-full px-3 py-2 border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-hidden focus:ring-0 focus:z-10 sm:text-sm resize-none"
>
<span class="block truncate w-full text-start"> {{ selectedSalutation?.salutation }}</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>
<transition
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute mt-1 max-h-60 z-20 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-hidden sm:text-sm h-32 overflow-y-auto"
>
<ListboxOption
v-slot="{ active, selected }"
v-for="salutation in salutations"
:key="salutation.id"
:value="salutation"
as="template"
>
<li
:class="[
active ? 'bg-red-200 text-amber-900' : 'text-gray-900',
'relative cursor-default select-none py-2 pl-10 pr-4',
]"
>
<span :class="[selected ? 'font-medium' : 'font-normal', 'block truncate']">{{
salutation.salutation
}}</span>
<span v-if="selected" class="absolute inset-y-0 left-0 flex items-center pl-3 text-primary">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</div>
<div>
<label for="firstname">Vorname</label>
<input type="text" id="firstname" required />
</div>
<div>
<label for="lastname">Nachname</label>
<input type="text" id="lastname" required />
</div>
<div>
<label for="nameaffix">Nameaffix (optional)</label>
<input type="text" id="nameaffix" />
</div>
<div>
<label for="birthdate">Geburtsdatum</label>
<input type="date" id="birthdate" required />
</div>
<div>
<label for="internalId">Interne ID (optional)</label>
<input type="text" id="internalId" />
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
import type { CreateEquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
import { useSalutationStore } from "../../../../stores/admin/configuration/salutation";
import type { SalutationViewModel } from "../../../../viewmodels/admin/configuration/salutation.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
selectedSalutation: null as null | SalutationViewModel,
};
},
computed: {
...mapState(useSalutationStore, ["salutations"]),
},
mounted() {
this.fetchSalutations();
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useEquipmentStore, ["createEquipment"]),
...mapActions(useSalutationStore, ["fetchSalutations"]),
triggerCreate(e: any) {
if (!this.selectedSalutation) return;
let formData = e.target.elements;
let createEquipment: CreateEquipmentViewModel = {
code: "",
name: "",
location: "",
equipmentTypeId: "",
};
this.status = "loading";
this.createEquipment(createEquipment)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,33 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-respiratory_gear-overview', params: { respiratoryGearId: respiratoryGear.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ respiratoryGear.equipment.name }}
</p>
</div>
<div class="p-2">
<p v-if="respiratoryGear.equipment">Code: {{ respiratoryGear.equipment.code }}</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { RespiratoryGearViewModel } from "@/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
respiratoryGear: { type: Object as PropType<RespiratoryGearViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,155 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Mitglied erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
<div>
<Listbox v-model="selectedSalutation" name="salutation" by="id">
<ListboxLabel>Anrede</ListboxLabel>
<div class="relative mt-1">
<ListboxButton
class="rounded-md shadow-xs relative block w-full px-3 py-2 border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-hidden focus:ring-0 focus:z-10 sm:text-sm resize-none"
>
<span class="block truncate w-full text-start"> {{ selectedSalutation?.salutation }}</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>
<transition
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute mt-1 max-h-60 z-20 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-hidden sm:text-sm h-32 overflow-y-auto"
>
<ListboxOption
v-slot="{ active, selected }"
v-for="salutation in salutations"
:key="salutation.id"
:value="salutation"
as="template"
>
<li
:class="[
active ? 'bg-red-200 text-amber-900' : 'text-gray-900',
'relative cursor-default select-none py-2 pl-10 pr-4',
]"
>
<span :class="[selected ? 'font-medium' : 'font-normal', 'block truncate']">{{
salutation.salutation
}}</span>
<span v-if="selected" class="absolute inset-y-0 left-0 flex items-center pl-3 text-primary">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</div>
<div>
<label for="firstname">Vorname</label>
<input type="text" id="firstname" required />
</div>
<div>
<label for="lastname">Nachname</label>
<input type="text" id="lastname" required />
</div>
<div>
<label for="nameaffix">Nameaffix (optional)</label>
<input type="text" id="nameaffix" />
</div>
<div>
<label for="birthdate">Geburtsdatum</label>
<input type="date" id="birthdate" required />
</div>
<div>
<label for="internalId">Interne ID (optional)</label>
<input type="text" id="internalId" />
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
import type { CreateEquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
import { useSalutationStore } from "../../../../stores/admin/configuration/salutation";
import type { SalutationViewModel } from "../../../../viewmodels/admin/configuration/salutation.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
selectedSalutation: null as null | SalutationViewModel,
};
},
computed: {
...mapState(useSalutationStore, ["salutations"]),
},
mounted() {
this.fetchSalutations();
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useEquipmentStore, ["createEquipment"]),
...mapActions(useSalutationStore, ["fetchSalutations"]),
triggerCreate(e: any) {
if (!this.selectedSalutation) return;
let formData = e.target.elements;
let createEquipment: CreateEquipmentViewModel = {
code: "",
name: "",
location: "",
equipmentTypeId: "",
};
this.status = "loading";
this.createEquipment(createEquipment)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,31 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-respiratory_mission-overview', params: { respiratoryMissionId: respiratoryMission.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>{{ respiratoryMission.title }} - {{ respiratoryMission.date }}</p>
</div>
<div class="p-2">
<p v-if="respiratoryMission.description">{{ respiratoryMission.description }}</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { RespiratoryMissionViewModel } from "@/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
respiratoryMission: { type: Object as PropType<RespiratoryMissionViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,155 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Mitglied erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
<div>
<Listbox v-model="selectedSalutation" name="salutation" by="id">
<ListboxLabel>Anrede</ListboxLabel>
<div class="relative mt-1">
<ListboxButton
class="rounded-md shadow-xs relative block w-full px-3 py-2 border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-hidden focus:ring-0 focus:z-10 sm:text-sm resize-none"
>
<span class="block truncate w-full text-start"> {{ selectedSalutation?.salutation }}</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>
<transition
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute mt-1 max-h-60 z-20 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-hidden sm:text-sm h-32 overflow-y-auto"
>
<ListboxOption
v-slot="{ active, selected }"
v-for="salutation in salutations"
:key="salutation.id"
:value="salutation"
as="template"
>
<li
:class="[
active ? 'bg-red-200 text-amber-900' : 'text-gray-900',
'relative cursor-default select-none py-2 pl-10 pr-4',
]"
>
<span :class="[selected ? 'font-medium' : 'font-normal', 'block truncate']">{{
salutation.salutation
}}</span>
<span v-if="selected" class="absolute inset-y-0 left-0 flex items-center pl-3 text-primary">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</div>
<div>
<label for="firstname">Vorname</label>
<input type="text" id="firstname" required />
</div>
<div>
<label for="lastname">Nachname</label>
<input type="text" id="lastname" required />
</div>
<div>
<label for="nameaffix">Nameaffix (optional)</label>
<input type="text" id="nameaffix" />
</div>
<div>
<label for="birthdate">Geburtsdatum</label>
<input type="date" id="birthdate" required />
</div>
<div>
<label for="internalId">Interne ID (optional)</label>
<input type="text" id="internalId" />
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
import type { CreateEquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
import { useSalutationStore } from "../../../../stores/admin/configuration/salutation";
import type { SalutationViewModel } from "../../../../viewmodels/admin/configuration/salutation.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
selectedSalutation: null as null | SalutationViewModel,
};
},
computed: {
...mapState(useSalutationStore, ["salutations"]),
},
mounted() {
this.fetchSalutations();
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useEquipmentStore, ["createEquipment"]),
...mapActions(useSalutationStore, ["fetchSalutations"]),
triggerCreate(e: any) {
if (!this.selectedSalutation) return;
let formData = e.target.elements;
let createEquipment: CreateEquipmentViewModel = {
code: "",
name: "",
location: "",
equipmentTypeId: "",
};
this.status = "loading";
this.createEquipment(createEquipment)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,34 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-respiratory_wearer-overview', params: { respiratoryWearerId: respiratoryWearer.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ respiratoryWearer.member.lastname }}, {{ respiratoryWearer.member.firstname }}
{{ respiratoryWearer.member.nameaffix ? `- ${respiratoryWearer.member.nameaffix}` : "" }}
</p>
</div>
<div class="p-2">
<p v-if="respiratoryWearer.internalId">ID: {{ respiratoryWearer.member.internalId }}</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { RespiratoryWearerViewModel } from "@/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
respiratoryWearer: { type: Object as PropType<RespiratoryWearerViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,103 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Mitglied erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
<div>
<label for="firstname">Vorname</label>
<input type="text" id="firstname" required />
</div>
<div>
<label for="lastname">Nachname</label>
<input type="text" id="lastname" required />
</div>
<div>
<label for="nameaffix">Nameaffix (optional)</label>
<input type="text" id="nameaffix" />
</div>
<div>
<label for="birthdate">Geburtsdatum</label>
<input type="date" id="birthdate" required />
</div>
<div>
<label for="internalId">Interne ID (optional)</label>
<input type="text" id="internalId" />
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { useVehicleStore } from "@/stores/admin/unit/vehicle/vehicle";
import type { CreateVehicleViewModel } from "@/viewmodels/admin/unit/vehicle/vehicle.models";
import { useSalutationStore } from "../../../../stores/admin/configuration/salutation";
import type { SalutationViewModel } from "../../../../viewmodels/admin/configuration/salutation.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
};
},
computed: {
...mapState(useSalutationStore, ["salutations"]),
},
mounted() {
this.fetchSalutations();
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useVehicleStore, ["createVehicle"]),
...mapActions(useSalutationStore, ["fetchSalutations"]),
triggerCreate(e: any) {
let formData = e.target.elements;
let createVehicle: CreateVehicleViewModel = {
name: "",
};
this.status = "loading";
this.createVehicle(createVehicle)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,28 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-vehicle-overview', params: { vehicleId: vehicle.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>{{ vehicle.name }}</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { VehicleViewModel } from "@/viewmodels/admin/unit/vehicle/vehicle.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
vehicle: { type: Object as PropType<VehicleViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,82 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Typ erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
<div>
<label for="type">Typ</label>
<input type="text" id="type" required />
</div>
<div>
<label for="description">Beschreibung (optional)</label>
<textarea id="description" class="h-18"></textarea>
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useVehicleTypeStore } from "@/stores/admin/unit/vehicleType/vehicleType";
import type { CreateVehicleTypeViewModel } from "@/viewmodels/admin/unit/vehicleType/vehicleType.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
};
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useVehicleTypeStore, ["createVehicleType"]),
triggerCreate(e: any) {
let formData = e.target.elements;
let createVehicleType: CreateVehicleTypeViewModel = {
type: formData.type.value,
description: formData.description.value,
};
this.status = "loading";
this.createVehicleType(createVehicleType)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,83 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Ausrüstung-Type löschen</p>
</div>
<br />
<p class="text-center">Type {{ vehicleType?.type }} löschen?</p>
<br />
<div class="flex flex-row gap-2">
<button
primary
type="submit"
:disabled="status == 'loading' || status?.status == 'success'"
@click="triggerDelete"
>
löschen
</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useVehicleTypeStore } from "@/stores/admin/unit/vehicleType/vehicleType";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
};
},
computed: {
...mapState(useModalStore, ["data"]),
...mapState(useVehicleTypeStore, ["vehicleTypes"]),
vehicleType() {
return this.vehicleTypes.find((m) => m.id == this.data);
},
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useVehicleTypeStore, ["deleteVehicleType"]),
triggerDelete() {
this.status = "loading";
this.deleteVehicleType(this.data)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.$router.push({ name: "admin-unit-vehicle_type" });
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,30 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-vehicle_type-overview', params: { vehicleTypeId: vehicleType.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ vehicleType.type }}
</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { VehicleTypeViewModel } from "@/viewmodels/admin/unit/vehicleType/vehicleType.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
vehicleType: { type: Object as PropType<VehicleTypeViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,33 +0,0 @@
<template>
<RouterLink
:to="{ name: 'admin-unit-wearable-overview', params: { wearableId: wearable.id } }"
class="flex flex-col h-fit w-full border border-primary rounded-md"
>
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ wearable.name }}
</p>
</div>
<div class="p-2">
<p v-if="wearable.code">Code: {{ wearable.code }}</p>
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { WearableViewModel } from "@/viewmodels/admin/unit/wearable/wearable.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
wearable: { type: Object as PropType<WearableViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -1,82 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Typ erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreate">
<div>
<label for="type">Typ</label>
<input type="text" id="type" required />
</div>
<div>
<label for="description">Beschreibung (optional)</label>
<textarea id="description" class="h-18"></textarea>
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="status == 'loading' || status?.status == 'success'">erstellen</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useWearableTypeStore } from "@/stores/admin/unit/wearableType/wearableType";
import type { CreateWearableTypeViewModel } from "@/viewmodels/admin/unit/wearableType/wearableType.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
};
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useWearableTypeStore, ["createWearableType"]),
triggerCreate(e: any) {
let formData = e.target.elements;
let createWearableType: CreateWearableTypeViewModel = {
type: formData.type.value,
description: formData.description.value,
};
this.status = "loading";
this.createWearableType(createWearableType)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,84 +0,0 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Ausrüstung-Type löschen</p>
</div>
<br />
<p class="text-center">Type {{ wearableType?.type }} löschen?</p>
<br />
<div class="flex flex-row gap-2">
<button
primary
type="submit"
:disabled="status == 'loading' || status?.status == 'success'"
@click="triggerDelete"
>
löschen
</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal" :disabled="status == 'loading' || status?.status == 'success'">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useWearableTypeStore } from "@/stores/admin/unit/wearableType/wearableType";
import type { CreateWearableTypeViewModel } from "@/viewmodels/admin/unit/wearableType/wearableType.models";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: undefined as any,
};
},
computed: {
...mapState(useModalStore, ["data"]),
...mapState(useWearableTypeStore, ["wearableTypes"]),
wearableType() {
return this.wearableTypes.find((m) => m.id == this.data);
},
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useWearableTypeStore, ["deleteWearableType"]),
triggerDelete() {
this.status = "loading";
this.deleteWearableType(this.data)
.then(() => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.$router.push({ name: "admin-unit-wearable_type" });
this.closeModal();
}, 1500);
})
.catch(() => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,27 +0,0 @@
<template>
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>
{{ wearableType.type }}
</p>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import type { WearableTypeViewModel } from "@/viewmodels/admin/unit/wearableType/wearableType.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
wearableType: { type: Object as PropType<WearableTypeViewModel>, default: {} },
},
computed: {
...mapState(useAbilityStore, ["can"]),
},
});
</script>

View file

@ -30,6 +30,9 @@
v-if="allowPredefinedSelect && can('read', 'configuration', 'query_store')"
class="flex flex-row gap-2 max-lg:w-full max-lg:order-10"
>
<div v-if="!isAsStored" class="p-1 border border-gray-400 bg-gray-100 rounded-md" title="Änderung erkannt">
<DocumentCurrencyRupeeIcon class="text-gray-500 h-6 w-6 cursor-pointer" />
</div>
<select v-model="activeQueryId" class="max-h-[34px] py-0!">
<option :value="undefined" disabled>gepeicherte Anfrage auswählen</option>
<option v-for="query in queries" :key="query.id" :value="query.id">
@ -70,7 +73,7 @@
</div>
<div class="p-2 h-44 md:h-60 w-full overflow-y-auto">
<textarea v-if="typeof value == 'string'" v-model="value" placeholder="SQL Query" class="h-full w-full" />
<Table v-else v-model="value" />
<Table v-else v-model="value" enableOrder />
</div>
</div>
</template>
@ -78,7 +81,7 @@
<script setup lang="ts">
import { defineAsyncComponent, defineComponent, markRaw, type PropType } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia";
import type { DynamicQueryStructure } from "@/types/dynamicQueries";
import { type DynamicQueryStructure } from "@/types/dynamicQueries";
import {
ArchiveBoxArrowDownIcon,
CommandLineIcon,
@ -88,12 +91,15 @@ import {
RectangleGroupIcon,
TrashIcon,
SparklesIcon,
DocumentCurrencyRupeeIcon,
} from "@heroicons/vue/24/outline";
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
import { useModalStore } from "@/stores/modal";
import Table from "./Table.vue";
import { useAbilityStore } from "@/stores/ability";
import { useQueryStoreStore } from "@/stores/admin/configuration/queryStore";
import { v4 as uuid } from "uuid";
import cloneDeep from "lodash.clonedeep";
</script>
<script lang="ts">
@ -102,6 +108,7 @@ export default defineComponent({
modelValue: {
type: [Object, String] as PropType<DynamicQueryStructure | string>,
default: {
id: uuid(),
select: "*",
table: "",
where: [],
@ -126,7 +133,12 @@ export default defineComponent({
} else {
this.queryMode = "builder";
}
this.value = query;
this.value = cloneDeep(query);
}
},
value() {
if (typeof this.value != "string" && !this.value.id) {
this.value.id = uuid();
}
},
},
@ -149,6 +161,11 @@ export default defineComponent({
this.$emit("update:model-value", val);
},
},
isAsStored() {
let stored = this.queries.find((q) => q.id == this.activeQueryId);
if (!stored) return true;
return JSON.stringify(this.value) == JSON.stringify(stored.query);
},
},
mounted() {
this.fetchTableMetas();
@ -163,6 +180,7 @@ export default defineComponent({
this.activeQueryId = undefined;
if (typeof this.value != "string") {
this.value = {
id: uuid(),
select: "*",
table: "",
where: [],
@ -182,6 +200,7 @@ export default defineComponent({
this.activeQueryId = undefined;
if (this.queryMode == "builder") {
this.value = {
id: uuid(),
select: "*",
table: "",
where: [],

View file

@ -1,6 +1,6 @@
<template>
<div class="flex flex-row gap-2 items-center w-full">
<select v-if="concat != '_'" v-model="concat" class="w-20! h-fit!">
<select v-if="!isFirst" v-model="concat" class="w-20! h-fit!">
<option value="" disabled>Verknüpfung auswählen</option>
<option v-for="operation in ['AND', 'OR']" :value="operation">
{{ operation }}
@ -68,6 +68,10 @@ import { TrashIcon } from "@heroicons/vue/24/outline";
<script lang="ts">
export default defineComponent({
props: {
isFirst: {
type: Boolean,
default: false,
},
table: {
type: String,
default: "",
@ -78,9 +82,6 @@ export default defineComponent({
},
},
emits: ["update:model-value", "remove"],
data() {
return {};
},
computed: {
...mapState(useQueryBuilderStore, ["tableMetas"]),
activeTable() {
@ -144,5 +145,10 @@ export default defineComponent({
},
},
},
mounted() {
if (this.concat == "_") {
this.concat = "AND";
}
},
});
</script>

View file

@ -1,12 +1,13 @@
<template>
<div class="flex flex-row gap-2">
<p class="w-14 min-w-14 pt-2">JOIN</p>
<p class="w-14 min-w-14 pt-2">I_JOIN</p>
<div class="flex flex-row flex-wrap gap-2 items-center w-full">
<div class="flex flex-row flex-wrap gap-2 items-center justify-end w-full">
<JoinTable
v-for="(join, index) in value"
:model-value="join"
:table="table"
:alreadyJoined="alreadyJoined"
@update:model-value="($event) => (value[index] = $event)"
@remove="removeAtIndex(index)"
/>
@ -21,10 +22,11 @@
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapActions, mapState } from "pinia";
import type { DynamicQueryStructure } from "@/types/dynamicQueries";
import { type DynamicQueryStructure, type JoinStructure } from "@/types/dynamicQueries";
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
import { PlusIcon } from "@heroicons/vue/24/outline";
import JoinTable from "./JoinTable.vue";
import { v4 as uuid } from "uuid";
</script>
<script lang="ts">
@ -35,24 +37,22 @@ export default defineComponent({
default: "",
},
modelValue: {
type: Array as PropType<Array<DynamicQueryStructure & { foreignColumn: string }>>,
type: Array as PropType<Array<DynamicQueryStructure & JoinStructure>>,
default: [],
},
alreadyJoined: {
type: Array as PropType<Array<string>>,
default: [],
},
},
emits: ["update:model-value"],
data() {
return {};
},
computed: {
...mapState(useQueryBuilderStore, ["tableMetas"]),
activeTable() {
return this.tableMetas.find((tm) => tm.tableName == this.table);
},
value: {
get() {
return this.modelValue;
},
set(val: Array<DynamicQueryStructure & { foreignColumn: string }>) {
set(val: Array<DynamicQueryStructure & JoinStructure>) {
this.$emit("update:model-value", val);
},
},
@ -60,11 +60,13 @@ export default defineComponent({
methods: {
addToValue() {
this.value.push({
id: uuid(),
select: "*",
table: "",
where: [],
join: [],
orderBy: [],
type: "defined",
foreignColumn: "",
});
},

View file

@ -2,13 +2,52 @@
<div class="flex flex-row gap-2 w-full">
<div class="flex flex-row gap-2 w-full">
<div class="flex flex-col gap-2 w-full">
<select v-model="foreignColumn" class="w-full">
<option value="" disabled>Relation auswählen</option>
<option v-for="relation in activeTable?.relations" :value="relation.column">
{{ relation.column }} -> {{ joinTableName(relation.referencedTableName) }}
</option>
</select>
<Table v-model="value" disable-table-select />
<div class="flex flex-row gap-2 w-full">
<div
v-if="false"
class="h-fit p-1 border border-gray-400 hover:bg-gray-200 rounded-md"
title="Join Modus wechseln"
@click="swapJoinType(value.type)"
>
<ArrowsUpDownIcon class="text-gray-500 h-6 w-6 cursor-pointer" />
</div>
<select v-if="value.type == 'defined'" v-model="context" class="w-full">
<option value="" disabled>Relation auswählen</option>
<option
v-for="relation in activeTable?.relations"
:value="relation.column"
:disabled="
alreadyJoined.includes(joinTableName(relation.referencedTableName)) &&
joinTableName(relation.referencedTableName) != value.table
"
>
{{ relation.column }} -> {{ joinTableName(relation.referencedTableName) }}
<span
v-if="
alreadyJoined.includes(joinTableName(relation.referencedTableName)) &&
joinTableName(relation.referencedTableName) != value.table
"
>
(Join auf dieser Ebene besteht schon)
</span>
</option>
</select>
<div v-else class="flex flex-col w-full">
<select v-model="joinTable">
<option value="" disabled>Tabelle auswählen</option>
<option
v-for="table in tableMetas"
:value="table.tableName"
:disabled="alreadyJoined.includes(table.tableName) && table.tableName != value.table"
>
{{ table.tableName }}
</option>
</select>
<input v-model="context" type="text" placeholder="Join Condition tabA.col = tabB.col" />
</div>
</div>
<Table v-model="value" disable-table-select :show-table-select="false" />
</div>
<div class="h-fit p-1 border border-gray-400 hover:bg-gray-200 rounded-md" @click="$emit('remove')">
<TrashIcon class="text-gray-500 h-6 w-6 cursor-pointer" />
@ -20,11 +59,12 @@
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapActions, mapState } from "pinia";
import type { DynamicQueryStructure } from "@/types/dynamicQueries";
import { type DynamicQueryStructure, type JoinStructure } from "@/types/dynamicQueries";
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
import Table from "./Table.vue";
import { TrashIcon } from "@heroicons/vue/24/outline";
import { ArrowsUpDownIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { joinTableName } from "@/helpers/queryFormatter";
import { v4 as uuid } from "uuid";
</script>
<script lang="ts">
@ -35,25 +75,15 @@ export default defineComponent({
default: "",
},
modelValue: {
type: Object as PropType<
DynamicQueryStructure & {
foreignColumn: string;
}
>,
default: {
select: "*",
table: "",
where: [],
join: [],
orderBy: [],
foreignColumn: "",
},
type: Object as PropType<DynamicQueryStructure & JoinStructure>,
required: true,
},
alreadyJoined: {
type: Array as PropType<Array<string>>,
default: [],
},
},
emits: ["update:model-value", "remove"],
data() {
return {};
},
computed: {
...mapState(useQueryBuilderStore, ["tableMetas"]),
activeTable() {
@ -63,27 +93,59 @@ export default defineComponent({
get() {
return this.modelValue;
},
set(
val: DynamicQueryStructure & {
foreignColumn: string;
}
) {
set(val: DynamicQueryStructure & JoinStructure) {
this.$emit("update:model-value", val);
},
},
foreignColumn: {
context: {
get() {
return this.modelValue.foreignColumn;
if (this.modelValue.type == "defined") {
return this.modelValue.foreignColumn ?? "";
} else {
return this.modelValue.condition ?? "";
}
},
set(val: string) {
if (this.modelValue.type == "defined") {
let relTable = this.activeTable?.relations.find((r) => r.column == val);
this.$emit("update:model-value", {
...this.modelValue,
foreignColumn: val,
table: joinTableName(relTable?.referencedTableName ?? ""),
});
} else {
this.$emit("update:model-value", {
...this.modelValue,
condition: val,
});
}
},
},
joinTable: {
get(): string {
return this.modelValue.table;
},
set(val: string) {
let relTable = this.activeTable?.relations.find((r) => r.column == val);
this.$emit("update:model-value", {
...this.modelValue,
foreignColumn: val,
table: joinTableName(relTable?.referencedTableName ?? ""),
table: val,
});
},
},
},
mounted() {
if (!this.value.id) {
this.value.id = uuid();
}
},
methods: {
swapJoinType(type: string) {
if (type == "defined") {
this.value.type = "custom";
} else {
this.value.type = "defined";
}
},
},
});
</script>

View file

@ -1,6 +1,6 @@
<template>
<div class="flex flex-row gap-2 w-full border border-gray-300 rounded-md p-1">
<select v-if="concat != '_'" v-model="concat" class="w-20! h-fit!">
<select v-if="isFirst" v-model="concat" class="w-20! h-fit!">
<option value="" disabled>Verknüpfung auswählen</option>
<option v-for="operation in ['AND', 'OR']" :value="operation">
{{ operation }}
@ -28,6 +28,10 @@ import NestedWhere from "./NestedWhere.vue";
<script lang="ts">
export default defineComponent({
props: {
isFirst: {
type: Boolean,
default: false,
},
table: {
type: String,
default: "",
@ -38,9 +42,6 @@ export default defineComponent({
},
},
emits: ["update:model-value", "remove"],
data() {
return {};
},
computed: {
...mapState(useQueryBuilderStore, ["tableMetas"]),
concat: {
@ -60,5 +61,10 @@ export default defineComponent({
},
},
},
mounted() {
if (this.concat == "_") {
this.concat = "AND";
}
},
});
</script>

View file

@ -1,12 +1,17 @@
<template>
<div class="flex flex-row gap-2">
<p class="w-14 min-w-14 pt-2">ORDER</p>
<p class="w-14 min-w-14 pt-2">SORT</p>
<div class="flex flex-row flex-wrap gap-2 items-center w-full">
<OrderStructure
v-for="(order, index) in value"
:model-value="order"
:table="table"
:columns="columns"
:alreadySorted="alreadySorted"
:notFirst="index != 0"
:notLast="index != value.length - 1"
@up="changeSort('up', index)"
@down="changeSort('down', index)"
@update:model-value="($event) => (value[index] = $event)"
@remove="removeAtIndex(index)"
/>
@ -35,9 +40,15 @@ export default defineComponent({
type: String,
default: "",
},
// columns: {
// type: [Array, String] as PropType<"*" | Array<string>>,
// default: "*",
// },
columns: {
type: [Array, String] as PropType<"*" | Array<string>>,
default: "*",
type: Array as PropType<
Array<{ table: string; id: string; depth: number; path: string[]; columns: "*" | string[] }>
>,
default: [],
},
modelValue: {
type: Array as PropType<Array<OrderByStructure>>,
@ -50,6 +61,9 @@ export default defineComponent({
},
computed: {
...mapState(useQueryBuilderStore, ["tableMetas"]),
alreadySorted() {
return this.modelValue.map((m) => ({ id: m.id, col: m.column }));
},
value: {
get() {
return this.modelValue;
@ -62,6 +76,9 @@ export default defineComponent({
methods: {
addToValue() {
this.value.push({
id: "",
depth: 0,
table: "",
column: "",
order: "ASC",
});
@ -69,6 +86,12 @@ export default defineComponent({
removeAtIndex(index: number) {
this.value.splice(index, 1);
},
changeSort(dir: "up" | "down", index: number) {
const swapIndex = dir === "up" ? index - 1 : index + 1;
if (swapIndex >= 0 && swapIndex < this.value.length) {
[this.value[index], this.value[swapIndex]] = [this.value[swapIndex], this.value[index]];
}
},
},
});
</script>

View file

@ -1,15 +1,26 @@
<template>
<div class="flex flex-row gap-2 items-center w-full">
<div class="flex flex-col min-w-fit">
<ChevronUpIcon v-if="notFirst" class="w-4 h-4 stroke-2 cursor-pointer" @click.prevent="$emit('up')" />
<ChevronDownIcon v-if="notLast" class="w-4 h-4 stroke-2 cursor-pointer" @click.prevent="$emit('down')" />
</div>
<select v-model="column" class="w-full">
<option value="" disabled>Spalte auswählen</option>
<option v-for="column in selectableColumns" :value="column">
{{ column }}
<option
v-for="selectable in selectableColumns"
:value="`${selectable.id}_${selectable.column}`"
:disabled="
alreadySorted.some((as) => as.id == selectable.id && as.col == selectable.column) &&
`${selectable.id}_${selectable.column}` != column
"
>
{{ [...selectable.path, selectable.table].join("-") }} -> {{ selectable.column }}
</option>
</select>
<select v-model="order">
<option value="" disabled>Sortierung auswählen</option>
<option v-for="order in ['ASC', 'DESC']" :value="order">
{{ order }}
<option v-for="order in orderable" :value="order.key">
{{ order.val }}
</option>
</select>
<div class="p-1 border border-gray-400 hover:bg-gray-200 rounded-md" @click="$emit('remove')">
@ -23,47 +34,102 @@ import { defineComponent, type PropType } from "vue";
import { mapActions, mapState } from "pinia";
import type { OrderByStructure, OrderByType } from "@/types/dynamicQueries";
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
import { TrashIcon } from "@heroicons/vue/24/outline";
import { TrashIcon, ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/outline";
</script>
<script lang="ts">
export default defineComponent({
props: {
notFirst: {
type: Boolean,
defailt: false,
},
notLast: {
type: Boolean,
defailt: false,
},
table: {
type: String,
default: "",
},
// columns: {
// type: [Array, String] as PropType<"*" | Array<string>>,
// default: "*",
// },
columns: {
type: [Array, String] as PropType<"*" | Array<string>>,
default: "*",
type: Array as PropType<
Array<{ table: string; id: string; depth: number; path: string[]; columns: "*" | string[] }>
>,
default: [],
},
modelValue: {
type: Object as PropType<OrderByStructure>,
default: {},
},
alreadySorted: {
type: Array as PropType<Array<{ id: string; col: string }>>,
default: [],
},
},
emits: ["update:model-value", "remove", "up", "down"],
watch: {
columns() {
if (!this.columns.some((c) => c.id == this.modelValue.id)) {
this.$emit("remove");
}
},
},
emits: ["update:model-value", "remove"],
data() {
return {};
return {
orderable: [
{ key: "ASC", val: "Aufsteigend (ABC)" },
{ key: "DESC", val: "Absteigend (CBA)" },
],
};
},
computed: {
...mapState(useQueryBuilderStore, ["tableMetas"]),
// selectableColumns() {
// if (this.columns == "*") {
// let meta = this.tableMetas.find((tm) => tm.tableName == this.table);
// if (!meta) return [];
// let relCols = meta.relations.map((r) => r.column);
// return meta.columns.map((c) => c.column).filter((c) => !relCols.includes(c));
// } else {
// return this.columns;
// }
// },
selectableColumns() {
if (this.columns == "*") {
let meta = this.tableMetas.find((tm) => tm.tableName == this.table);
if (!meta) return [];
let relCols = meta.relations.map((r) => r.column);
return meta.columns.map((c) => c.column).filter((c) => !relCols.includes(c));
} else {
return this.columns;
}
return this.columns.reduce(
(acc, curr) => {
if (curr.columns == "*") {
let meta = this.tableMetas.find((tm) => tm.tableName == curr.table);
if (meta) {
let relCols = meta.relations.map((r) => r.column);
meta.columns
.map((c) => c.column)
.filter((c) => !relCols.includes(c))
.forEach((c) =>
acc.push({ id: curr.id, depth: curr.depth, table: curr.table, column: c, path: curr.path })
);
}
} else {
curr.columns.forEach((c) =>
acc.push({ id: curr.id, depth: curr.depth, table: curr.table, column: c, path: curr.path })
);
}
return acc;
},
[] as Array<{ id: string; depth: number; table: string; column: string; path: string[] }>
);
},
column: {
get() {
return this.modelValue.column;
return `${this.modelValue.id}_${this.modelValue.column}`;
},
set(val: string) {
this.$emit("update:model-value", { ...this.modelValue, column: val });
set(val: `${string}_${string}`) {
let col = this.selectableColumns.find((sc) => sc.id == val.split("_")[0] && sc.column == val.split("_")[1]);
this.$emit("update:model-value", { ...this.modelValue, ...col });
},
},
order: {

View file

@ -1,23 +1,22 @@
<template>
<div class="flex flex-col gap-2 w-full">
<TableSelect v-model="table" :disableTableSelect="disableTableSelect" />
<TableSelect v-if="showTableSelect" v-model="table" :disableTableSelect="disableTableSelect" />
<ColumnSelect v-if="table != ''" v-model="columnSelect" :table="table" />
<Where v-if="table != ''" v-model="where" :table="table" />
<Order v-if="table != ''" v-model="order" :table="table" :columns="columnSelect" />
<Join v-if="table != ''" v-model="modelValue.join" :table="table" />
<Join v-if="table != ''" v-model="modelValue.join" :table="table" :alreadyJoined="alreadyJoined" />
<Order v-if="table != '' && enableOrder" v-model="order" :table="table" :columns="nestedTablesByDepth" />
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapActions, mapState } from "pinia";
import type { ConditionStructure, DynamicQueryStructure, OrderByStructure } from "@/types/dynamicQueries";
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
import { type ConditionStructure, type DynamicQueryStructure, type OrderByStructure } from "@/types/dynamicQueries";
import ColumnSelect from "./ColumnSelect.vue";
import Where from "./Where.vue";
import Order from "./Order.vue";
import Join from "./Join.vue";
import TableSelect from "./TableSelect.vue";
import { v4 as uuid } from "uuid";
</script>
<script lang="ts">
@ -25,21 +24,50 @@ export default defineComponent({
props: {
modelValue: {
type: Object as PropType<DynamicQueryStructure>,
default: {
select: "*",
table: "",
where: [],
join: [],
orderBy: [],
},
required: true,
},
disableTableSelect: {
type: Boolean,
default: false,
},
enableOrder: {
type: Boolean,
default: false,
},
showTableSelect: {
type: Boolean,
default: true,
},
},
emits: ["update:model-value"],
computed: {
alreadyJoined() {
return this.modelValue.join?.map((j) => j.table);
},
nestedTablesByDepth() {
const tables: Array<{ table: string; id: string; depth: number; path: string[]; columns: "*" | string[] }> = [];
function recurse(item: DynamicQueryStructure, path: string[]) {
tables.push({ table: item.table, id: item.id, depth: path.length, path, columns: item.select });
if (item.join) {
item.join.forEach((child) => {
recurse(child, [...path, item.table]);
});
}
}
recurse(this.modelValue, []);
return tables;
},
value: {
get() {
return this.modelValue;
},
set(val: DynamicQueryStructure) {
this.$emit("update:model-value", val);
},
},
table: {
get() {
return this.modelValue.table || "";
@ -81,5 +109,10 @@ export default defineComponent({
},
},
},
mounted() {
if (!this.value.id) {
this.value.id = uuid();
}
},
});
</script>

View file

@ -5,6 +5,7 @@
<div v-for="(condition, index) in value" class="contents">
<NestedCondition
v-if="condition.structureType == 'nested'"
:isFirst="index == 0"
:model-value="condition"
:table="table"
@update:model-value="($event) => (value[index] = $event)"
@ -12,6 +13,7 @@
/>
<Condition
v-else
:isFirst="index == 0"
:model-value="condition"
:table="table"
@update:model-value="($event) => (value[index] = $event)"
@ -74,14 +76,14 @@ export default defineComponent({
addNestedToValue() {
this.value.push({
structureType: "nested",
concat: this.value.length == 0 ? "_" : "AND",
concat: "AND",
conditions: [],
});
},
addConditionToValue() {
this.value.push({
structureType: "condition",
concat: this.value.length == 0 ? "_" : "AND",
concat: "AND",
operation: "eq",
column: "",
value: "",

View file

@ -1,14 +0,0 @@
import type { DamageReportViewModel } from "../viewmodels/admin/unit/damageReport/damageReport.models";
import { equipmentDemoData } from "./equipment";
export const damageReportDemoData: Array<DamageReportViewModel> = [
{
id: "sdfgh",
reported: new Date(),
status: "",
done: false,
description: "knjgljna g",
affectedEquipmentId: equipmentDemoData[0].id,
affectedEquipment: equipmentDemoData[0],
},
];

View file

@ -1,13 +0,0 @@
import type { EquipmentViewModel } from "../viewmodels/admin/unit/equipment/equipment.models";
import { equipmentTypeDemoData } from "./equipmentType";
export const equipmentDemoData: Array<EquipmentViewModel> = [
{
id: "abc",
code: "0456984224498",
name: "B-Schlauch",
location: "HLF",
equipmentTypeId: equipmentTypeDemoData[0].id,
equipmentType: equipmentTypeDemoData[0],
},
];

View file

@ -1,9 +0,0 @@
import type { EquipmentTypeViewModel } from "../viewmodels/admin/unit/equipmentType/equipmentType.models";
export const equipmentTypeDemoData: Array<EquipmentTypeViewModel> = [
{
id: "xyz",
type: "B-Schlauch",
description: "Shläuche vom Typ B",
},
];

View file

@ -1,12 +0,0 @@
import type { InspectionPlanViewModel } from "../viewmodels/admin/unit/inspectionPlan/inspectionPlan.models";
import { equipmentTypeDemoData } from "./equipmentType";
export const inspectionPlanDemoData: Array<InspectionPlanViewModel> = [
{
id: "abc",
title: "Sichtprüfung",
inspectionInterval: "1-m",
equipmentTypeId: equipmentTypeDemoData[0].id,
equipmentType: equipmentTypeDemoData[0],
},
];

View file

@ -1,10 +0,0 @@
import type { RespiratoryGearViewModel } from "../viewmodels/admin/unit/respiratoryGear/respiratoryGear.models";
import { equipmentDemoData } from "./equipment";
export const respiratoryGearDemoData: Array<RespiratoryGearViewModel> = [
{
id: "adfsg",
equipmentId: equipmentDemoData[0].id,
equipment: equipmentDemoData[0],
},
];

View file

@ -1,11 +0,0 @@
import type { RespiratoryMissionViewModel } from "../viewmodels/admin/unit/respiratoryMission/respiratoryMission.models";
import { equipmentDemoData } from "./equipment";
export const respiratoryMissionDemoData: Array<RespiratoryMissionViewModel> = [
{
id: "adfsg",
date: new Date(),
title: "B5",
description: "B5 Einsatz",
},
];

View file

@ -1,17 +0,0 @@
import type { RespiratoryWearerViewModel } from "../viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models";
export const respiratoryWearerDemoData: Array<RespiratoryWearerViewModel> = [
{
id: "dfghj",
memberId: "9469991d-fa22-4899-82ce-b1ba5de990dc",
member: {
id: "9469991d-fa22-4899-82ce-b1ba5de990dc",
salutation: { id: 3, salutation: "Herr" },
firstname: "Julian",
lastname: "Krauser",
nameaffix: "",
birthdate: new Date("2003-09-20"),
internalId: "1312",
},
},
];

View file

@ -1,14 +0,0 @@
import type { VehicleViewModel } from "../viewmodels/admin/unit/vehicle/vehicle.models";
export const vehicleDemoData: Array<VehicleViewModel> = [
{
id: "kjhb",
name: "HLF",
type: "HLF 20/10",
},
{
id: "kjhb",
name: "LF",
type: "LF 8/6",
},
];

View file

@ -1,9 +0,0 @@
import type { VehicleTypeViewModel } from "../viewmodels/admin/unit/vehicleType/vehicleType.models";
export const vehicleTypeDemoData: Array<VehicleTypeViewModel> = [
{
id: "xyz",
type: "HLF",
description: "HLF",
},
];

View file

@ -1,22 +0,0 @@
import type { WearableViewModel } from "../viewmodels/admin/unit/wearable/wearable.models";
import { wearableTypeDemoData } from "./wearableType";
export const wearableDemoData: Array<WearableViewModel> = [
{
id: "abc",
code: "0456984224498",
name: "B-Schlauch",
wearerId: "9469991d-fa22-4899-82ce-b1ba5de990dc",
wearer: {
id: "9469991d-fa22-4899-82ce-b1ba5de990dc",
salutation: { id: 3, salutation: "Herr" },
firstname: "Julian",
lastname: "Krauser",
nameaffix: "",
birthdate: new Date("2003-09-20"),
internalId: "1312",
},
wearableTypeId: wearableTypeDemoData[0].id,
wearableType: wearableTypeDemoData[0],
},
];

View file

@ -1,9 +0,0 @@
import type { WearableTypeViewModel } from "../viewmodels/admin/unit/wearableType/wearableType.models";
export const wearableTypeDemoData: Array<WearableTypeViewModel> = [
{
id: "xyz",
type: "Jacke",
description: "Bayern 2000",
},
];

View file

@ -1,143 +0,0 @@
import type { BarcodeFormat, DetectedBarcode } from "barcode-detector/pure";
/*** select camera ***/
export interface Camera {
label: string;
constraints: {
deviceId?: string;
facingMode: string;
};
}
export const defaultConstraintOptions: Array<Camera> = [
{ label: "rear camera", constraints: { facingMode: "environment" } },
{ label: "front camera", constraints: { facingMode: "user" } },
];
export async function getAvailableCameras(useDefault: boolean = false): Promise<Array<Camera>> {
// NOTE: on iOS we can't invoke `enumerateDevices` before the user has given
// camera access permission. `QrcodeStream` internally takes care of
// requesting the permissions. The `camera-on` event should guarantee that this
// has happened.
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(({ kind }) => kind === "videoinput");
return [
...(useDefault ? defaultConstraintOptions : []),
...videoDevices.map(({ deviceId, label }) => ({
label: `${label}`, //(ID: ${deviceId})
constraints: { deviceId, facingMode: "custom" },
})),
];
}
/*** track functons ***/
export function paintOutline(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
for (const detectedCode of detectedCodes) {
const [firstPoint, ...otherPoints] = detectedCode.cornerPoints;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(firstPoint.x, firstPoint.y);
for (const { x, y } of otherPoints) {
ctx.lineTo(x, y);
}
ctx.lineTo(firstPoint.x, firstPoint.y);
ctx.closePath();
ctx.stroke();
}
}
export function paintBoundingBox(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
for (const detectedCode of detectedCodes) {
const {
boundingBox: { x, y, width, height },
} = detectedCode;
ctx.lineWidth = 2;
ctx.strokeStyle = "#007bff";
ctx.strokeRect(x, y, width, height);
}
}
export function paintCenterText(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
for (const detectedCode of detectedCodes) {
const { boundingBox, rawValue } = detectedCode;
const centerX = boundingBox.x + boundingBox.width / 2;
const centerY = boundingBox.y + boundingBox.height / 2;
const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width);
ctx.font = `bold ${fontSize}px sans-serif`;
ctx.textAlign = "center";
ctx.lineWidth = 3;
ctx.strokeStyle = "#35495e";
ctx.strokeText(detectedCode.rawValue, centerX, centerY);
ctx.fillStyle = "#5cb984";
ctx.fillText(rawValue, centerX, centerY);
}
}
export const trackFunctionOptions = [
{ text: "nothing (default)", value: undefined },
{ text: "outline", value: paintOutline },
{ text: "centered text", value: paintCenterText },
{ text: "bounding box", value: paintBoundingBox },
{
text: "mixed",
value: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => {
paintOutline(detectedCodes, ctx);
paintCenterText(detectedCodes, ctx);
},
},
];
/*** barcode formats ***/
export const barcodeFormats: Array<BarcodeFormat> = [
"aztec",
"code_128",
"code_39",
"code_93",
"codabar",
"databar",
"databar_expanded",
"data_matrix",
"dx_film_edge",
"ean_13",
"ean_8",
"itf",
"maxi_code",
"micro_qr_code",
"pdf417",
"qr_code",
"rm_qr_code",
"upc_a",
"upc_e",
"linear_codes",
"matrix_codes",
];
/*** error handling ***/
export function handleScannerError(err: Error) {
let error = `[${err.name}]: `;
if (err.name === "NotAllowedError") {
error += "you need to grant camera access permission";
} else if (err.name === "NotFoundError") {
error += "no camera on this device";
} else if (err.name === "NotSupportedError") {
error += "secure context required (HTTPS, localhost)";
} else if (err.name === "NotReadableError") {
error += "is the camera already in use?";
} else if (err.name === "OverconstrainedError") {
error += "installed cameras are not suitable";
} else if (err.name === "StreamApiNotSupportedError") {
error += "Stream API is not supported in this browser";
} else if (err.name === "InsecureContextError") {
error += "Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";
} else {
error += err.message;
}
return error;
}

View file

@ -72,7 +72,7 @@ a[button] {
button[primary]:not([primary="false"]),
a[button][primary]:not([primary="false"]) {
@apply border border-transparent text-white bg-primary hover:bg-primary;
@apply border-2 border-transparent text-white bg-primary hover:bg-primary;
}
button[primary-outline]:not([primary-outline="false"]),
@ -95,7 +95,7 @@ select {
input[readonly],
textarea[readonly],
select[readonly] {
@apply select-none focus:border-gray-300 cursor-default;
@apply select-none;
/* pointer-events-none; */
}
@ -131,29 +131,3 @@ summary > svg {
summary::-webkit-details-marker {
display: none;
}
.fc-button-primary {
@apply bg-primary! border-primary! outline-hidden! ring-0! hover:bg-red-700! hover:border-red-700! h-10 text-center;
}
.fc-button-active {
@apply bg-red-500! border-red-500!;
}
.fc-toolbar {
@apply flex-wrap;
}
/* For screens between 850px and 768px */
@media (max-width: 850px) and (min-width: 768px) {
.fc-header-toolbar.fc-toolbar.fc-toolbar-ltr > .fc-toolbar-chunk:nth-child(2) {
@apply order-1!;
}
/* Your styles for this range */
}
/* For screens between 525px and 0px */
@media (max-width: 525px) and (min-width: 0px) {
/* Your styles for this range */
.fc-header-toolbar.fc-toolbar.fc-toolbar-ltr > .fc-toolbar-chunk:nth-child(2) {
@apply order-1!;
}
}

View file

@ -1,4 +1,4 @@
import { useBackupStore } from "@/stores/admin/management/backup";
import { useBackupStore } from "../stores/admin/management/backup";
export async function setBackupPage(to: any, from: any, next: any) {
const backup = useBackupStore();

View file

@ -6,19 +6,11 @@ import { loadAccountData } from "./accountGuard";
import { isSetup } from "./setupGuard";
import { abilityAndNavUpdate } from "./adminGuard";
import type { PermissionType, PermissionSection, PermissionModule } from "@/types/permissionTypes";
import { resetMemberStores, setMemberId } from "./club/memberGuard";
import { resetProtocolStores, setProtocolId } from "./club/protocolGuard";
import { resetNewsletterStores, setNewsletterId } from "./club/newsletterGuard";
import { resetMemberStores, setMemberId } from "./memberGuard";
import { resetProtocolStores, setProtocolId } from "./protocolGuard";
import { resetNewsletterStores, setNewsletterId } from "./newsletterGuard";
import { config } from "../config";
import { setBackupPage } from "./management/backupGuard";
import { resetEquipmentTypeStores, setEquipmentTypeId } from "./unit/equipmentType";
import { resetEquipmentStores, setEquipmentId } from "./unit/equipment";
import { resetVehicleStores, setVehicleId } from "./unit/vehicle";
import { resetRespiratoryGearStores, setRespiratoryGearId } from "./unit/respiratoryGear";
import { resetRespiratoryWearerStores, setRespiratoryWearerId } from "./unit/respiratoryWearer";
import { resetRespiratoryMissionStores, setRespiratoryMissionId } from "./unit/respiratoryMission";
import { resetWearableStores, setWearableId } from "./unit/wearable";
import { resetInspectionPlanStores, setInspectionPlanId } from "./unit/inspectionPlan";
import { setBackupPage } from "./backupGuard";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -312,546 +304,6 @@ const router = createRouter({
},
],
},
{
path: "unit",
name: "admin-unit",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-default",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "read", section: "unit" },
beforeEnter: [abilityAndNavUpdate],
},
{
path: "equipment",
name: "admin-unit-equipment-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "equipment" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-equipment",
component: () => import("@/views/admin/unit/equipment/Equipment.vue"),
beforeEnter: [resetEquipmentStores],
},
{
path: "create",
name: "admin-unit-equipment-create",
component: () => import("@/views/admin/unit/equipment/CreateEquipment.vue"),
meta: { type: "create", section: "unit", module: "equipment" },
beforeEnter: [abilityAndNavUpdate],
},
{
path: ":equipmentId",
name: "admin-unit-equipment-routing",
component: () => import("@/views/admin/unit/equipment/EquipmentRouting.vue"),
beforeEnter: [setEquipmentId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-equipment-overview",
component: () => import("@/views/admin/unit/equipment/Overview.vue"),
props: true,
},
{
path: "maintenance",
name: "admin-unit-equipment-maintenance",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "inspection",
name: "admin-unit-equipment-inspection",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "report",
name: "admin-unit-equipment-damage_report",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-equipment-edit",
component: () => import("@/views/admin/unit/equipment/UpdateEquipment.vue"),
meta: { type: "update", section: "unit", module: "equipment" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "vehicle",
name: "admin-unit-vehicle-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "vehicle" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-vehicle",
component: () => import("@/views/admin/unit/vehicle/Vehicle.vue"),
beforeEnter: [resetVehicleStores],
},
{
path: "create",
name: "admin-unit-vehicle-create",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "create", section: "unit", module: "vehicle" },
beforeEnter: [abilityAndNavUpdate],
},
{
path: ":vehicleId",
name: "admin-unit-vehicle-routing",
component: () => import("@/views/admin/unit/vehicle/VehicleRouting.vue"),
beforeEnter: [setVehicleId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-vehicle-overview",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "maintenance",
name: "admin-unit-vehicle-maintenance",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "inspection",
name: "admin-unit-vehicle-inspection",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "report",
name: "admin-unit-vehicle-damage_report",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-vehicle-edit",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "update", section: "unit", module: "vehicle" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "wearable",
name: "admin-unit-wearable-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "wearable" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-wearable",
component: () => import("@/views/admin/unit/wearable/Wearable.vue"),
beforeEnter: [resetWearableStores],
},
{
path: "create",
name: "admin-unit-wearable-create",
component: () => import("@/views/admin/unit/wearable/CreateWearable.vue"),
meta: { type: "create", section: "unit", module: "wearable" },
beforeEnter: [abilityAndNavUpdate],
},
{
path: ":wearableId",
name: "admin-unit-wearable-routing",
component: () => import("@/views/admin/unit/wearable/WearableRouting.vue"),
beforeEnter: [setWearableId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-wearable-overview",
component: () => import("@/views/admin/unit/wearable/Overview.vue"),
props: true,
},
{
path: "report",
name: "admin-unit-wearable-damage_report",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-wearable-edit",
component: () => import("@/views/admin/unit/wearable/UpdateWearable.vue"),
meta: { type: "update", section: "unit", module: "wearable" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "respiratory-gear",
name: "admin-unit-respiratory_gear-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "respiratory_gear" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-respiratory_gear",
component: () => import("@/views/admin/unit/respiratoryGear/RespiratoryGear.vue"),
beforeEnter: [resetRespiratoryGearStores],
},
{
path: ":respiratoryGearId",
name: "admin-unit-respiratory_gear-routing",
component: () => import("@/views/admin/unit/respiratoryGear/RespiratoryGearRouting.vue"),
beforeEnter: [setRespiratoryGearId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-respiratory_gear-overview",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "maintenance",
name: "admin-unit-respiratory_gear-maintenance",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "inspection",
name: "admin-unit-respiratory_gear-inspection",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "mission",
name: "admin-unit-respiratory_gear-mission",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-respiratory_gear-edit",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "update", section: "unit", module: "respiratory_gear" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "respiratory-wearer",
name: "admin-unit-respiratory_wearer-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "respiratory_wearer" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-respiratory_wearer",
component: () => import("@/views/admin/unit/respiratoryWearer/RespiratoryWearer.vue"),
beforeEnter: [resetRespiratoryWearerStores],
},
{
path: ":respiratoryWearerId",
name: "admin-unit-respiratory_wearer-routing",
component: () => import("@/views/admin/unit/respiratoryWearer/RespiratoryWearerRouting.vue"),
beforeEnter: [setRespiratoryWearerId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-respiratory_wearer-overview",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "mission",
name: "admin-unit-respiratory_wearer-mission",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "education",
name: "admin-unit-respiratory_wearer-education",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "instruction",
name: "admin-unit-respiratory_wearer-instruction",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "screening",
name: "admin-unit-respiratory_wearer-screening",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "strain",
name: "admin-unit-respiratory_wearer-strain",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-respiratory_wearer-edit",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "update", section: "unit", module: "respiratory_wearer" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "respiratory-mission",
name: "admin-unit-respiratory_mission-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "respiratory_mission" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-respiratory_mission",
component: () => import("@/views/admin/unit/respiratoryMission/RespiratoryMission.vue"),
beforeEnter: [resetRespiratoryMissionStores],
},
{
path: ":respiratoryMissionId",
name: "admin-unit-respiratory_mission-routing",
component: () => import("@/views/admin/unit/respiratoryMission/RespiratoryMissionRouting.vue"),
beforeEnter: [setRespiratoryMissionId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-respiratory_mission-overview",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "wearer",
name: "admin-unit-respiratory_mission-wearer",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "gear",
name: "admin-unit-respiratory_mission-gear",
component: () => import("@/views/admin/ViewSelect.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-respiratory_mission-edit",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "update", section: "unit", module: "respiratory_mission" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "damage-report",
name: "admin-unit-damage_report-route",
component: () => import("@/views/admin/unit/damageReport/DamageReportRouting.vue"),
meta: { type: "read", section: "unit", module: "damage_report" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-damage_report",
component: () => import("@/views/admin/unit/damageReport/DamageReport.vue"),
},
{
path: "done",
name: "admin-unit-damage_report-done",
component: () => import("@/views/admin/unit/damageReport/DamageReport.vue"),
},
],
},
{
path: "equipment-type",
name: "admin-unit-equipment_type-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "equipment_type" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-equipment_type",
component: () => import("@/views/admin/unit/equipmentType/EquipmentType.vue"),
beforeEnter: [resetEquipmentTypeStores],
},
{
path: ":equipmentTypeId",
name: "admin-unit-equipment_type-routing",
component: () => import("@/views/admin/unit/equipmentType/EquipmentTypeRouting.vue"),
beforeEnter: [setEquipmentTypeId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-equipment_type-overview",
component: () => import("@/views/admin/unit/equipmentType/Overview.vue"),
props: true,
},
{
path: "inspection-plan",
name: "admin-unit-equipment_type-inspection_plan",
component: () => import("@/views/admin/unit/equipmentType/InspectionPlans.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-equipment_type-edit",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "update", section: "unit", module: "equipment_type" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "vehicle-type",
name: "admin-unit-vehicle_type-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "vehicle_type" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-vehicle_type",
component: () => import("@/views/admin/unit/vehicleType/VehicleType.vue"),
},
{
path: ":vehicleTypeId",
name: "admin-unit-vehicle_type-routing",
component: () => import("@/views/admin/unit/vehicleType/VehicleTypeRouting.vue"),
beforeEnter: [setEquipmentTypeId],
props: true,
children: [
{
path: "overview",
name: "admin-unit-vehicle_type-overview",
component: () => import("@/views/admin/unit/vehicleType/Overview.vue"),
props: true,
},
{
path: "inspection-plan",
name: "admin-unit-vehicle_type-inspection_plan",
component: () => import("@/views/admin/unit/vehicleType/InspectionPlans.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-vehicle_type-edit",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "update", section: "unit", module: "equipment_type" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
{
path: "wearable-type",
name: "admin-unit-wearable_type-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "wearable_type" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-wearable_type",
component: () => import("@/views/admin/unit/wearableType/WearableType.vue"),
},
{
path: ":wearableTypeId/edit",
name: "admin-unit-wearable_type-edit",
component: () => import("@/views/admin/unit/wearableType/UpdateWearableType.vue"),
meta: { type: "update", section: "unit", module: "wearable_type" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
{
path: "inspection-plan",
name: "admin-unit-inspection_plan-route",
component: () => import("@/views/RouterView.vue"),
meta: { type: "read", section: "unit", module: "inspection_plan" },
beforeEnter: [abilityAndNavUpdate],
children: [
{
path: "",
name: "admin-unit-inspection_plan",
component: () => import("@/views/admin/unit/inspectionPlan/InspectionPlan.vue"),
beforeEnter: [resetInspectionPlanStores],
},
{
path: "create",
name: "admin-unit-inspection_plan-create",
component: () => import("@/views/admin/unit/inspectionPlan/CreateInspectionPlan.vue"),
meta: { type: "create", section: "unit", module: "equipment" },
beforeEnter: [abilityAndNavUpdate],
},
{
path: ":inspectionPlanId",
name: "admin-unit-inspection_plan-routing",
component: () => import("@/views/admin/unit/inspectionPlan/InspectionPlanRouting.vue"),
beforeEnter: [setInspectionPlanId],
props: true,
children: [
{
path: "",
name: "admin-unit-inspection_plan-overview",
component: () => import("@/views/admin/unit/inspectionPlan/Overview.vue"),
props: true,
},
{
path: "edit",
name: "admin-unit-inspection_plan-edit",
component: () => import("@/views/admin/ViewSelect.vue"),
meta: { type: "update", section: "unit", module: "inspection_plan" },
beforeEnter: [abilityAndNavUpdate],
props: true,
},
],
},
],
},
],
},
{
path: "configuration",
name: "admin-configuration",

View file

@ -1,7 +1,7 @@
import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter";
import { useNewsletterDatesStore } from "@/stores/admin/club/newsletter/newsletterDates";
import { useNewsletterRecipientsStore } from "@/stores/admin/club/newsletter/newsletterRecipients";
import { useNewsletterPrintoutStore } from "../../stores/admin/club/newsletter/newsletterPrintout";
import { useNewsletterPrintoutStore } from "../stores/admin/club/newsletter/newsletterPrintout";
export async function setNewsletterId(to: any, from: any, next: any) {
const newsletter = useNewsletterStore();

View file

@ -3,7 +3,7 @@ import { useProtocolAgendaStore } from "@/stores/admin/club/protocol/protocolAge
import { useProtocolDecisionStore } from "@/stores/admin/club/protocol/protocolDecision";
import { useProtocolPresenceStore } from "@/stores/admin/club/protocol/protocolPresence";
import { useProtocolVotingStore } from "@/stores/admin/club/protocol/protocolVoting";
import { useProtocolPrintoutStore } from "../../stores/admin/club/protocol/protocolPrintout";
import { useProtocolPrintoutStore } from "../stores/admin/club/protocol/protocolPrintout";
export async function setProtocolId(to: any, from: any, next: any) {
const protocol = useProtocolStore();

View file

@ -1,20 +0,0 @@
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
export async function setEquipmentId(to: any, from: any, next: any) {
const EquipmentStore = useEquipmentStore();
EquipmentStore.activeEquipment = to.params?.equipmentId ?? null;
//useXYStore().$reset();
next();
}
export async function resetEquipmentStores(to: any, from: any, next: any) {
const EquipmentStore = useEquipmentStore();
EquipmentStore.activeEquipment = null;
EquipmentStore.activeEquipmentObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipmentType";
export async function setEquipmentTypeId(to: any, from: any, next: any) {
const equipmentTypeStore = useEquipmentTypeStore();
equipmentTypeStore.activeEquipmentType = to.params?.equipmentTypeId ?? null;
//useXYStore().$reset();
next();
}
export async function resetEquipmentTypeStores(to: any, from: any, next: any) {
const equipmentTypeStore = useEquipmentTypeStore();
equipmentTypeStore.activeEquipmentType = null;
equipmentTypeStore.activeEquipmentTypeObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useInspectionPlanStore } from "@/stores/admin/unit/inspectionPlan/inspectionPlan";
export async function setInspectionPlanId(to: any, from: any, next: any) {
const InspectionPlanStore = useInspectionPlanStore();
InspectionPlanStore.activeInspectionPlan = to.params?.inspectionPlanId ?? null;
//useXYStore().$reset();
next();
}
export async function resetInspectionPlanStores(to: any, from: any, next: any) {
const InspectionPlanStore = useInspectionPlanStore();
InspectionPlanStore.activeInspectionPlan = null;
InspectionPlanStore.activeInspectionPlanObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useRespiratoryGearStore } from "@/stores/admin/unit/respiratoryGear/respiratoryGear";
export async function setRespiratoryGearId(to: any, from: any, next: any) {
const RespiratoryGearStore = useRespiratoryGearStore();
RespiratoryGearStore.activeRespiratoryGear = to.params?.respiratoryGearId ?? null;
//useXYStore().$reset();
next();
}
export async function resetRespiratoryGearStores(to: any, from: any, next: any) {
const RespiratoryGearStore = useRespiratoryGearStore();
RespiratoryGearStore.activeRespiratoryGear = null;
RespiratoryGearStore.activeRespiratoryGearObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useRespiratoryMissionStore } from "@/stores/admin/unit/respiratoryMission/respiratoryMission";
export async function setRespiratoryMissionId(to: any, from: any, next: any) {
const RespiratoryMissionStore = useRespiratoryMissionStore();
RespiratoryMissionStore.activeRespiratoryMission = to.params?.respiratoryMissionId ?? null;
//useXYStore().$reset();
next();
}
export async function resetRespiratoryMissionStores(to: any, from: any, next: any) {
const RespiratoryMissionStore = useRespiratoryMissionStore();
RespiratoryMissionStore.activeRespiratoryMission = null;
RespiratoryMissionStore.activeRespiratoryMissionObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useRespiratoryWearerStore } from "@/stores/admin/unit/respiratoryWearer/respiratoryWearer";
export async function setRespiratoryWearerId(to: any, from: any, next: any) {
const RespiratoryWearerStore = useRespiratoryWearerStore();
RespiratoryWearerStore.activeRespiratoryWearer = to.params?.respiratoryWearerId ?? null;
//useXYStore().$reset();
next();
}
export async function resetRespiratoryWearerStores(to: any, from: any, next: any) {
const RespiratoryWearerStore = useRespiratoryWearerStore();
RespiratoryWearerStore.activeRespiratoryWearer = null;
RespiratoryWearerStore.activeRespiratoryWearerObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useVehicleStore } from "@/stores/admin/unit/vehicle/vehicle";
export async function setVehicleId(to: any, from: any, next: any) {
const VehicleStore = useVehicleStore();
VehicleStore.activeVehicle = to.params?.vehicleId ?? null;
//useXYStore().$reset();
next();
}
export async function resetVehicleStores(to: any, from: any, next: any) {
const VehicleStore = useVehicleStore();
VehicleStore.activeVehicle = null;
VehicleStore.activeVehicleObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useVehicleTypeStore } from "@/stores/admin/unit/vehicleType/vehicleType";
export async function setVehicleTypeId(to: any, from: any, next: any) {
const vehicleTypeStore = useVehicleTypeStore();
vehicleTypeStore.activeVehicleType = to.params?.vehicleTypeId ?? null;
//useXYStore().$reset();
next();
}
export async function resetVehicleTypeStores(to: any, from: any, next: any) {
const vehicleTypeStore = useVehicleTypeStore();
vehicleTypeStore.activeVehicleType = null;
vehicleTypeStore.activeVehicleTypeObj = null;
//useXYStore().$reset();
next();
}

View file

@ -1,20 +0,0 @@
import { useWearableStore } from "@/stores/admin/unit/wearable/wearable";
export async function setWearableId(to: any, from: any, next: any) {
const WearableStore = useWearableStore();
WearableStore.activeWearable = to.params?.wearableId ?? null;
//useXYStore().$reset();
next();
}
export async function resetWearableStores(to: any, from: any, next: any) {
const WearableStore = useWearableStore();
WearableStore.activeWearable = null;
WearableStore.activeWearableObj = null;
//useXYStore().$reset();
next();
}

View file

@ -57,15 +57,6 @@ export const useNavigationStore = defineStore("navigation", {
} as topLevelNavigationModel,
]
: []),
...(abilityStore.canSection("read", "unit")
? [
{
key: "unit",
title: "Wehr",
levelDefault: "equipment",
} as topLevelNavigationModel,
]
: []),
...(abilityStore.canSection("read", "configuration")
? [
{
@ -104,41 +95,8 @@ export const useNavigationStore = defineStore("navigation", {
...(abilityStore.can("read", "club", "listprint") ? [{ key: "listprint", title: "Liste Drucken" }] : []),
],
},
unit: {
mainTitle: "Wehr",
main: [
...(abilityStore.can("read", "unit", "equipment") ? [{ key: "equipment", title: "Gerätschaften" }] : []),
...(abilityStore.can("read", "unit", "vehicle") ? [{ key: "vehicle", title: "Fahrzeuge" }] : []),
...(abilityStore.can("read", "unit", "wearable") ? [{ key: "wearable", title: "Kleidung" }] : []),
...(abilityStore.can("read", "unit", "respiratory_gear")
? [{ key: "respiratory_gear", title: "Atemschutz-Geräte" }]
: []),
...(abilityStore.can("read", "unit", "respiratory_wearer")
? [{ key: "respiratory_wearer", title: "Atemschutz-Träger" }]
: []),
...(abilityStore.can("read", "unit", "respiratory_mission")
? [{ key: "respiratory_mission", title: "Atemschutz-Einsätze" }]
: []),
...(abilityStore.can("read", "unit", "damage_report")
? [{ key: "damage_report", title: "Schadensmeldungen" }]
: []),
{ key: "divider1", title: "Basisdaten" },
...(abilityStore.can("read", "unit", "equipment_type")
? [{ key: "equipment_type", title: "Geräte-Typen" }]
: []),
...(abilityStore.can("read", "unit", "vehicle_type")
? [{ key: "vehicle_type", title: "Fahrzeug-Arten" }]
: []),
...(abilityStore.can("read", "unit", "wearable_type")
? [{ key: "wearable_type", title: "Kleidungs-Arten" }]
: []),
...(abilityStore.can("read", "unit", "inspection_plan")
? [{ key: "inspection_plan", title: "Prüfpläne" }]
: []),
],
},
configuration: {
mainTitle: "Konfiguration",
mainTitle: "Einstellungen",
main: [
{ key: "divider1", title: "Mitgliederdaten" },
...(abilityStore.can("read", "configuration", "salutation")

View file

@ -1,91 +0,0 @@
import { defineStore } from "pinia";
import type {
DamageReportViewModel,
CreateDamageReportViewModel,
UpdateDamageReportViewModel,
} from "@/viewmodels/admin/unit/damageReport/damageReport.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import type { VehicleViewModel } from "../../../../viewmodels/admin/unit/vehicle/vehicle.models";
import { damageReportDemoData } from "../../../../demodata/damageReport";
export const useDamageReportStore = defineStore("damageReport", {
state: () => {
return {
damageReports: [] as Array<DamageReportViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchDamageReports(offset = 0, count = 25, search = "", clear = false) {
this.damageReports = damageReportDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.damageReports.length;
this.loading = "fetched";
return;
if (clear) this.damageReports = [];
this.loading = "loading";
http
.get(`/admin/damageReport?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.damageReports
.filter((elem: DamageReportViewModel) => this.damageReports.findIndex((m) => m.id == elem.id) == -1)
.map((elem: DamageReportViewModel, index: number): DamageReportViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: DamageReportViewModel & { tab_pos: number }) => {
this.damageReports.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllDamageReports(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/damageReport?noLimit=true`).then((res) => {
return { ...res, data: res.data.damageReports };
});
},
async getDamageReportsByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> {
return await http
.post(`/admin/damageReport/ids`, {
ids,
})
.then((res) => {
return { ...res, data: res.data.damageReports };
});
},
async searchDamageReports(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/damageReport?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.damageReports };
});
},
fetchDamageReportById(id: string) {
return http.get(`/admin/damageReport/${id}`);
},
async createDamageReport(damageReport: CreateDamageReportViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/damageReport`, {
// TODO: data
});
this.fetchDamageReports();
return result;
},
async updateDamageReport(damageReport: UpdateDamageReportViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/damageReport/${damageReport.id}`, {
// TODO: data
});
this.fetchDamageReports();
return result;
},
async deleteDamageReport(damageReport: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/damageReport/${damageReport}`);
this.fetchDamageReports();
return result;
},
},
});

View file

@ -1,108 +0,0 @@
import { defineStore } from "pinia";
import type {
EquipmentViewModel,
CreateEquipmentViewModel,
UpdateEquipmentViewModel,
} from "@/viewmodels/admin/unit/equipment/equipment.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { equipmentDemoData } from "../../../../demodata/equipment";
export const useEquipmentStore = defineStore("equipment", {
state: () => {
return {
equipments: [] as Array<EquipmentViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeEquipment: null as string | null,
activeEquipmentObj: null as EquipmentViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchEquipments(offset = 0, count = 25, search = "", clear = false) {
this.equipments = equipmentDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.equipments.length;
this.loading = "fetched";
return;
if (clear) this.equipments = [];
this.loading = "loading";
http
.get(`/admin/equipment?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.equipments
.filter((elem: EquipmentViewModel) => this.equipments.findIndex((m) => m.id == elem.id) == -1)
.map((elem: EquipmentViewModel, index: number): EquipmentViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: EquipmentViewModel & { tab_pos: number }) => {
this.equipments.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllEquipments(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/equipment?noLimit=true`).then((res) => {
return { ...res, data: res.data.equipments };
});
},
async getEquipmentsByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> {
return await http
.post(`/admin/equipment/ids`, {
ids,
})
.then((res) => {
return { ...res, data: res.data.equipments };
});
},
async searchEquipments(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/equipment?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.equipments };
});
},
fetchEquipmentByActiveId() {
this.activeEquipmentObj = equipmentDemoData.find((e) => e.id == this.activeEquipment) as EquipmentViewModel;
this.loading = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/equipment/${this.activeEquipment}`)
.then((res) => {
this.activeEquipmentObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchEquipmentById(id: string) {
return http.get(`/admin/equipment/${id}`);
},
async createEquipment(equipment: CreateEquipmentViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/equipment`, {
// TODO: data
});
this.fetchEquipments();
return result;
},
async updateActiveEquipment(equipment: UpdateEquipmentViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/equipment/${equipment.id}`, {
// TODO: data
});
this.fetchEquipments();
return result;
},
async deleteEquipment(equipment: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/equipment/${equipment}`);
this.fetchEquipments();
return result;
},
},
});

View file

@ -1,101 +0,0 @@
import { defineStore } from "pinia";
import type {
EquipmentTypeViewModel,
CreateEquipmentTypeViewModel,
UpdateEquipmentTypeViewModel,
} from "@/viewmodels/admin/unit/equipmentType/equipmentType.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { equipmentTypeDemoData } from "../../../../demodata/equipmentType";
export const useEquipmentTypeStore = defineStore("equipmentType", {
state: () => {
return {
equipmentTypes: [] as Array<EquipmentTypeViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeEquipmentType: null as string | null,
activeEquipmentTypeObj: null as EquipmentTypeViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchEquipmentTypes(offset = 0, count = 25, search = "", clear = false) {
this.equipmentTypes = equipmentTypeDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.equipmentTypes.length;
this.loading = "fetched";
return;
if (clear) this.equipmentTypes = [];
this.loading = "loading";
http
.get(`/admin/equipmentType?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.equipments
.filter((elem: EquipmentTypeViewModel) => this.equipmentTypes.findIndex((m) => m.id == elem.id) == -1)
.map((elem: EquipmentTypeViewModel, index: number): EquipmentTypeViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: EquipmentTypeViewModel & { tab_pos: number }) => {
this.equipmentTypes.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllEquipmentTypes(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/equipmentType?noLimit=true`).then((res) => {
return { ...res, data: res.data.equipments };
});
},
async searchEquipmentTypes(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/equipmentType?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.equipments };
});
},
fetchEquipmentTypeByActiveId() {
this.activeEquipmentTypeObj = equipmentTypeDemoData.find(
(e) => e.id == this.activeEquipmentType
) as EquipmentTypeViewModel;
this.loadingActive = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/equipmentType/${this.activeEquipmentType}`)
.then((res) => {
this.activeEquipmentTypeObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchEquipmentTypeById(id: string) {
return http.get(`/admin/equipmentType/${id}`);
},
async createEquipmentType(equipmentType: CreateEquipmentTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/equipmentType`, {
// TODO: data
});
this.fetchEquipmentTypes();
return result;
},
async updateActiveEquipmentType(equipmentType: UpdateEquipmentTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/equipmentType/${equipmentType.id}`, {
// TODO: data
});
this.fetchEquipmentTypes();
return result;
},
async deleteEquipmentType(equipmentType: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/equipmentType/${equipmentType}`);
this.fetchEquipmentTypes();
return result;
},
},
});

View file

@ -1,110 +0,0 @@
import { defineStore } from "pinia";
import type {
InspectionPlanViewModel,
CreateInspectionPlanViewModel,
UpdateInspectionPlanViewModel,
} from "@/viewmodels/admin/unit/inspectionPlan/inspectionPlan.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { inspectionPlanDemoData } from "@/demodata/inspectionPlan";
export const useInspectionPlanStore = defineStore("inspectionPlan", {
state: () => {
return {
inspectionPlans: [] as Array<InspectionPlanViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeInspectionPlan: null as string | null,
activeInspectionPlanObj: null as InspectionPlanViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchInspectionPlans(offset = 0, count = 25, search = "", clear = false) {
this.inspectionPlans = inspectionPlanDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.inspectionPlans.length;
this.loading = "fetched";
return;
if (clear) this.inspectionPlans = [];
this.loading = "loading";
http
.get(`/admin/inspectionPlan?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.inspectionPlans
.filter((elem: InspectionPlanViewModel) => this.inspectionPlans.findIndex((m) => m.id == elem.id) == -1)
.map((elem: InspectionPlanViewModel, index: number): InspectionPlanViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: InspectionPlanViewModel & { tab_pos: number }) => {
this.inspectionPlans.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllInspectionPlans(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/inspectionPlan?noLimit=true`).then((res) => {
return { ...res, data: res.data.inspectionPlans };
});
},
async getInspectionPlansByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> {
return await http
.post(`/admin/inspectionPlan/ids`, {
ids,
})
.then((res) => {
return { ...res, data: res.data.inspectionPlans };
});
},
async searchInspectionPlans(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/inspectionPlan?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.inspectionPlans };
});
},
fetchInspectionPlanByActiveId() {
this.activeInspectionPlanObj = inspectionPlanDemoData.find(
(e) => e.id == this.activeInspectionPlan
) as InspectionPlanViewModel;
this.loading = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/inspectionPlan/${this.activeInspectionPlan}`)
.then((res) => {
this.activeInspectionPlanObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchInspectionPlanById(id: string) {
return http.get(`/admin/inspectionPlan/${id}`);
},
async createInspectionPlan(inspectionPlan: CreateInspectionPlanViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/inspectionPlan`, {
// TODO: data
});
this.fetchInspectionPlans();
return result;
},
async updateActiveInspectionPlan(inspectionPlan: UpdateInspectionPlanViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/inspectionPlan/${inspectionPlan.id}`, {
// TODO: data
});
this.fetchInspectionPlans();
return result;
},
async deleteInspectionPlan(inspectionPlan: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/inspectionPlan/${inspectionPlan}`);
this.fetchInspectionPlans();
return result;
},
},
});

View file

@ -1,103 +0,0 @@
import { defineStore } from "pinia";
import type {
RespiratoryGearViewModel,
CreateRespiratoryGearViewModel,
UpdateRespiratoryGearViewModel,
} from "@/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { respiratoryGearDemoData } from "../../../../demodata/respiratoryGear";
export const useRespiratoryGearStore = defineStore("respiratoryGear", {
state: () => {
return {
respiratoryGears: [] as Array<RespiratoryGearViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeRespiratoryGear: null as string | null,
activeRespiratoryGearObj: null as RespiratoryGearViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchRespiratoryGears(offset = 0, count = 25, search = "", clear = false) {
this.respiratoryGears = respiratoryGearDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.respiratoryGears.length;
this.loading = "fetched";
return;
if (clear) this.respiratoryGears = [];
this.loading = "loading";
http
.get(`/admin/respiratoryGear?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.respiratoryGears
.filter((elem: RespiratoryGearViewModel) => this.respiratoryGears.findIndex((m) => m.id == elem.id) == -1)
.map((elem: RespiratoryGearViewModel, index: number): RespiratoryGearViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: RespiratoryGearViewModel & { tab_pos: number }) => {
this.respiratoryGears.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllRespiratoryGears(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/respiratoryGear?noLimit=true`).then((res) => {
return { ...res, data: res.data.respiratoryGears };
});
},
async searchRespiratoryGears(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/respiratoryGear?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.respiratoryGears };
});
},
fetchRespiratoryGearByActiveId() {
this.activeRespiratoryGearObj = respiratoryGearDemoData.find(
(e) => e.id == this.activeRespiratoryGear
) as RespiratoryGearViewModel;
this.loading = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/respiratoryGear/${this.activeRespiratoryGear}`)
.then((res) => {
this.activeRespiratoryGearObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchRespiratoryGearById(id: string) {
return http.get(`/admin/respiratoryGear/${id}`);
},
async createRespiratoryGear(respiratoryGear: CreateRespiratoryGearViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/respiratoryGear`, {
// TODO: data
});
this.fetchRespiratoryGears();
return result;
},
async updateActiveRespiratoryGear(
respiratoryGear: UpdateRespiratoryGearViewModel
): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/respiratoryGear/${respiratoryGear.id}`, {
// TODO: data
});
this.fetchRespiratoryGears();
return result;
},
async deleteRespiratoryGear(respiratoryGear: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/respiratoryGear/${respiratoryGear}`);
this.fetchRespiratoryGears();
return result;
},
},
});

View file

@ -1,109 +0,0 @@
import { defineStore } from "pinia";
import type {
RespiratoryMissionViewModel,
CreateRespiratoryMissionViewModel,
UpdateRespiratoryMissionViewModel,
} from "@/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { respiratoryMissionDemoData } from "../../../../demodata/respiratoryMission";
export const useRespiratoryMissionStore = defineStore("respiratoryMission", {
state: () => {
return {
respiratoryMissions: [] as Array<RespiratoryMissionViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeRespiratoryMission: null as string | null,
activeRespiratoryMissionObj: null as RespiratoryMissionViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchRespiratoryMissions(offset = 0, count = 25, search = "", clear = false) {
this.respiratoryMissions = respiratoryMissionDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.respiratoryMissions.length;
this.loading = "fetched";
return;
if (clear) this.respiratoryMissions = [];
this.loading = "loading";
http
.get(`/admin/respiratoryMission?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.respiratoryMissions
.filter(
(elem: RespiratoryMissionViewModel) => this.respiratoryMissions.findIndex((m) => m.id == elem.id) == -1
)
.map(
(elem: RespiratoryMissionViewModel, index: number): RespiratoryMissionViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
}
)
.forEach((elem: RespiratoryMissionViewModel & { tab_pos: number }) => {
this.respiratoryMissions.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllRespiratoryMissions(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/respiratoryMission?noLimit=true`).then((res) => {
return { ...res, data: res.data.respiratoryMissions };
});
},
async searchRespiratoryMissions(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/respiratoryMission?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.respiratoryMissions };
});
},
fetchRespiratoryMissionByActiveId() {
this.activeRespiratoryMissionObj = this.respiratoryMissions.find(
(e) => e.id == this.activeRespiratoryMission
) as RespiratoryMissionViewModel;
this.loading = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/respiratoryMission/${this.activeRespiratoryMission}`)
.then((res) => {
this.activeRespiratoryMissionObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchRespiratoryMissionById(id: string) {
return http.get(`/admin/respiratoryMission/${id}`);
},
async createRespiratoryMission(
respiratoryMission: CreateRespiratoryMissionViewModel
): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/respiratoryMission`, {
// TODO: data
});
this.fetchRespiratoryMissions();
return result;
},
async updateActiveRespiratoryMission(
respiratoryMission: UpdateRespiratoryMissionViewModel
): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/respiratoryMission/${respiratoryMission.id}`, {
// TODO: data
});
this.fetchRespiratoryMissions();
return result;
},
async deleteRespiratoryMission(respiratoryMission: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/respiratoryMission/${respiratoryMission}`);
this.fetchRespiratoryMissions();
return result;
},
},
});

View file

@ -1,109 +0,0 @@
import { defineStore } from "pinia";
import type {
RespiratoryWearerViewModel,
CreateRespiratoryWearerViewModel,
UpdateRespiratoryWearerViewModel,
} from "@/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { respiratoryWearerDemoData } from "../../../../demodata/respiratoryWearer";
export const useRespiratoryWearerStore = defineStore("respiratoryWearer", {
state: () => {
return {
respiratoryWearers: [] as Array<RespiratoryWearerViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeRespiratoryWearer: null as string | null,
activeRespiratoryWearerObj: null as RespiratoryWearerViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchRespiratoryWearers(offset = 0, count = 25, search = "", clear = false) {
this.respiratoryWearers = respiratoryWearerDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.respiratoryWearers.length;
this.loading = "fetched";
return;
if (clear) this.respiratoryWearers = [];
this.loading = "loading";
http
.get(`/admin/respiratoryWearer?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.respiratoryWearers
.filter(
(elem: RespiratoryWearerViewModel) => this.respiratoryWearers.findIndex((m) => m.id == elem.id) == -1
)
.map(
(elem: RespiratoryWearerViewModel, index: number): RespiratoryWearerViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
}
)
.forEach((elem: RespiratoryWearerViewModel & { tab_pos: number }) => {
this.respiratoryWearers.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllRespiratoryWearers(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/respiratoryWearer?noLimit=true`).then((res) => {
return { ...res, data: res.data.respiratoryWearers };
});
},
async searchRespiratoryWearers(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/respiratoryWearer?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.respiratoryWearers };
});
},
fetchRespiratoryWearerByActiveId() {
this.activeRespiratoryWearerObj = respiratoryWearerDemoData.find(
(e) => e.id == this.activeRespiratoryWearer
) as RespiratoryWearerViewModel;
this.loading = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/respiratoryWearer/${this.activeRespiratoryWearer}`)
.then((res) => {
this.activeRespiratoryWearerObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchRespiratoryWearerById(id: string) {
return http.get(`/admin/respiratoryWearer/${id}`);
},
async createRespiratoryWearer(
respiratoryWearer: CreateRespiratoryWearerViewModel
): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/respiratoryWearer`, {
// TODO: data
});
this.fetchRespiratoryWearers();
return result;
},
async updateActiveRespiratoryWearer(
respiratoryWearer: UpdateRespiratoryWearerViewModel
): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/respiratoryWearer/${respiratoryWearer.id}`, {
// TODO: data
});
this.fetchRespiratoryWearers();
return result;
},
async deleteRespiratoryWearer(respiratoryWearer: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/respiratoryWearer/${respiratoryWearer}`);
this.fetchRespiratoryWearers();
return result;
},
},
});

View file

@ -1,108 +0,0 @@
import { defineStore } from "pinia";
import type {
VehicleViewModel,
CreateVehicleViewModel,
UpdateVehicleViewModel,
} from "@/viewmodels/admin/unit/vehicle/vehicle.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { vehicleDemoData } from "../../../../demodata/vehicle";
export const useVehicleStore = defineStore("vehicle", {
state: () => {
return {
vehicles: [] as Array<VehicleViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeVehicle: null as string | null,
activeVehicleObj: null as VehicleViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchVehicles(offset = 0, count = 25, search = "", clear = false) {
this.vehicles = vehicleDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.vehicles.length;
this.loading = "fetched";
return;
if (clear) this.vehicles = [];
this.loading = "loading";
http
.get(`/admin/vehicle?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.vehicles
.filter((elem: VehicleViewModel) => this.vehicles.findIndex((m) => m.id == elem.id) == -1)
.map((elem: VehicleViewModel, index: number): VehicleViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: VehicleViewModel & { tab_pos: number }) => {
this.vehicles.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllVehicles(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/vehicle?noLimit=true`).then((res) => {
return { ...res, data: res.data.vehicles };
});
},
async getVehiclesByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> {
return await http
.post(`/admin/vehicle/ids`, {
ids,
})
.then((res) => {
return { ...res, data: res.data.vehicles };
});
},
async searchVehicles(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/vehicle?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.vehicles };
});
},
fetchVehicleByActiveId() {
this.activeVehicleObj = vehicleDemoData.find((e) => e.id == this.activeVehicle) as VehicleViewModel;
this.loading = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/vehicle/${this.activeVehicle}`)
.then((res) => {
this.activeVehicleObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchVehicleById(id: string) {
return http.get(`/admin/vehicle/${id}`);
},
async createVehicle(vehicle: CreateVehicleViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/vehicle`, {
// TODO: data
});
this.fetchVehicles();
return result;
},
async updateActiveVehicle(vehicle: UpdateVehicleViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/vehicle/${vehicle.id}`, {
// TODO: data
});
this.fetchVehicles();
return result;
},
async deleteVehicle(vehicle: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/vehicle/${vehicle}`);
this.fetchVehicles();
return result;
},
},
});

View file

@ -1,101 +0,0 @@
import { defineStore } from "pinia";
import type {
VehicleTypeViewModel,
CreateVehicleTypeViewModel,
UpdateVehicleTypeViewModel,
} from "@/viewmodels/admin/unit/vehicleType/vehicleType.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { vehicleTypeDemoData } from "../../../../demodata/vehicleType";
export const useVehicleTypeStore = defineStore("vehicleType", {
state: () => {
return {
vehicleTypes: [] as Array<VehicleTypeViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeVehicleType: null as string | null,
activeVehicleTypeObj: null as VehicleTypeViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchVehicleTypes(offset = 0, count = 25, search = "", clear = false) {
this.vehicleTypes = vehicleTypeDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.vehicleTypes.length;
this.loading = "fetched";
return;
if (clear) this.vehicleTypes = [];
this.loading = "loading";
http
.get(`/admin/vehicleType?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.vehicles
.filter((elem: VehicleTypeViewModel) => this.vehicleTypes.findIndex((m) => m.id == elem.id) == -1)
.map((elem: VehicleTypeViewModel, index: number): VehicleTypeViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: VehicleTypeViewModel & { tab_pos: number }) => {
this.vehicleTypes.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllVehicleTypes(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/vehicleType?noLimit=true`).then((res) => {
return { ...res, data: res.data.vehicles };
});
},
async searchVehicleTypes(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/vehicleType?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.vehicles };
});
},
fetchVehicleTypeByActiveId() {
this.activeVehicleTypeObj = vehicleTypeDemoData.find(
(e) => e.id == this.activeVehicleType
) as VehicleTypeViewModel;
this.loadingActive = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/vehicleType/${this.activeVehicleType}`)
.then((res) => {
this.activeVehicleTypeObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchVehicleTypeById(id: string) {
return http.get(`/admin/vehicleType/${id}`);
},
async createVehicleType(vehicleType: CreateVehicleTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/vehicleType`, {
// TODO: data
});
this.fetchVehicleTypes();
return result;
},
async updateActiveVehicleType(vehicleType: UpdateVehicleTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/vehicleType/${vehicleType.id}`, {
// TODO: data
});
this.fetchVehicleTypes();
return result;
},
async deleteVehicleType(vehicleType: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/vehicleType/${vehicleType}`);
this.fetchVehicleTypes();
return result;
},
},
});

View file

@ -1,108 +0,0 @@
import { defineStore } from "pinia";
import type {
WearableViewModel,
CreateWearableViewModel,
UpdateWearableViewModel,
} from "@/viewmodels/admin/unit/wearable/wearable.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { wearableDemoData } from "../../../../demodata/wearable";
export const useWearableStore = defineStore("wearable", {
state: () => {
return {
wearables: [] as Array<WearableViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
activeWearable: null as string | null,
activeWearableObj: null as WearableViewModel | null,
loadingActive: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchWearables(offset = 0, count = 25, search = "", clear = false) {
this.wearables = wearableDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.wearables.length;
this.loading = "fetched";
return;
if (clear) this.wearables = [];
this.loading = "loading";
http
.get(`/admin/wearable?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.wearables
.filter((elem: WearableViewModel) => this.wearables.findIndex((m) => m.id == elem.id) == -1)
.map((elem: WearableViewModel, index: number): WearableViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: WearableViewModel & { tab_pos: number }) => {
this.wearables.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllWearables(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/wearable?noLimit=true`).then((res) => {
return { ...res, data: res.data.wearables };
});
},
async getWearablesByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> {
return await http
.post(`/admin/wearable/ids`, {
ids,
})
.then((res) => {
return { ...res, data: res.data.wearables };
});
},
async searchWearables(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/wearable?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.wearables };
});
},
fetchWearableByActiveId() {
this.activeWearableObj = wearableDemoData.find((e) => e.id == this.activeWearable) as WearableViewModel;
this.loading = "fetched";
return;
this.loadingActive = "loading";
http
.get(`/admin/wearable/${this.activeWearable}`)
.then((res) => {
this.activeWearableObj = res.data;
this.loadingActive = "fetched";
})
.catch((err) => {
this.loadingActive = "failed";
});
},
fetchWearableById(id: string) {
return http.get(`/admin/wearable/${id}`);
},
async createWearable(wearable: CreateWearableViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/wearable`, {
// TODO: data
});
this.fetchWearables();
return result;
},
async updateActiveWearable(wearable: UpdateWearableViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/wearable/${wearable.id}`, {
// TODO: data
});
this.fetchWearables();
return result;
},
async deleteWearable(wearable: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/wearable/${wearable}`);
this.fetchWearables();
return result;
},
},
});

View file

@ -1,81 +0,0 @@
import { defineStore } from "pinia";
import type {
WearableTypeViewModel,
CreateWearableTypeViewModel,
UpdateWearableTypeViewModel,
} from "@/viewmodels/admin/unit/wearableType/wearableType.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { wearableTypeDemoData } from "../../../../demodata/wearableType";
export const useWearableTypeStore = defineStore("wearableType", {
state: () => {
return {
wearableTypes: [] as Array<WearableTypeViewModel & { tab_pos: number }>,
totalCount: 0 as number,
loading: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchWearableTypes(offset = 0, count = 25, search = "", clear = false) {
this.wearableTypes = wearableTypeDemoData.map((e, i) => ({ ...e, tab_pos: i }));
this.totalCount = this.wearableTypes.length;
this.loading = "fetched";
return;
if (clear) this.wearableTypes = [];
this.loading = "loading";
http
.get(`/admin/wearableType?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
.then((result) => {
this.totalCount = result.data.total;
result.data.wearables
.filter((elem: WearableTypeViewModel) => this.wearableTypes.findIndex((m) => m.id == elem.id) == -1)
.map((elem: WearableTypeViewModel, index: number): WearableTypeViewModel & { tab_pos: number } => {
return {
...elem,
tab_pos: index + offset,
};
})
.forEach((elem: WearableTypeViewModel & { tab_pos: number }) => {
this.wearableTypes.push(elem);
});
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getAllWearableTypes(): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/wearableType?noLimit=true`).then((res) => {
return { ...res, data: res.data.wearables };
});
},
async searchWearableTypes(search: string): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/wearableType?search=${search}&noLimit=true`).then((res) => {
return { ...res, data: res.data.wearables };
});
},
fetchWearableTypeById(id: string) {
return http.get(`/admin/wearableType/${id}`);
},
async createWearableType(wearableType: CreateWearableTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.post(`/admin/wearableType`, {
// TODO: data
});
this.fetchWearableTypes();
return result;
},
async updateWearableType(wearableType: UpdateWearableTypeViewModel): Promise<AxiosResponse<any, any>> {
const result = await http.patch(`/admin/wearableType/${wearableType.id}`, {
// TODO: data
});
this.fetchWearableTypes();
return result;
},
async deleteWearableType(wearableType: number): Promise<AxiosResponse<any, any>> {
const result = await http.delete(`/admin/wearableType/${wearableType}`);
this.fetchWearableTypes();
return result;
},
},
});

View file

@ -4,22 +4,19 @@ export const useModalStore = defineStore("modal", {
state: () => {
return {
show: false,
component_ref: undefined as any,
data: undefined as any,
callback: undefined as undefined | Function,
component_ref: null as any,
data: null as any,
};
},
actions: {
openModal(component_ref: any, data?: any, callback?: Function) {
openModal(component_ref: any, data?: any) {
this.component_ref = component_ref;
this.data = data;
this.callback = callback;
this.show = true;
},
closeModal() {
this.component_ref = undefined;
this.data = undefined;
this.callback = undefined;
this.component_ref = null;
this.data = null;
this.show = false;
},
},

View file

@ -1,9 +1,10 @@
export interface DynamicQueryStructure {
id: string;
select: string[] | "*";
table: string;
where?: Array<ConditionStructure>;
join?: Array<DynamicQueryStructure & { foreignColumn: string }>;
orderBy?: Array<OrderByStructure>;
join?: Array<DynamicQueryStructure & JoinStructure>;
orderBy?: Array<OrderByStructure>; // only at top level
}
export type ConditionStructure = (
@ -47,7 +48,12 @@ export type WhereOperation =
| "timespanEq"; // Date before x years (YYYY-01-01 <bis> YYYY-12-31)
// TODO: age between | age equals | age greater | age smaller
export type JoinStructure = { foreignColumn: string; type: "defined" } | { condition: string; type: "custom" };
export type OrderByStructure = {
id: string;
depth: number;
table: string;
column: string;
order: OrderByType;
};

View file

@ -1,42 +1,27 @@
export type PermissionSection = "club" | "unit" | "configuration" | "management";
export type PermissionSection = "club" | "configuration" | "management";
export type PermissionModule =
// club
| "member"
| "calendar"
| "newsletter"
| "newsletter_config"
| "protocol"
| "query"
| "listprint"
// unit
| "equipment"
| "equipment_type"
| "vehicle"
| "vehicle_type"
| "inspection_plan"
| "respiratory_gear"
| "respiratory_wearer"
| "respiratory_mission"
| "damage_report"
| "wearable"
| "wearable_type"
// configuration
| "qualification"
| "award"
| "executive_position"
| "communication_type"
| "membership_status"
| "newsletter_config"
| "salutation"
| "calendar_type"
| "user"
| "role"
| "webapi"
| "query"
| "query_store"
| "template"
| "template_usage"
| "backup"
// management
| "user"
| "role"
| "webapi";
| "backup";
export type PermissionType = "read" | "create" | "update" | "delete";
@ -59,61 +44,33 @@ export type SectionsAndModulesObject = {
[section in PermissionSection]: Array<PermissionModule>;
};
export const permissionSections: Array<PermissionSection> = ["club", "unit", "configuration", "management"];
export const permissionSections: Array<PermissionSection> = ["club", "configuration", "management"];
export const permissionModules: Array<PermissionModule> = [
// club
"member",
"calendar",
"newsletter",
"newsletter_config",
"protocol",
"query",
"listprint",
// unit
"equipment",
"equipment_type",
"vehicle",
"vehicle_type",
"inspection_plan",
"respiratory_gear",
"respiratory_wearer",
"respiratory_mission",
"damage_report",
"wearable",
"wearable_type",
// configuration
"qualification",
"award",
"executive_position",
"communication_type",
"membership_status",
"newsletter_config",
"salutation",
"calendar_type",
"user",
"role",
"webapi",
"query",
"query_store",
"template",
"template_usage",
"backup",
// management
"user",
"role",
"webapi",
];
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
export const sectionsAndModules: SectionsAndModulesObject = {
club: ["member", "calendar", "newsletter", "protocol", "query", "listprint"],
unit: [
"equipment",
"equipment_type",
"vehicle",
"vehicle_type",
"inspection_plan",
"respiratory_gear",
"respiratory_wearer",
"respiratory_mission",
"damage_report",
"wearable",
"wearable_type",
],
configuration: [
"qualification",
"award",

View file

@ -1,22 +0,0 @@
import type { EquipmentViewModel } from "../equipment/equipment.models";
export interface DamageReportViewModel {
id: string;
reported: Date;
status: string;
done: boolean;
description: string;
affectedEquipmentId: string;
affectedEquipment: EquipmentViewModel;
}
export interface CreateDamageReportViewModel {
description: string;
affectedEquipmentId: string;
}
export interface UpdateDamageReportViewModel {
id: string;
status: string;
done: boolean;
}

View file

@ -1,24 +0,0 @@
import type { EquipmentTypeViewModel } from "../equipmentType/equipmentType.models";
export interface EquipmentViewModel {
id: string;
code: string;
name: string;
location: string;
equipmentTypeId: string;
equipmentType: EquipmentTypeViewModel;
}
export interface CreateEquipmentViewModel {
code: string;
name: string;
location: string;
equipmentTypeId: string;
}
export interface UpdateEquipmentViewModel {
id: string;
code: string;
name: string;
location: string;
}

View file

@ -1,17 +0,0 @@
export interface EquipmentTypeViewModel {
id: string;
type: string;
description: string;
// attached inspection plans
}
export interface CreateEquipmentTypeViewModel {
type: string;
description: string;
}
export interface UpdateEquipmentTypeViewModel {
id: string;
type: string;
description: string;
}

View file

@ -1,21 +0,0 @@
import type { EquipmentTypeViewModel } from "../equipmentType/equipmentType.models";
export interface InspectionPlanViewModel {
id: string;
title: string;
inspectionInterval?: `${number}-${"d" | "m" | "y"}` | `${number}/${number}`;
equipmentTypeId: string;
equipmentType: EquipmentTypeViewModel;
}
export interface CreateInspectionPlanViewModel {
title: string;
inspectionInterval?: `${number}-${"d" | "m" | "y"}` | `${number}/${number}`;
equipmentTypeId: string;
}
export interface UpdateInspectionPlanViewModel {
id: string;
title: string;
inspectionInterval?: `${number}-${"d" | "m" | "y"}` | `${number}/${number}`;
}

View file

@ -1,16 +0,0 @@
import type { EquipmentViewModel } from "../equipment/equipment.models";
export interface RespiratoryGearViewModel {
id: string;
equipmentId: string;
equipment: EquipmentViewModel;
}
export interface CreateRespiratoryGearViewModel {
equipmentId: string;
}
export interface UpdateRespiratoryGearViewModel {
id: string;
equipmentId: string;
}

View file

@ -1,20 +0,0 @@
export interface RespiratoryMissionViewModel {
id: string;
date: Date;
title: string;
description: string;
// refs to used respiratory gear and wearers
}
export interface CreateRespiratoryMissionViewModel {
date: Date;
title: string;
description: string;
}
export interface UpdateRespiratoryMissionViewModel {
id: string;
date: Date;
title: string;
description: string;
}

View file

@ -1,16 +0,0 @@
import type { MemberViewModel } from "../../club/member/member.models";
export interface RespiratoryWearerViewModel {
id: string;
memberId: string;
member: MemberViewModel;
}
export interface CreateRespiratoryWearerViewModel {
memberId: string;
}
export interface UpdateRespiratoryWearerViewModel {
id: string;
memberId: string;
}

View file

@ -1,16 +0,0 @@
export interface VehicleViewModel {
id: string;
name: string;
type: string;
}
export interface CreateVehicleViewModel {
name: string;
type: string;
}
export interface UpdateVehicleViewModel {
id: string;
name: string;
type: string;
}

View file

@ -1,16 +0,0 @@
export interface VehicleTypeViewModel {
id: string;
type: string;
description: string;
}
export interface CreateVehicleTypeViewModel {
type: string;
description: string;
}
export interface UpdateVehicleTypeViewModel {
id: string;
type: string;
description: string;
}

View file

@ -1,29 +0,0 @@
import type { MemberViewModel } from "../../club/member/member.models";
import type { WearableTypeViewModel } from "../wearableType/wearableType.models";
export interface WearableViewModel {
id: string;
code: string;
name: string;
location?: string;
wearerId?: string;
wearer: MemberViewModel;
wearableTypeId: string;
wearableType: WearableTypeViewModel;
}
export interface CreateWearableViewModel {
code: string;
name: string;
wearerId?: string;
location?: string;
wearableTypeId: string;
}
export interface UpdateWearableViewModel {
id: string;
code: string;
name: string;
location?: string;
wearerId?: string;
}

View file

@ -1,16 +0,0 @@
export interface WearableTypeViewModel {
id: string;
type: string;
description: string;
}
export interface CreateWearableTypeViewModel {
type: string;
description: string;
}
export interface UpdateWearableTypeViewModel {
id: string;
type: string;
description: string;
}

View file

@ -4,14 +4,17 @@
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8">Kalender</h1>
<div class="flex flex-row gap-2">
<PlusIcon class="text-gray-500 h-5 w-5 cursor-pointer" @click="select" />
<PlusIcon
class="text-gray-500 h-5 w-5 cursor-pointer"
@click="select({ start: '', end: '', allDay: false })"
/>
<LinkIcon class="text-gray-500 h-5 w-5 cursor-pointer" @click="openLinkModal" />
</div>
</div>
</template>
<template #diffMain>
<div class="flex flex-col w-full h-full gap-2 justify-between px-7 overflow-hidden">
<FullCalendar :options="calendarOptions" class="max-h-full h-full" />
<CustomCalendar :items="formattedItems" @date-select="select" @event-select="eventClick" />
</div>
</template>
</MainTemplate>
@ -22,14 +25,10 @@ import { defineComponent, markRaw, defineAsyncComponent } from "vue";
import { mapActions, mapState } from "pinia";
import { useModalStore } from "@/stores/modal";
import MainTemplate from "@/templates/Main.vue";
import FullCalendar from "@fullcalendar/vue3";
import deLocale from "@fullcalendar/core/locales/de";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { useCalendarStore } from "@/stores/admin/club/calendar";
import { useAbilityStore } from "@/stores/ability";
import { LinkIcon, PlusIcon } from "@heroicons/vue/24/outline";
import CustomCalendar from "@/components/CustomCalendar.vue";
</script>
<script lang="ts">
@ -40,33 +39,6 @@ export default defineComponent({
computed: {
...mapState(useCalendarStore, ["formattedItems"]),
...mapState(useAbilityStore, ["can"]),
calendarOptions() {
return {
timeZone: "local",
locale: deLocale,
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
headerToolbar: {
left: "dayGridMonth,timeGridWeek",
center: "title",
right: "prev,today,next",
},
eventDisplay: "block",
weekends: true,
editable: true,
selectable: true,
selectMirror: false,
dayMaxEvents: true,
weekNumbers: true,
displayEventTime: true,
nowIndicator: true,
weekText: "KW",
allDaySlot: false,
events: this.formattedItems,
select: this.select,
eventClick: this.eventClick,
};
},
},
mounted() {
this.fetchCalendars();
@ -74,22 +46,22 @@ export default defineComponent({
methods: {
...mapActions(useModalStore, ["openModal"]),
...mapActions(useCalendarStore, ["fetchCalendars"]),
select(e: any) {
select({ start, end, allDay }: { start: string; end: string; allDay: boolean }) {
if (!this.can("create", "club", "calendar")) return;
this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/CreateCalendarModal.vue"))),
{
start: e?.startStr ?? new Date().toISOString(),
end: e?.endStr ?? new Date().toISOString(),
allDay: e?.allDay ?? false,
start,
end,
allDay,
}
);
},
eventClick(e: any) {
eventClick(id: string) {
if (!this.can("update", "club", "calendar")) return;
this.openModal(
markRaw(defineAsyncComponent(() => import("@/components/admin/club/calendar/UpdateCalendarModal.vue"))),
e.event.id
id
);
},
openLinkModal(e: any) {
@ -99,55 +71,4 @@ export default defineComponent({
},
},
});
/**
locale: deLocale,
events: this.absencesList.map((x) => ({
id: x.absenceId,
start: x.startDate,
end: x.endDate,
allday: true,
backgroundColor: this.getColorForAbsenceType(x.absenceType),
borderColor: '#ffffff',
title: this.getAbsenceType(x.absenceType) + ' ' + x.fullName,
})),
plugins: [
interactionPlugin,
dayGridPlugin,
timeGridPlugin,
listPlugin,
multiMonthPlugin,
],
initialView: 'dayGridMonth',
eventDisplay: 'block',
weekends: false,
editable: true,
selectable: true,
selectMirror: true,
dayMaxEvents: true,
weekNumbers: true,
displayEventTime: false,
weekText: 'KW',
validRange: { start: '2023-01-01', end: '' },
headerToolbar: {
left: 'today prev,next',
center: 'title',
right: 'listMonth,dayGridMonth,multiMonthYear,customview',
},
views: {
customview: {
type: 'multiMonth',
multiMonthMaxColumns: 1,
duration: { month: 12 },
buttonText: 'grid',
},
},
dateClick: this.handleDateSelect.bind(this),
datesSet: this.handleMonthChange.bind(this),
select: this.handleDateSelect.bind(this),
eventClick: this.handleEventClick.bind(this),
eventsSet: this.handleEvents.bind(this),
};
*/
</script>

View file

@ -70,14 +70,11 @@ import { useQueryStoreStore } from "@/stores/admin/configuration/queryStore";
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useQueryBuilderStore, ["loading", "loadingData", "tableMetas", "data", "totalLength", "queryError"]),
...mapState(useQueryBuilderStore, ["loading", "loadingData", "data", "totalLength", "queryError"]),
...mapWritableState(useQueryBuilderStore, ["query"]),
},
mounted() {
this.fetchTableMetas();
},
methods: {
...mapActions(useQueryBuilderStore, ["fetchTableMetas", "sendQuery", "clearResults", "exportData"]),
...mapActions(useQueryBuilderStore, ["sendQuery", "clearResults", "exportData"]),
...mapActions(useQueryStoreStore, ["triggerSave"]),
},
});

View file

@ -1,46 +0,0 @@
<template>
<div class="flex flex-col w-full h-full gap-2 justify-center px-7">
<Pagination
:items="damageReports"
:totalCount="totalCount"
:indicateLoading="loading == 'loading'"
@load-data="(offset, count, search) => fetchDamageReports(offset, count, search)"
@search="(search) => fetchDamageReports(0, maxEntriesPerPage, search, true)"
>
<template #pageRow="{ row }: { row: DamageReportViewModel }">
<DamageReportListItem :damageReport="row" />
</template>
</Pagination>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { useAbilityStore } from "@/stores/ability";
import { useDamageReportStore } from "@/stores/admin/unit/damageReport/damageReport";
import type { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport/damageReport.models";
import Pagination from "@/components/Pagination.vue";
import DamageReportListItem from "@/components/admin/unit/damageReport/DamageReportListItem.vue";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
maxEntriesPerPage: 25,
};
},
computed: {
...mapState(useDamageReportStore, ["damageReports", "totalCount", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchDamageReports(0, this.maxEntriesPerPage, "", true);
},
methods: {
...mapActions(useDamageReportStore, ["fetchDamageReports"]),
},
});
</script>

View file

@ -1,69 +0,0 @@
<template>
<MainTemplate>
<template #topBar>
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8">Schadensmeldungen</h1>
</div>
</template>
<template #diffMain>
<div class="flex flex-col gap-2 grow px-7 overflow-hidden">
<div class="flex flex-col grow gap-2 overflow-hidden">
<div class="w-full flex flex-row max-lg:flex-wrap justify-center">
<RouterLink
v-for="tab in tabs"
:key="tab.route"
v-slot="{ isExactActive }"
:to="{ name: tab.route }"
class="w-1/2 md:w-1/3 lg:w-full p-0.5 first:pl-0 last:pr-0"
>
<p
:class="[
'w-full rounded-lg py-2.5 text-sm text-center font-medium leading-5 focus:ring-0 outline-hidden',
isExactActive ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : ' hover:bg-red-200',
]"
>
{{ tab.title }}
</p>
</RouterLink>
</div>
<RouterView />
</div>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { RouterLink, RouterView } from "vue-router";
import { useAbilityStore } from "@/stores/ability";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
</script>
<script lang="ts">
export default defineComponent({
props: {
equipmentId: String,
},
data() {
return {
tabs: [
{ route: "admin-unit-damage_report", title: "offen" },
{ route: "admin-unit-damage_report-done", title: "bearbeitet" },
],
};
},
computed: {
...mapState(useEquipmentStore, ["activeEquipmentObj"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchEquipmentByActiveId();
},
methods: {
...mapActions(useEquipmentStore, ["fetchEquipmentByActiveId"]),
},
});
</script>

View file

@ -1,211 +0,0 @@
<template>
<MainTemplate>
<template #topBar>
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8">Ausrüstung erfassen</h1>
</div>
</template>
<template #diffMain>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<form class="flex flex-col gap-4 py-2 w-full max-w-xl mx-auto" @submit.prevent="triggerCreate">
<div>
<Combobox v-model="selectedType">
<ComboboxLabel>Typ</ComboboxLabel>
<div class="relative mt-1">
<ComboboxInput
class="rounded-md shadow-xs relative block w-full px-3 py-2 border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-hidden focus:ring-0 focus:z-10 sm:text-sm resize-none"
@input="query = $event.target.value"
/>
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
</ComboboxButton>
<TransitionRoot
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
@after-leave="query = ''"
>
<ComboboxOptions
class="z-20 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-md ring-1 ring-black/5 focus:outline-hidden sm:text-sm"
>
<ComboboxOption v-if="loading || deferingSearch" as="template" disabled>
<li class="flex flex-row gap-2 text-text relative cursor-default select-none py-2 pl-3 pr-4">
<Spinner />
<span class="font-normal block truncate">suche</span>
</li>
</ComboboxOption>
<ComboboxOption v-else-if="filtered.length === 0 && query == ''" as="template" disabled>
<li class="text-text relative cursor-default select-none py-2 pl-3 pr-4">
<span class="font-normal block truncate">tippe, um zu suchen...</span>
</li>
</ComboboxOption>
<ComboboxOption v-else-if="filtered.length === 0" as="template" disabled>
<li class="text-text relative cursor-default select-none py-2 pl-3 pr-4">
<span class="font-normal block truncate">Keine Auswahl gefunden.</span>
</li>
</ComboboxOption>
<ComboboxOption
v-if="!(loading || deferingSearch)"
v-for="type in filtered"
as="template"
:key="type.id"
:value="type.id"
v-slot="{ selected, active }"
>
<li
class="relative cursor-default select-none py-2 pl-10 pr-4"
:class="{
'bg-primary text-white': active,
'text-gray-900': !active,
}"
>
<span class="block truncate" :class="{ 'font-medium': selected, 'font-normal': !selected }">
{{ type.type }}
</span>
<span
v-if="selected"
class="absolute inset-y-0 left-0 flex items-center pl-3"
:class="{ 'text-white': active, 'text-primary': !active }"
>
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
</ComboboxOptions>
</TransitionRoot>
</div>
</Combobox>
</div>
<div>
<label for="name">Bezeichnung</label>
<input type="text" id="name" required />
</div>
<ScanInput name="code" label="Code" :required="false" />
<div>
<label for="location">Verortung (optional)</label>
<input type="text" id="location" />
</div>
<div class="flex flex-row justify-end gap-2">
<RouterLink
:to="{ name: 'admin-unit-equipment' }"
primary-outline
button
class="w-fit!"
:disabled="status == 'loading' || status?.status == 'success'"
>
abbrechen
</RouterLink>
<button primary type="submit" class="w-fit!" :disabled="status == 'loading'">speichern</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
import type { CreateEquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import {
Combobox,
ComboboxLabel,
ComboboxInput,
ComboboxButton,
ComboboxOptions,
ComboboxOption,
TransitionRoot,
} from "@headlessui/vue";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import type { EquipmentTypeViewModel } from "@/viewmodels/admin/unit/equipmentType/equipmentType.models";
import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipmentType";
import ScanInput from "@/components/ScanInput.vue";
</script>
<script lang="ts">
export default defineComponent({
watch: {
query() {
this.deferingSearch = true;
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.deferingSearch = false;
this.search();
}, 600);
},
},
data() {
return {
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: null as any,
selectedType: null as null | string,
loading: false as boolean,
deferingSearch: false as boolean,
timer: undefined as any,
query: "" as string,
filtered: [] as Array<EquipmentTypeViewModel>,
};
},
computed: {
...mapState(useEquipmentTypeStore, ["equipmentTypes"]),
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useEquipmentStore, ["createEquipment"]),
...mapActions(useEquipmentTypeStore, ["searchEquipmentTypes"]),
search() {
this.filtered = [];
if (this.query == "") return;
this.loading = true;
this.searchEquipmentTypes(this.query)
.then((res) => {
this.filtered = res.data;
})
.catch((err) => {})
.finally(() => {
this.loading = false;
});
},
triggerCreate(e: any) {
if (this.selectedType == null) return;
let formData = e.target.elements;
let createEquipment: CreateEquipmentViewModel = {
code: formData.code.value || null,
name: formData.name.value,
location: formData.location.value,
equipmentTypeId: this.selectedType,
};
this.status = "loading";
this.createEquipment(createEquipment)
.then((res) => {
this.status = { status: "success" };
this.timeout = setTimeout(() => {
this.$router.push({
name: "admin-unit-equipment-overview",
params: {
equipmentId: res.data,
},
});
}, 1500);
})
.catch((err) => {
this.status = { status: "failed" };
});
},
},
});
</script>

View file

@ -1,70 +0,0 @@
<template>
<MainTemplate>
<template #topBar>
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8">Gerätschaften</h1>
</div>
</template>
<template #diffMain>
<div class="flex flex-col w-full h-full gap-2 justify-center px-7">
<Pagination
:items="equipments"
:totalCount="totalCount"
:indicateLoading="loading == 'loading'"
useSearch
useScanner
@load-data="(offset, count, search) => fetchEquipments(offset, count, search)"
@search="(search) => fetchEquipments(0, maxEntriesPerPage, search, true)"
>
<template #pageRow="{ row }: { row: EquipmentViewModel }">
<EquipmentListItem :equipment="row" />
</template>
</Pagination>
<div class="flex flex-row gap-4">
<RouterLink
v-if="can('create', 'unit', 'equipment')"
:to="{ name: 'admin-unit-equipment-create' }"
primary
button
class="w-fit!"
>
Gerätschaft erfassen
</RouterLink>
</div>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
import Pagination from "@/components/Pagination.vue";
import { useAbilityStore } from "@/stores/ability";
import type { EquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
import EquipmentListItem from "@/components/admin/unit/equipment/EquipmentListItem.vue";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
currentPage: 0,
maxEntriesPerPage: 25,
};
},
computed: {
...mapState(useEquipmentStore, ["equipments", "totalCount", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchEquipments(0, this.maxEntriesPerPage, "", true);
},
methods: {
...mapActions(useEquipmentStore, ["fetchEquipments"]),
},
});
</script>

View file

@ -1,81 +0,0 @@
<template>
<MainTemplate>
<template #headerInsert>
<RouterLink to="../" class="text-primary">zurück zur Liste</RouterLink>
</template>
<template #topBar>
<div class="flex flex-row gap-2 items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8 min-h-fit grow">
{{ activeEquipmentObj?.name }}
</h1>
<RouterLink v-if="can('update', 'unit', 'equipment')" :to="{ name: 'admin-unit-equipment-edit' }">
<PencilIcon class="w-5 h-5" />
</RouterLink>
</div>
</template>
<template #diffMain>
<div class="flex flex-col gap-2 grow px-7 overflow-hidden">
<div class="flex flex-col grow gap-2 overflow-hidden">
<div class="w-full flex flex-row max-lg:flex-wrap justify-center">
<RouterLink
v-for="tab in tabs"
:key="tab.route"
v-slot="{ isActive }"
:to="{ name: tab.route }"
class="w-1/2 md:w-1/3 lg:w-full p-0.5 first:pl-0 last:pr-0"
>
<p
:class="[
'w-full rounded-lg py-2.5 text-sm text-center font-medium leading-5 focus:ring-0 outline-hidden',
isActive ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : ' hover:bg-red-200',
]"
>
{{ tab.title }}
</p>
</RouterLink>
</div>
<RouterView />
</div>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { RouterLink, RouterView } from "vue-router";
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
import { useAbilityStore } from "@/stores/ability";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
</script>
<script lang="ts">
export default defineComponent({
props: {
equipmentId: String,
},
data() {
return {
tabs: [
{ route: "admin-unit-equipment-overview", title: "Übersicht" },
{ route: "admin-unit-equipment-maintenance", title: "Wartungen" },
{ route: "admin-unit-equipment-inspection", title: "Prüfungen" },
{ route: "admin-unit-equipment-damage_report", title: "Schadensmeldungen" },
],
};
},
computed: {
...mapState(useEquipmentStore, ["activeEquipmentObj"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchEquipmentByActiveId();
},
methods: {
...mapActions(useEquipmentStore, ["fetchEquipmentByActiveId"]),
},
});
</script>

View file

@ -1,51 +0,0 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<div v-if="activeEquipmentObj != null" class="flex flex-col gap-2 w-full">
<div>
<label for="type">Typ</label>
<input type="text" id="type" :value="activeEquipmentObj.equipmentType.type" readonly />
</div>
<div>
<label for="name">Bezeichnung</label>
<input type="text" id="name" :value="activeEquipmentObj.name" readonly />
</div>
<div>
<label for="code">Code</label>
<input type="text" id="code" :value="activeEquipmentObj.code" readonly />
</div>
<div>
<label for="location">Verortung</label>
<input type="text" id="location" :value="activeEquipmentObj.location" readonly />
</div>
</div>
<Spinner v-if="loadingActive == 'loading'" class="mx-auto" />
<p v-else-if="loadingActive == 'failed'" @click="fetchEquipmentByActiveId" class="cursor-pointer">
&#8634; laden fehlgeschlagen
</p>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
</script>
<script lang="ts">
export default defineComponent({
props: {
equipmentId: String,
},
computed: {
...mapState(useEquipmentStore, ["activeEquipmentObj", "loadingActive"]),
},
mounted() {
this.fetchEquipmentByActiveId();
},
methods: {
...mapActions(useEquipmentStore, ["fetchEquipmentByActiveId"]),
},
});
</script>

View file

@ -1,119 +0,0 @@
<template>
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
<Spinner v-if="loading == 'loading'" class="mx-auto" />
<p v-else-if="loading == 'failed'">laden fehlgeschlagen</p>
<form
v-else-if="equipment != null"
class="flex flex-col gap-4 py-2 w-full max-w-xl mx-auto"
@submit.prevent="triggerUpdate"
>
<p class="mx-auto">Ausrüstung bearbeiten</p>
<div>
<label for="name">Bezeichnung</label>
<input type="text" id="name" required v-model="equipment.name" />
</div>
<ScanInput name="code" label="Code" :required="false" v-model="equipment.code" />
<div>
<label for="location">Verortung (optional)</label>
<input type="text" id="location" v-model="equipment.location" />
</div>
<div class="flex flex-row justify-end gap-2">
<button primary-outline type="reset" class="w-fit!" :disabled="canSaveOrReset" @click="resetForm">
abbrechen
</button>
<button primary type="submit" class="w-fit!" :disabled="status == 'loading'">speichern</button>
<Spinner v-if="status == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="status?.status == 'success'" />
<FailureXMark v-else-if="status?.status == 'failed'" />
</div>
</form>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia";
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
import type {
CreateEquipmentViewModel,
EquipmentViewModel,
UpdateEquipmentViewModel,
} from "@/viewmodels/admin/unit/equipment/equipment.models";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import ScanInput from "@/components/ScanInput.vue";
import isEqual from "lodash.isequal";
import cloneDeep from "lodash.clonedeep";
</script>
<script lang="ts">
export default defineComponent({
props: {
equipmentId: String,
},
watch: {
loadingActive() {
if (this.loading == "loading") {
this.loading = this.loadingActive;
}
if (this.loadingActive == "fetched") this.equipment = cloneDeep(this.activeEquipmentObj);
},
},
data() {
return {
loading: "loading" as "loading" | "fetched" | "failed",
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
equipment: null as null | EquipmentViewModel,
timeout: null as any,
};
},
computed: {
canSaveOrReset(): boolean {
return isEqual(this.activeEquipmentObj, this.equipment);
},
...mapState(useEquipmentStore, ["activeEquipmentObj", "loadingActive"]),
},
mounted() {
this.fetchItem();
},
beforeUnmount() {
try {
clearTimeout(this.timeout);
} catch (error) {}
},
methods: {
...mapActions(useEquipmentStore, ["updateActiveEquipment", "fetchEquipmentByActiveId"]),
resetForm() {
this.equipment = cloneDeep(this.activeEquipmentObj);
},
fetchItem() {
this.fetchEquipmentByActiveId();
},
triggerUpdate(e: any) {
if (this.equipment == null) return;
let formData = e.target.elements;
let updateEquipment: UpdateEquipmentViewModel = {
id: this.equipment.id,
code: formData.code.value || null,
name: formData.name.value,
location: formData.location.value,
};
this.status = "loading";
this.updateActiveEquipment(updateEquipment)
.then((res) => {
this.fetchItem();
this.status = { status: "success" };
})
.catch((err) => {
this.status = { status: "failed" };
})
.finally(() => {
this.timeout = setTimeout(() => {
this.status = null;
}, 2000);
});
},
},
});
</script>

View file

@ -1,72 +0,0 @@
<template>
<MainTemplate>
<template #topBar>
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8">Geräte-Typen</h1>
</div>
</template>
<template #diffMain>
<div class="flex flex-col w-full h-full gap-2 justify-center px-7">
<Pagination
:items="equipmentTypes"
:totalCount="totalCount"
:indicateLoading="loading == 'loading'"
useSearch
@load-data="(offset, count, search) => fetchEquipmentTypes(offset, count, search)"
@search="(search) => fetchEquipmentTypes(0, maxEntriesPerPage, search, true)"
>
<template #pageRow="{ row }: { row: EquipmentTypeViewModel }">
<EquipmentTypeListItem :equipmentType="row" />
</template>
</Pagination>
<div class="flex flex-row gap-4">
<button v-if="can('create', 'unit', 'equipment_type')" primary class="w-fit!" @click="openCreateModal">
Geräte-Typ erstellen
</button>
</div>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
import { mapActions, mapState } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipmentType";
import { useModalStore } from "@/stores/modal";
import Pagination from "@/components/Pagination.vue";
import { useAbilityStore } from "@/stores/ability";
import type { EquipmentTypeViewModel } from "@/viewmodels/admin/unit/equipmentType/equipmentType.models";
import EquipmentTypeListItem from "@/components/admin/unit/equipmentType/EquipmentTypeListItem.vue";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
currentPage: 0,
maxEntriesPerPage: 25,
};
},
computed: {
...mapState(useEquipmentTypeStore, ["equipmentTypes", "totalCount", "loading"]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchEquipmentTypes(0, this.maxEntriesPerPage, "", true);
},
methods: {
...mapActions(useEquipmentTypeStore, ["fetchEquipmentTypes"]),
...mapActions(useModalStore, ["openModal"]),
openCreateModal() {
this.openModal(
markRaw(
defineAsyncComponent(() => import("@/components/admin/unit/equipmentType/CreateEquipmentTypeModal.vue"))
)
);
},
},
});
</script>

Some files were not shown because too many files have changed in this diff Show more