Compare commits
2 commits
v1.8.0-bet
...
main
Author | SHA1 | Date | |
---|---|---|---|
a7149b2712 | |||
e54a6b91e0 |
220 changed files with 134 additions and 16046 deletions
68
package-lock.json
generated
68
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "ff-admin",
|
||||
"version": "1.8.0-beta1",
|
||||
"version": "1.7.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ff-admin",
|
||||
"version": "1.8.0-beta1",
|
||||
"version": "1.7.5",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@fullcalendar/core": "^6.1.18",
|
||||
|
@ -43,7 +43,6 @@
|
|||
"unplugin-vue-markdown": "^29.1.0",
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.5.18",
|
||||
"vue-qrcode-reader": "^5.7.1",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -3793,18 +3792,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",
|
||||
|
@ -4925,16 +4912,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": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.3.0.tgz",
|
||||
|
@ -9466,12 +9443,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",
|
||||
|
@ -11038,19 +11009,6 @@
|
|||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-qrcode-reader": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.7.2.tgz",
|
||||
"integrity": "sha512-MRwo8IWM+1UzvfRhOQQBqEap06nl0E8QFIb+/HxS1KiH8BqL2qhlzMVvJgMUti4m5x+XH2YlGS0v1Qshpg+Hbw==",
|
||||
"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.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
|
||||
|
@ -11102,19 +11060,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",
|
||||
|
@ -11807,15 +11752,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ff-admin",
|
||||
"version": "1.8.0-beta1",
|
||||
"version": "1.7.5",
|
||||
"description": "Feuerwehr/Verein Mitgliederverwaltung UI",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
@ -58,7 +58,6 @@
|
|||
"unplugin-vue-markdown": "^29.1.0",
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.5.18",
|
||||
"vue-qrcode-reader": "^5.7.1",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -36,9 +36,12 @@ export default defineComponent({
|
|||
document.getSelection()?.toString() || this.clickedOnEl.value || this.clickedOnEl.innerText || "";
|
||||
|
||||
let selection = document.getSelection()?.toString();
|
||||
console.log(selection);
|
||||
if (selection == "") {
|
||||
console.log("jo");
|
||||
const range = document.createRange();
|
||||
range.selectNode(this.clickedOnEl);
|
||||
console.log(range);
|
||||
window.getSelection()?.removeAllRanges();
|
||||
window.getSelection()?.addRange(range);
|
||||
}
|
||||
|
|
|
@ -4,27 +4,24 @@
|
|||
class="absolute inset-0 w-full h-full flex justify-center items-center bg-black/50 select-none z-50 p-2"
|
||||
v-show="show"
|
||||
>
|
||||
<component
|
||||
:is="component_ref"
|
||||
:data="data"
|
||||
:callback="callback"
|
||||
class="p-4 bg-white rounded-lg max-h-[95%] overflow-y-auto"
|
||||
/>
|
||||
<component :is="component_ref" :data="data" class="p-4 bg-white rounded-lg max-h-[95%] overflow-y-auto" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { useContextMenuStore } from "@/stores/context-menu";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
computed: {
|
||||
...mapState(useModalStore, ["show", "component_ref", "data", "callback"]),
|
||||
...mapState(useModalStore, ["show", "component_ref", "data"]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useContextMenuStore, ["closeContextMenu"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
notification.type == 'error' ? 'border border-red-400' : '',
|
||||
notification.type == 'warning' ? 'border border-red-400' : '',
|
||||
notification.type == 'info' ? 'border border-gray-400' : '',
|
||||
notification.type == 'success' ? 'border border-green-400' : '',
|
||||
]"
|
||||
>
|
||||
<!-- @mouseover="hovering(notification.id, true)"
|
||||
|
@ -37,10 +36,6 @@
|
|||
v-if="notification.type == 'info'"
|
||||
class="flex items-center justify-center min-w-12 w-12 h-12 bg-gray-500 rounded-lg text-white p-1"
|
||||
/>
|
||||
<HandThumbUpIcon
|
||||
v-if="notification.type == 'success'"
|
||||
class="flex items-center justify-center min-w-12 w-12 h-12 bg-green-500 rounded-lg text-white p-1"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
|
@ -49,7 +44,6 @@
|
|||
notification.type == 'error' ? 'text-red-500' : '',
|
||||
notification.type == 'warning' ? 'text-red-500' : '',
|
||||
notification.type == 'info' ? 'text-gray-700' : '',
|
||||
notification.type == 'success' ? 'text-green-700' : '',
|
||||
]"
|
||||
>{{ notification.title }}</span
|
||||
>
|
||||
|
@ -77,7 +71,6 @@ import {
|
|||
ExclamationCircleIcon,
|
||||
InformationCircleIcon,
|
||||
XMarkIcon,
|
||||
HandThumbUpIcon,
|
||||
} from "@heroicons/vue/24/outline";
|
||||
|
||||
export interface Props {
|
||||
|
|
|
@ -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/scanner/ManageScanModal.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> -->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="flex w-full relative">
|
||||
<div class="flex relative">
|
||||
<input type="text" readonly :value="copyText" />
|
||||
<ClipboardIcon
|
||||
class="w-5 h-5 p-2 box-content absolute right-1 top-1/2 -translate-y-1/2 bg-white cursor-pointer"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
@after-leave="query = ''"
|
||||
>
|
||||
<ComboboxOptions
|
||||
class="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 z-20"
|
||||
class="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">
|
|
@ -18,23 +18,7 @@
|
|||
<CheckIcon v-if="appSettings['app.show_link_to_calendar']" class="h-2.5 w-2.5 stroke-4 text-white" />
|
||||
</div>
|
||||
<input v-else id="show_link_to_calendar" type="checkbox" :checked="appSettings['app.show_link_to_calendar']" />
|
||||
<label for="show_link_to_calendar"><i>Kalender</i> Link anzeigen</label>
|
||||
</div>
|
||||
<div class="w-full flex flex-row items-center gap-2">
|
||||
<div
|
||||
v-if="!enableEdit"
|
||||
class="border-2 border-gray-500 rounded-sm"
|
||||
:class="appSettings['app.show_link_to_damagereport'] ? 'bg-gray-500' : 'h-3.5 w-3.5'"
|
||||
>
|
||||
<CheckIcon v-if="appSettings['app.show_link_to_damagereport']" class="h-2.5 w-2.5 stroke-4 text-white" />
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
id="show_link_to_damagereport"
|
||||
type="checkbox"
|
||||
:checked="appSettings['app.show_link_to_damagereport']"
|
||||
/>
|
||||
<label for="show_link_to_damagereport"><i>Schaden melden</i> Link anzeigen</label>
|
||||
<label for="show_link_to_calendar">Kalender-Link anzeigen</label>
|
||||
</div>
|
||||
</BaseSetting>
|
||||
</template>
|
||||
|
@ -76,10 +60,6 @@ export default defineComponent({
|
|||
key: "app.show_link_to_calendar",
|
||||
value: formData.show_link_to_calendar.checked,
|
||||
},
|
||||
{
|
||||
key: "app.show_link_to_damagereport",
|
||||
value: formData.show_link_to_damagereport.checked,
|
||||
},
|
||||
]);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<template>
|
||||
<div @click="showInfo" class="cursor-pointer">
|
||||
<InformationCircleIcon class="w-5 h-5" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { InformationCircleIcon } from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
showInfo() {
|
||||
this.openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/admin/unit/InspectionTimeFormatExplainModal.vue")))
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,55 +0,0 @@
|
|||
<template>
|
||||
<div class="relative w-full md:max-w-md">
|
||||
<div class="flex flex-row gap-2 items-center justify-center">
|
||||
<InformationCircleIcon class="text-gray-500 h-5 w-5" />
|
||||
<p class="text-xl font-medium">Zeit Format für Erinnerung und Intervall</p>
|
||||
</div>
|
||||
<br />
|
||||
<table class="min-w-full text-sm border border-gray-200 rounded">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="px-3 py-2 font-mono text-gray-700 border-b border-gray-100"><zahl>-(d|m|y)</td>
|
||||
<td class="px-3 py-2 text-gray-600 border-b border-gray-100">
|
||||
Ein Intervall, z.B. <span class="font-mono">7-d</span> für alle 7 Tage,
|
||||
<span class="font-mono">1-m</span> für jeden Monat.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 font-mono text-gray-700 border-b border-gray-100">DD/MM</td>
|
||||
<td class="px-3 py-2 text-gray-600 border-b border-gray-100">
|
||||
Ein bestimmtes Datum, z.B. <span class="font-mono">15/06</span> für den 15. Juni.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 font-mono text-gray-700">DD/*</td>
|
||||
<td class="px-3 py-2 text-gray-600">
|
||||
Ein Tag jeden Monats, z.B. <span class="font-mono">01/*</span> für den ersten Tag jedes Monats.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Im Fall von Erinnerungen wird das Format als zeitliche Angabe vor einem Datum verwendet.</p>
|
||||
<br />
|
||||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal">schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { InformationCircleIcon } from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,52 +0,0 @@
|
|||
<template>
|
||||
<RouterLink
|
||||
:to="{ name: 'admin-unit-damage_report-overview', params: { damageReportId: damageReport.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>
|
||||
{{ damageReport.title }} -
|
||||
{{ damageReport?.related?.name ?? "Ohne Zuordnung" }}
|
||||
<small v-if="damageReport?.related">({{ damageReport.related.code }})</small>
|
||||
</p>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div v-if="damageReport.images.length != 0" class="cursor-pointer">
|
||||
<PhotoIcon class="w-5 h-5" />
|
||||
</div>
|
||||
<div v-if="damageReport.location" class="cursor-pointer">
|
||||
<MapPinIcon class="w-5 h-5" />
|
||||
</div>
|
||||
<div v-if="damageReport.reportedBy" class="cursor-pointer">
|
||||
<UserIcon class="w-5 h-5" />
|
||||
</div>
|
||||
<div v-if="damageReport.repair" class="cursor-pointer">
|
||||
<WrenchScrewdriverIcon class="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p>gemeldet: {{ new Date(damageReport.reportedAt).toLocaleString("de") }}</p>
|
||||
<p>Status: {{ damageReport.status }}</p>
|
||||
<p v-if="damageReport.description">Beschreibung: {{ damageReport.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 { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport.models";
|
||||
import { MapPinIcon, PhotoIcon, UserIcon, WrenchScrewdriverIcon } from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
damageReport: { type: Object as PropType<DamageReportViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -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>
|
|
@ -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/equipment/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>
|
|
@ -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/equipment/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>
|
|
@ -1,40 +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>
|
||||
<div class="flex flex-col p-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<p class="min-w-16">Anzahl angelegter Geräte:</p>
|
||||
<p class="grow overflow-hidden">{{ equipmentType.equipmentCount }}</p>
|
||||
</div>
|
||||
<div v-if="equipmentType.description" class="flex flex-row gap-2">
|
||||
<p class="min-w-16">Beschreibung:</p>
|
||||
<p class="grow overflow-hidden">{{ equipmentType.description }}</p>
|
||||
</div>
|
||||
</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/equipment/equipmentType.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
equipmentType: { type: Object as PropType<EquipmentTypeViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,85 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full md:max-w-md">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-xl font-medium">angefangene Prüfung löschen</p>
|
||||
</div>
|
||||
<br />
|
||||
<p class="text-center">
|
||||
{{ activeInspectionObj?.inspectionPlan.title }} zu {{ activeInspectionObj?.related.name }}
|
||||
<small v-if="activeInspectionObj?.related.code">({{ activeInspectionObj?.related.code }})</small> begonnen am
|
||||
{{ new Date(activeInspectionObj?.created ?? "").toLocaleDateString("de-de") }} 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 { useInspectionStore } from "@/stores/admin/unit/inspection/inspection";
|
||||
</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(useInspectionStore, ["activeInspectionObj"]),
|
||||
},
|
||||
beforeUnmount() {
|
||||
try {
|
||||
clearTimeout(this.timeout);
|
||||
} catch (error) {}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useInspectionStore, ["deleteInspection"]),
|
||||
triggerDelete() {
|
||||
if (!this.activeInspectionObj) return;
|
||||
|
||||
this.status = "loading";
|
||||
this.deleteInspection(this.activeInspectionObj.id)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
this.timeout = setTimeout(() => {
|
||||
this.$router.push({ name: "admin-unit-inspection" });
|
||||
this.closeModal();
|
||||
}, 1500);
|
||||
})
|
||||
.catch(() => {
|
||||
this.status = { status: "failed" };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,91 +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>{{ inspectionPoint.title }}</p>
|
||||
<DocumentCheckIcon class="w-5 h-5 cursor-pointer" @click="openDetailView" />
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p v-if="inspectionPoint.description" class="pb-2">Beschreibung: {{ inspectionPoint.description }}</p>
|
||||
<hr v-if="inspectionPoint.description" />
|
||||
<label :for="inspectionPoint.id">{{ inspectionPoint.others == "pdf" ? "PDF" : "Bild" }}-Datei</label>
|
||||
<button
|
||||
:primary="value != ''"
|
||||
:primary-outline="value == ''"
|
||||
type="button"
|
||||
class="flex flex-row gap-2"
|
||||
@click="($refs.fileInput as HTMLInputElement).click()"
|
||||
:disabled="!editable"
|
||||
>
|
||||
<span v-if="value == ''">Datei wählen</span><span v-else>Datei gewählt</span>
|
||||
<CheckIcon v-if="value != ''" class="h-5 w-5" />
|
||||
</button>
|
||||
<input
|
||||
ref="fileInput"
|
||||
:id="inspectionPoint.id"
|
||||
:name="inspectionPoint.id"
|
||||
type="file"
|
||||
:accept="inspectionPoint.others == 'pdf' ? 'application/pdf' : 'image/*'"
|
||||
hidden
|
||||
@change="selectFile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, markRaw, type PropType } from "vue";
|
||||
import type { InspectionPointViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
import { CheckIcon, DocumentCheckIcon } from "@heroicons/vue/24/outline";
|
||||
import { mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
inspectionPoint: {
|
||||
type: Object as PropType<InspectionPointViewModel>,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
"update:model-value": (p: string) => {
|
||||
return true;
|
||||
},
|
||||
"update:upload": (p: File | null) => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string | number) {
|
||||
this.$emit("update:model-value", val.toString());
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
selectFile(e: Event) {
|
||||
this.$emit("update:upload", (e.target as HTMLInputElement).files?.[0] ?? null);
|
||||
this.value = "set";
|
||||
},
|
||||
openDetailView() {
|
||||
this.openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/admin/unit/inspection/UploadedFileViewModal.vue"))),
|
||||
this.inspectionPoint
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,83 +0,0 @@
|
|||
<template>
|
||||
<div class="relative w-full md:max-w-md">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-xl font-medium">Prüfung abschließen</p>
|
||||
</div>
|
||||
<br />
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="flex flex-row text-sm">
|
||||
<InformationCircleIcon class="text-gray-500 h-5 w-5" /> Nach abschluss der Prüfung können keine Änderung mehr an
|
||||
dieser vorgenommen werden. <br />
|
||||
Es wird ein PDF ausgedruckt und ist dann zu dieser Prüfung verfügbar.
|
||||
</p>
|
||||
<br />
|
||||
<div class="flex flex-row gap-2">
|
||||
<button :disabled="status == 'loading' || status?.status == 'success'" primary @click="finishInspection">
|
||||
Prüfung abschließen
|
||||
</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>
|
||||
<br />
|
||||
|
||||
<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 { InformationCircleIcon } from "@heroicons/vue/24/outline";
|
||||
import { useInspectionStore } from "@/stores/admin/unit/inspection/inspection";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
||||
timeout: null as any,
|
||||
};
|
||||
},
|
||||
beforeUnmount() {
|
||||
try {
|
||||
clearTimeout(this.timeout);
|
||||
} catch (error) {}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useInspectionStore, ["finishActiveInspection"]),
|
||||
|
||||
finishInspection(e: any) {
|
||||
this.status = "loading";
|
||||
this.finishActiveInspection()
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
this.timeout = setTimeout(() => {
|
||||
this.closeModal();
|
||||
}, 2100);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.status = { status: "failed" };
|
||||
})
|
||||
.finally(() => {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.status = null;
|
||||
}, 2000);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,62 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full h-full flex flex-col gap-2">
|
||||
<Spinner v-if="status == 'loading'" />
|
||||
<div class="grow">
|
||||
<iframe ref="viewer" class="w-full h-full" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 justify-end">
|
||||
<a ref="download" button primary class="w-fit!">download</a>
|
||||
<button primary-outline class="w-fit!" @click="closeModal">schließen</button>
|
||||
</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 { useInspectionStore } from "@/stores/admin/unit/inspection/inspection";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useModalStore, ["data"]),
|
||||
...mapState(useInspectionStore, ["activeInspectionObj"]),
|
||||
},
|
||||
mounted() {
|
||||
this.fetchItem();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useInspectionStore, ["fetchInspectionPrintoutById"]),
|
||||
fetchItem() {
|
||||
this.status = "loading";
|
||||
this.fetchInspectionPrintoutById()
|
||||
.then((response) => {
|
||||
this.status = { status: "success" };
|
||||
const blob = new Blob([response.data], { type: "application/pdf" });
|
||||
(this.$refs.viewer as HTMLIFrameElement).src = window.URL.createObjectURL(blob);
|
||||
|
||||
const fileURL = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const fileLink = this.$refs.download as HTMLAnchorElement;
|
||||
fileLink.href = fileURL;
|
||||
fileLink.setAttribute(
|
||||
"download",
|
||||
`Prüf-Ausdruck_${[this.activeInspectionObj?.related.code ?? "", this.activeInspectionObj?.related.name].join("_")}_${this.activeInspectionObj?.inspectionPlan.title}_${new Date(this.activeInspectionObj?.created ?? "").toLocaleDateString("de-de")}.pdf`
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
this.status = { status: "failed" };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,80 +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>{{ inspectionPoint.title }}</p>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p v-if="inspectionPoint.description" class="pb-2">Beschreibung: {{ inspectionPoint.description }}</p>
|
||||
<hr v-if="inspectionPoint.description" />
|
||||
<label :for="inspectionPoint.id">
|
||||
Zahl <small>{{ restrictedRange }}</small>
|
||||
</label>
|
||||
<input
|
||||
:id="inspectionPoint.id"
|
||||
:name="inspectionPoint.id"
|
||||
type="number"
|
||||
v-model="value"
|
||||
:min="inspectionPoint.min"
|
||||
:max="inspectionPoint.max"
|
||||
:class="{ 'ring-red-500! ring-1!': isInRange && editable }"
|
||||
:disabled="!editable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { InspectionPointViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
inspectionPoint: {
|
||||
type: Object as PropType<InspectionPointViewModel>,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string | number) {
|
||||
this.$emit("update:model-value", val.toString());
|
||||
},
|
||||
},
|
||||
restrictedRange() {
|
||||
let range = "";
|
||||
if (this.inspectionPoint.min != null) range += `min. ${this.inspectionPoint.min}`;
|
||||
if (this.inspectionPoint.max != null) range += ` bis max. ${this.inspectionPoint.max}`;
|
||||
return range;
|
||||
},
|
||||
isInRange() {
|
||||
if (this.inspectionPoint.min != null && this.inspectionPoint.max != null)
|
||||
return Number(this.value) < this.inspectionPoint.min || this.inspectionPoint.max < Number(this.value);
|
||||
|
||||
if (this.inspectionPoint.min != null) return Number(this.value) < this.inspectionPoint.min;
|
||||
|
||||
if (this.inspectionPoint.max != null) return this.inspectionPoint.max < Number(this.value);
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!this.value) {
|
||||
this.value = String(this.inspectionPoint.min ?? 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,71 +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>{{ inspectionPoint.title }}</p>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p v-if="inspectionPoint.description" class="pb-2">Beschreibung: {{ inspectionPoint.description }}</p>
|
||||
<hr v-if="inspectionPoint.description" />
|
||||
<div class="flex flex-row gap-2">
|
||||
<button
|
||||
v-for="option in options"
|
||||
:key="option.key"
|
||||
:primary="value == option.key"
|
||||
:primary-outline="value != option.key"
|
||||
:disabled="!editable"
|
||||
:value="option.key"
|
||||
@click="value = option.key"
|
||||
>
|
||||
{{ option.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { RadioGroup, RadioGroupOption } from "@headlessui/vue";
|
||||
import type { InspectionPointViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
inspectionPoint: {
|
||||
type: Object as PropType<InspectionPointViewModel>,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: String as PropType<"true" | "false" | "">,
|
||||
default: "",
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
data() {
|
||||
return {
|
||||
options: [
|
||||
{ key: "true", title: "OK" },
|
||||
{ key: "false", title: "nicht OK" },
|
||||
] as Array<{ key: "true" | "false"; title: string }>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.value == "") this.value = "false";
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,55 +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>{{ inspectionPoint.title }}</p>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p v-if="inspectionPoint.description" class="pb-2">Beschreibung: {{ inspectionPoint.description }}</p>
|
||||
<hr v-if="inspectionPoint.description" />
|
||||
<label :for="inspectionPoint.id">Freitext</label>
|
||||
<textarea
|
||||
:id="inspectionPoint.id"
|
||||
:name="inspectionPoint.id"
|
||||
class="h-18"
|
||||
:class="{ 'ring-red-500! ring-1!': value == '' && editable }"
|
||||
v-model="value"
|
||||
:disabled="!editable"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { InspectionPointViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
inspectionPoint: {
|
||||
type: Object as PropType<InspectionPointViewModel>,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string | number) {
|
||||
this.$emit("update:model-value", val.toString());
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,63 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full h-full flex flex-col gap-2">
|
||||
<Spinner v-if="status == 'loading'" />
|
||||
<div class="grow">
|
||||
<iframe v-if="data.others == 'pdf'" ref="viewer" class="w-full h-full" />
|
||||
<img v-else ref="viewer" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 justify-end">
|
||||
<a ref="download" button primary class="w-fit!">download</a>
|
||||
<button primary-outline class="w-fit!" @click="closeModal">schließen</button>
|
||||
</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 { useInspectionStore } from "@/stores/admin/unit/inspection/inspection";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useModalStore, ["data"]),
|
||||
...mapState(useInspectionStore, ["activeInspectionObj"]),
|
||||
},
|
||||
mounted() {
|
||||
this.fetchItem();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useInspectionStore, ["fetchUploadedFileByPointId"]),
|
||||
fetchItem() {
|
||||
this.status = "loading";
|
||||
this.fetchUploadedFileByPointId(this.data.id)
|
||||
.then((response) => {
|
||||
this.status = { status: "success" };
|
||||
const blob = new Blob([response.data], { type: this.data.others == "pdf" ? "application/pdf" : "image/png" });
|
||||
(this.$refs.viewer as HTMLIFrameElement).src = window.URL.createObjectURL(blob);
|
||||
|
||||
const fileURL = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const fileLink = this.$refs.download as HTMLAnchorElement;
|
||||
fileLink.href = fileURL;
|
||||
fileLink.setAttribute(
|
||||
"download",
|
||||
this.data.others == "pdf" ? `${this.data.title}.pdf` : `${this.data.title}.png`
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
this.status = { status: "failed" };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,37 +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 }} - {{ inspectionPlan.related.type }}</p>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p v-if="inspectionPlan.inspectionInterval">Prüfinterval: {{ inspectionPlan.inspectionInterval }}</p>
|
||||
<p v-if="inspectionPlan.remindTime">Erinnerung: {{ inspectionPlan.remindTime }}</p>
|
||||
<div v-if="inspectionPlan.inspectionPoints.length == 0" class="flex flex-row gap-2 items-center">
|
||||
<ExclamationTriangleIcon class="h-5 w-5 text-error" />
|
||||
<p>Prüfplan noch nicht fertig gestellt. Es fehlen Prüfpunkte!</p>
|
||||
</div>
|
||||
</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/inspection/inspectionPlan.models";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
inspectionPlan: { type: Object as PropType<InspectionPlanViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,120 +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 gap-2">
|
||||
<ClipboardDocumentCheckIcon v-if="modelValue.type == InspectionPointEnum.oknok" class="w-6 h-6" />
|
||||
<CalculatorIcon v-else-if="modelValue.type == InspectionPointEnum.number" class="w-6 h-6" />
|
||||
<BoldIcon v-else-if="modelValue.type == InspectionPointEnum.text" class="w-6 h-6" />
|
||||
<DocumentIcon v-else-if="modelValue.type == InspectionPointEnum.file" class="w-6 h-6" />
|
||||
|
||||
<input type="text" placeholder="Titel" class="grow !w-fit" v-model="title" required />
|
||||
|
||||
<div class="flex flex-col">
|
||||
<ChevronUpIcon v-if="index != 0" class="text-white w-4 h-4 stroke-2 cursor-pointer" @click="$emit('up')" />
|
||||
<ChevronDownIcon
|
||||
v-if="index != totalCount - 1"
|
||||
class="text-white w-4 h-4 stroke-2 cursor-pointer"
|
||||
@click="$emit('down')"
|
||||
/>
|
||||
</div>
|
||||
<TrashIcon class="h-5 w-5 cursor-pointer" @click="$emit('remove')" />
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<textarea name="description" placeholder="Beschreibung" v-model="description"></textarea>
|
||||
<div v-if="modelValue.type == InspectionPointEnum.number">
|
||||
<label for="min">Mindestens</label>
|
||||
<input type="number" v-model="min" min="0" />
|
||||
</div>
|
||||
<div v-if="modelValue.type == InspectionPointEnum.number">
|
||||
<label for="max">Maximal</label>
|
||||
<input type="number" v-model="max" :min="Number(min ?? 0) + 1" />
|
||||
</div>
|
||||
<div v-if="modelValue.type == InspectionPointEnum.file">
|
||||
<p>Dateiart</p>
|
||||
<div class="flex flex-row gap-2">
|
||||
<button :primary="others == 'img'" :primary-outline="others != 'img'" type="button" @click="others = 'img'">
|
||||
Bild
|
||||
</button>
|
||||
<button :primary="others == 'pdf'" :primary-outline="others != 'pdf'" type="button" @click="others = 'pdf'">
|
||||
PDF
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { InspectionPointViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
import { InspectionPointEnum } from "@/enums/inspectionEnum";
|
||||
import {
|
||||
BoldIcon,
|
||||
CalculatorIcon,
|
||||
ClipboardDocumentCheckIcon,
|
||||
TrashIcon,
|
||||
ChevronUpIcon,
|
||||
ChevronDownIcon,
|
||||
DocumentIcon,
|
||||
} from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
index: { type: Number, default: 0 },
|
||||
totalCount: { type: Number, default: 0 },
|
||||
modelValue: {
|
||||
type: Object as PropType<InspectionPointViewModel>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["up", "down", "remove", "update:model-value"],
|
||||
computed: {
|
||||
title: {
|
||||
get() {
|
||||
return this.modelValue.title;
|
||||
},
|
||||
set(val: string) {
|
||||
this.$emit("update:model-value", { ...this.modelValue, title: val });
|
||||
},
|
||||
},
|
||||
description: {
|
||||
get() {
|
||||
return this.modelValue.description;
|
||||
},
|
||||
set(val: string) {
|
||||
this.$emit("update:model-value", { ...this.modelValue, description: val });
|
||||
},
|
||||
},
|
||||
min: {
|
||||
get() {
|
||||
return this.modelValue.min;
|
||||
},
|
||||
set(val: string) {
|
||||
this.$emit("update:model-value", { ...this.modelValue, min: String(val) == "" ? null : String(val) });
|
||||
},
|
||||
},
|
||||
max: {
|
||||
get() {
|
||||
return this.modelValue.max;
|
||||
},
|
||||
set(val: string) {
|
||||
this.$emit("update:model-value", { ...this.modelValue, max: String(val) == "" ? null : String(val) });
|
||||
},
|
||||
},
|
||||
others: {
|
||||
get() {
|
||||
return this.modelValue.others;
|
||||
},
|
||||
set(val: string) {
|
||||
this.$emit("update:model-value", { ...this.modelValue, others: val == "" ? null : val });
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.modelValue.type == InspectionPointEnum.file && !this.others) {
|
||||
this.others = "img";
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<RouterLink
|
||||
:to="{ name: 'admin-unit-inspection_plan-overview', params: { inspectionPlanId: inspectionPlan.id } }"
|
||||
:disabled="!can('create', 'unit', 'inspection_plan')"
|
||||
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>Interval: {{ inspectionPlan.inspectionInterval }}</p>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { InspectionPlanViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
import { mapState } from "pinia";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
inspectionPlan: {
|
||||
type: Object as PropType<InspectionPlanViewModel>,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -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.related.name }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p v-if="damageReport.related">Code: {{ damageReport.related.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.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
damageReport: { type: Object as PropType<DamageReportViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,39 +0,0 @@
|
|||
<template>
|
||||
<RouterLink
|
||||
:to="{ name: 'admin-unit-repair-overview', params: { repairId: repair.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>
|
||||
{{ repair.title }} -
|
||||
{{ repair?.related?.name ?? "Ohne Zuordnung" }}
|
||||
<small v-if="repair?.related">({{ repair.related.code }})</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<p>begonnen: {{ new Date(repair.createdAt).toLocaleString("de") }}</p>
|
||||
<p>Status: {{ repair.status }}</p>
|
||||
<p v-if="repair.responsible">Verantwortlich: {{ repair.responsible }}</p>
|
||||
<p v-if="repair.description">Beschreibung: {{ repair.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 { RepairViewModel } from "@/viewmodels/admin/unit/repair.models";
|
||||
import { MapPinIcon, PhotoIcon, UserIcon, WrenchScrewdriverIcon } from "@heroicons/vue/24/outline";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
repair: { type: Object as PropType<RepairViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,156 +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: "",
|
||||
commissioned: new Date(),
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createEquipment(createEquipment)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
this.timeout = setTimeout(() => {
|
||||
this.closeModal();
|
||||
}, 1500);
|
||||
})
|
||||
.catch(() => {
|
||||
this.status = { status: "failed" };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -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/respiratory/respiratoryGear.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
respiratoryGear: { type: Object as PropType<RespiratoryGearViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,156 +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: "",
|
||||
commissioned: new Date(),
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createEquipment(createEquipment)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
this.timeout = setTimeout(() => {
|
||||
this.closeModal();
|
||||
}, 1500);
|
||||
})
|
||||
.catch(() => {
|
||||
this.status = { status: "failed" };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -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/respiratory/respiratoryMission.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
respiratoryMission: { type: Object as PropType<RespiratoryMissionViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,156 +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: "",
|
||||
commissioned: new Date(),
|
||||
};
|
||||
this.status = "loading";
|
||||
this.createEquipment(createEquipment)
|
||||
.then(() => {
|
||||
this.status = { status: "success" };
|
||||
this.timeout = setTimeout(() => {
|
||||
this.closeModal();
|
||||
}, 1500);
|
||||
})
|
||||
.catch(() => {
|
||||
this.status = { status: "failed" };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -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.member.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/respiratory/respiratoryWearer.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
respiratoryWearer: { type: Object as PropType<RespiratoryWearerViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,31 +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>
|
||||
<div class="p-2">
|
||||
<p v-if="vehicle.code">Code: {{ vehicle.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 { 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>
|
|
@ -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/vehicle/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>
|
|
@ -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>
|
|
@ -1,40 +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>
|
||||
<div class="flex flex-col p-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<p class="min-w-16">Anzahl angelegter Fahrzeuge:</p>
|
||||
<p class="grow overflow-hidden">{{ vehicleType.vehicleCount }}</p>
|
||||
</div>
|
||||
<div v-if="vehicleType.description" class="flex flex-row gap-2">
|
||||
<p class="min-w-16">Beschreibung:</p>
|
||||
<p class="grow overflow-hidden">{{ vehicleType.description }}</p>
|
||||
</div>
|
||||
</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/vehicle/vehicleType.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
vehicleType: { type: Object as PropType<VehicleTypeViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -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>
|
|
@ -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/wearable/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>
|
|
@ -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/wearable/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>
|
|
@ -1,51 +0,0 @@
|
|||
<template>
|
||||
<RouterLink
|
||||
:to="{ name: 'admin-unit-wearable_type-overview', params: { wearableTypeId: wearableType.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>
|
||||
{{ wearableType.type }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col p-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<p class="min-w-16">Anzahl angelegter Kleidung:</p>
|
||||
<p class="grow overflow-hidden">{{ wearableType.wearableCount }}</p>
|
||||
</div>
|
||||
<div v-if="wearableType.description" class="flex flex-row gap-2">
|
||||
<p class="min-w-16">Beschreibung:</p>
|
||||
<p class="grow overflow-hidden">{{ wearableType.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, markRaw, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { PencilIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
import type { WearableTypeViewModel } from "@/viewmodels/admin/unit/wearable/wearableType.models";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
wearableType: { type: Object as PropType<WearableTypeViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
openDeleteModal() {
|
||||
this.openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/admin/unit/wearableType/DeleteWearableTypeModal.vue"))),
|
||||
this.wearableType.id
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,132 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-primary cursor-pointer" @click="$emit('stepBack')">zurück</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="mx-auto">Eingaben prüfen</p>
|
||||
<div v-if="check.gear" class="rounded-md p-2 box-border border-2 font-medium border-primary text-black">
|
||||
<p>
|
||||
{{ check.gear.name }} <small>({{ check.gear.code }})</small>
|
||||
</p>
|
||||
<p>Typ: {{ check.gear.type }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="title">Kurzbeschreibung (Titel)</label>
|
||||
<input id="title" type="text" readonly :value="check.title" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="description">Beschreibung des Schadens</label>
|
||||
<textarea id="description" readonly :value="check.description"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="location">Fundort (optional)</label>
|
||||
<textarea id="location" readonly :value="check.location"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="note">Anmerkung für Bearbeiter (optional)</label>
|
||||
<textarea id="note" readonly :value="check.note"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="reportedBy">Gemeldet von (optional)</label>
|
||||
<input id="reportedBy" type="text" readonly :value="check.reportedBy" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="image">Bild (optional)</label>
|
||||
<img ref="vis" class="mt-2 max-h-36 mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2">
|
||||
<button primary :disabled="status == 'loading' || status == 'success'" @click="submit">
|
||||
Schadensmeldung absenden
|
||||
</button>
|
||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
||||
<SuccessCheckmark v-else-if="status == 'success'" />
|
||||
<FailureXMark v-else-if="status == 'failed'" />
|
||||
</div>
|
||||
<p v-if="message" class="text-center">{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
import type { MinifiedEquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
|
||||
import type { MinifiedVehicleViewModel } from "@/viewmodels/admin/unit/vehicle/vehicle.models";
|
||||
import type { MinifiedWearableViewModel } from "@/viewmodels/admin/unit/wearable/wearable.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
check: {
|
||||
type: Object as PropType<{
|
||||
gear: undefined | MinifiedEquipmentViewModel | MinifiedVehicleViewModel | MinifiedWearableViewModel;
|
||||
title: string;
|
||||
description: string;
|
||||
location: string;
|
||||
note: string;
|
||||
reportedBy: string;
|
||||
image: undefined | File;
|
||||
}>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
nextStep: (s: string) => true,
|
||||
stepBack: () => true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
status: undefined as undefined | "loading" | "success" | "failed",
|
||||
message: "" as string,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.check.image) {
|
||||
this.setImage(this.check.image);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setImage(img: File) {
|
||||
let image = this.$refs.vis as HTMLImageElement;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
image.src = e.target?.result as string;
|
||||
};
|
||||
reader.readAsDataURL(img);
|
||||
},
|
||||
submit() {
|
||||
this.status = "loading";
|
||||
this.message = "";
|
||||
const formData = new FormData();
|
||||
if (this.check.gear) formData.append("related", JSON.stringify(this.check.gear));
|
||||
formData.append("title", this.check.title);
|
||||
formData.append("description", this.check.description);
|
||||
formData.append("location", this.check.location);
|
||||
formData.append("note", this.check.note);
|
||||
formData.append("reportedBy", this.check.reportedBy);
|
||||
if (this.check.image) formData.append("images", this.check.image);
|
||||
|
||||
this.$http
|
||||
.post("/public/reportdamage", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
this.status = "success";
|
||||
setTimeout(() => {
|
||||
this.$emit("nextStep", "result");
|
||||
}, 2000);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.status = "failed";
|
||||
this.message = err.message;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,103 +0,0 @@
|
|||
<template>
|
||||
<form class="flex flex-col gap-2" @submit.prevent="setup">
|
||||
<p class="text-primary cursor-pointer" @click="$emit('stepBack')">zurück</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div>
|
||||
<label for="title">Kurzbeschreibung (Titel)</label>
|
||||
<input id="title" type="text" required :value="data.title" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="description">Beschreibung des Schadens</label>
|
||||
<textarea id="description" required :value="data.description"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="location">Fundort (optional)</label>
|
||||
<textarea id="location" :value="data.location"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="note">Anmerkung für Bearbeiter (optional)</label>
|
||||
<textarea id="note" :value="data.note"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="reportedBy">Gemeldet von (optional)</label>
|
||||
<input id="reportedBy" type="text" placeholder="dein Name" :value="data.reportedBy" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="image">Bild (optional)</label>
|
||||
<input id="image" type="file" accept="image/*" @change="(e: any) => setImage(e.target.files[0])" />
|
||||
<img ref="vis" class="mt-2 max-h-36 mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" primary>weiter</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import ScanInput from "@/components/scanner/ScanInput.vue";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
import type { MinifiedEquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
|
||||
import type { MinifiedVehicleViewModel } from "@/viewmodels/admin/unit/vehicle/vehicle.models";
|
||||
import type { MinifiedWearableViewModel } from "@/viewmodels/admin/unit/wearable/wearable.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<{
|
||||
gear: undefined | MinifiedEquipmentViewModel | MinifiedVehicleViewModel | MinifiedWearableViewModel;
|
||||
title: string;
|
||||
description: string;
|
||||
location: string;
|
||||
note: string;
|
||||
reportedBy: string;
|
||||
image: undefined | File;
|
||||
}>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
nextStep: (s: string) => true,
|
||||
stepBack: () => true,
|
||||
data: (d: {
|
||||
title: string;
|
||||
description: string;
|
||||
location: string;
|
||||
note: string;
|
||||
reportedBy: string;
|
||||
image?: File;
|
||||
}) => true,
|
||||
},
|
||||
mounted() {
|
||||
if (this.data.image) {
|
||||
this.setImage(this.data.image);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setup(e: any) {
|
||||
let formData = e.target.elements;
|
||||
this.$emit("data", {
|
||||
title: formData.title.value,
|
||||
description: formData.description.value,
|
||||
location: formData.location.value,
|
||||
note: formData.note.value,
|
||||
reportedBy: formData.reportedBy.value,
|
||||
image: formData.image.files[0],
|
||||
});
|
||||
this.$emit("nextStep", "check");
|
||||
},
|
||||
setImage(img: File) {
|
||||
let image = this.$refs.vis as HTMLImageElement;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
image.src = e.target?.result as string;
|
||||
};
|
||||
reader.readAsDataURL(img);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,28 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="font-medium text-center">Schadensmeldung eingereicht</h1>
|
||||
<br />
|
||||
<p>
|
||||
Deine Schadensmeldung wurde erfolgreich übermittelt.
|
||||
<br />
|
||||
Die Verwantwortlichen werden benachtichtigt.
|
||||
<br />
|
||||
Du kannst diese Seite jetzt schließen.
|
||||
</p>
|
||||
<br />
|
||||
|
||||
<p class="text-primary cursor-pointer self-end" @click="$emit('start')">neue Meldung einreichen</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
emits: {
|
||||
start: () => true,
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,116 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-primary cursor-pointer" @click="$emit('stepBack')">zurück</p>
|
||||
|
||||
<Scanner v-if="!scanned" useInput @code="checkCode" />
|
||||
|
||||
<div v-if="scanned" class="contents">
|
||||
<div class="flex flex-col gap-2 h-80 overflow-y-scroll pr-2">
|
||||
<div v-if="status != undefined" class="flex flex-row gap-2 h-7 w-7 mx-auto">
|
||||
<p>Suche</p>
|
||||
<Spinner v-if="status == 'loading'" class="my-auto" />
|
||||
<SuccessCheckmark v-else-if="status == 'success'" />
|
||||
<FailureXMark v-else-if="status == 'failed'" />
|
||||
</div>
|
||||
<p v-else-if="available.length == 0" class="mx-auto">nichts gefunden</p>
|
||||
<div
|
||||
v-for="item in available"
|
||||
:key="item.id"
|
||||
class="rounded-md p-2 cursor-pointer box-border border-2 font-medium"
|
||||
:class="
|
||||
item.type == selected?.type && item.id == selected.id
|
||||
? 'bg-primary text-white border-transparent hover:bg-accent'
|
||||
: 'border-primary text-black hover:bg-primary hover:text-white'
|
||||
"
|
||||
@click="selected = item"
|
||||
>
|
||||
<p>
|
||||
{{ item.name }} <small>({{ item.code }})</small>
|
||||
</p>
|
||||
<p>Typ: {{ item.type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="selected != undefined"
|
||||
primary
|
||||
:disabled="status == 'loading'"
|
||||
@click="
|
||||
$emit('data', selected);
|
||||
$emit('nextStep', 'input');
|
||||
"
|
||||
>
|
||||
mit ausgewähltem fortfahren
|
||||
</button>
|
||||
<button
|
||||
primary-outline
|
||||
:disabled="status == 'loading'"
|
||||
@click="
|
||||
scanned = false;
|
||||
code = '';
|
||||
"
|
||||
>
|
||||
neu Scannen
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="message" class="text-center">{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import ScanInput from "@/components/scanner/ScanInput.vue";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
import Scanner from "@/components/scanner/Scanner.vue";
|
||||
import type { MinifiedEquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
|
||||
import type { MinifiedVehicleViewModel } from "@/viewmodels/admin/unit/vehicle/vehicle.models";
|
||||
import type { MinifiedWearableViewModel } from "@/viewmodels/admin/unit/wearable/wearable.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
emits: {
|
||||
nextStep: (s: string) => true,
|
||||
stepBack: () => true,
|
||||
data: (gear: MinifiedEquipmentViewModel | MinifiedVehicleViewModel | MinifiedWearableViewModel) => true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
code: "" as string,
|
||||
status: undefined as undefined | "loading" | "success" | "failed",
|
||||
message: "" as string,
|
||||
available: [] as Array<MinifiedEquipmentViewModel | MinifiedVehicleViewModel | MinifiedWearableViewModel>,
|
||||
selected: undefined as
|
||||
| undefined
|
||||
| MinifiedEquipmentViewModel
|
||||
| MinifiedVehicleViewModel
|
||||
| MinifiedWearableViewModel,
|
||||
scanned: false as boolean,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
checkCode(c: string) {
|
||||
this.available = [];
|
||||
this.selected = undefined;
|
||||
this.scanned = true;
|
||||
this.status = "loading";
|
||||
this.code = c;
|
||||
this.$http
|
||||
.get(`/public/reportdamage?code=${this.code}`)
|
||||
.then((res) => {
|
||||
this.available = res.data;
|
||||
this.status = "success";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.status = "failed";
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.status = undefined;
|
||||
}, 1500);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,19 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<br />
|
||||
<button primary @click="$emit('nextStep', 'select')">Barcode verwenden</button>
|
||||
<button primary-outline @click="$emit('nextStep', 'input')">ohne Barcode fortfahren</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
emits: {
|
||||
nextStep: (s: string) => true,
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,64 +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" />
|
||||
<div class="w-full flex flex-row justify-center items-stretch" :class="{ 'max-md:hidden': activeTab == 'self' }">
|
||||
<div v-for="tab in tabs" :key="tab.type" class="w-1/2 p-0.5 first:pl-0 last:pr-0" @click="activeTab = tab.type">
|
||||
<p
|
||||
:class="[
|
||||
'flex w-full h-full items-center justify-center rounded-lg py-2.5 text-sm text-center font-medium leading-5 focus:ring-0 outline-hidden',
|
||||
activeTab == tab.type
|
||||
? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none'
|
||||
: ' hover:bg-red-200',
|
||||
]"
|
||||
>
|
||||
{{ tab.title }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Scanner v-if="activeTab == 'self'" @code="(c) => commit(c)" />
|
||||
<Phone v-else @code="(c) => commit(c)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
import { XMarkIcon } from "@heroicons/vue/24/outline";
|
||||
import Scanner from "./Scanner.vue";
|
||||
import Phone from "./Phone.vue";
|
||||
import { useScannerStore } from "@/stores/admin/scanner";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
callback: {
|
||||
type: Function,
|
||||
default: (result: string) => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: "self" as "self" | "phone",
|
||||
tabs: [
|
||||
{ type: "self", title: "Scanner" },
|
||||
{ type: "phone", title: "Smartphone" },
|
||||
] as Array<{ type: "self" | "phone"; title: string }>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useScannerStore, ["inUse"]),
|
||||
},
|
||||
mounted() {
|
||||
if (this.inUse) this.activeTab = "phone";
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
commit(code: string) {
|
||||
this.callback(code);
|
||||
this.closeModal();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,91 +0,0 @@
|
|||
<template>
|
||||
<div v-if="!inUse" class="relative flex flex-col w-full h-[455px] overflow-hidden">
|
||||
<br />
|
||||
<button primary @click="startSession">Smartphone als Scanner verwenden</button>
|
||||
</div>
|
||||
<div v-else class="relative flex flex-col w-full h-[455px] overflow-hidden">
|
||||
<br />
|
||||
|
||||
<div class="grow flex flex-col gap-2 overflow-y-scroll pr-2">
|
||||
<div
|
||||
v-for="i in results"
|
||||
:key="i"
|
||||
class="h-10 w-full flex justify-center items-center gap-2 py-2 px-4 text-sm font-medium rounded-md text-white bg-primary"
|
||||
>
|
||||
<p class="grow">{{ i }}</p>
|
||||
<TrashIcon class="w-5 h-5 cursor-pointer" @click="() => removeElementFromResults(i)" />
|
||||
<ArrowRightIcon class="w-5 h-5 cursor-pointer" @click="commit(i)" />
|
||||
</div>
|
||||
<p v-if="results.length == 0">Bisher keine Scan-Ergebnisse vorhanden.</p>
|
||||
</div>
|
||||
<br />
|
||||
<RouterLink :to="{ name: 'public-scanner-select' }" target="_blank" class="text-primary"
|
||||
>Link zur Scanoberfläche:</RouterLink
|
||||
>
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<TextCopy :copyText="roomId" />
|
||||
<QrCodeIcon class="h-7 w-7 cursor-pointer" @click="showQRCode = true" />
|
||||
</div>
|
||||
|
||||
<div v-show="showQRCode" class="absolute w-full h-full flex items-center justify-center bg-white/95 p-2">
|
||||
<img ref="qr" />
|
||||
<QrCodeIcon class="absolute bottom-1 right-0 h-7 w-7 cursor-pointer" @click="showQRCode = false" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useScannerStore } from "@/stores/admin/scanner";
|
||||
import { ArrowRightIcon, QrCodeIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import TextCopy from "../TextCopy.vue";
|
||||
import QRCode from "qrcode";
|
||||
import { RouterLink } from "vue-router";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
emits: ["code"],
|
||||
data() {
|
||||
return {
|
||||
showQRCode: false as boolean,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
linkToScan() {
|
||||
this.renderQRCode();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(useScannerStore, ["inUse", "roomId", "results"]),
|
||||
linkToScan() {
|
||||
return `${window.location.origin}/public/scanner/${this.roomId}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.renderQRCode();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useScannerStore, ["startSession", "endSession", "removeElementFromResults"]),
|
||||
commit(c: string) {
|
||||
this.$emit("code", c);
|
||||
},
|
||||
renderQRCode() {
|
||||
QRCode.toDataURL(this.linkToScan, {
|
||||
width: 300,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: "#000000",
|
||||
light: "#FFFFFF",
|
||||
},
|
||||
errorCorrectionLevel: "M",
|
||||
})
|
||||
.then((res) => {
|
||||
(this.$refs.qr as HTMLImageElement).src = res;
|
||||
})
|
||||
.catch((err) => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -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/scanner/ManageScanModal.vue"))),
|
||||
"codeScanInput",
|
||||
(result: string) => {
|
||||
(this.$refs.resultInput as HTMLInputElement).value = result;
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,51 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-col items-center w-full md:max-w-md">
|
||||
<div class="w-full flex flex-row items-center justify-between">
|
||||
<p>Link zur Scanoberfläche</p>
|
||||
<XMarkIcon class="w-5 h-5 cursor-pointer" @click="closeModal" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<img ref="qr" />
|
||||
<TextCopy :copyText="roomId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useScannerStore } from "@/stores/admin/scanner";
|
||||
import { XMarkIcon } from "@heroicons/vue/24/outline";
|
||||
import TextCopy from "../TextCopy.vue";
|
||||
import QRCode from "qrcode";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
computed: {
|
||||
...mapState(useScannerStore, ["roomId"]),
|
||||
linkToScan() {
|
||||
return `${window.location.origin}/public/scanner/${this.roomId}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
QRCode.toDataURL(this.linkToScan, {
|
||||
width: 300,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: "#000000",
|
||||
light: "#FFFFFF",
|
||||
},
|
||||
errorCorrectionLevel: "M",
|
||||
})
|
||||
.then((res) => {
|
||||
(this.$refs.qr as HTMLImageElement).src = res;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,41 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-col items-center w-full md:max-w-md h-[455px] overflow-hidden">
|
||||
<div class="w-full flex flex-row items-center justify-between">
|
||||
<p>Scan-Ergebnisse</p>
|
||||
<XMarkIcon class="w-5 h-5 cursor-pointer" @click="closeModal" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div class="w-full grow flex flex-col gap-2 overflow-y-scroll pr-2">
|
||||
<div
|
||||
v-for="i in results"
|
||||
:key="i"
|
||||
class="h-10 w-full flex justify-center items-center gap-2 py-2 px-4 text-sm font-medium rounded-md text-white bg-primary"
|
||||
>
|
||||
<p class="grow select-text">{{ i }}</p>
|
||||
<TrashIcon class="w-5 h-5 cursor-pointer" @click="() => removeElementFromResults(i)" />
|
||||
</div>
|
||||
<p v-if="results.length == 0">Bisher keine Scan-Ergebnisse vorhanden.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useScannerStore } from "@/stores/admin/scanner";
|
||||
import { TrashIcon, XMarkIcon } from "@heroicons/vue/24/outline";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
computed: {
|
||||
...mapState(useScannerStore, ["results"]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useModalStore, ["closeModal"]),
|
||||
...mapActions(useScannerStore, ["removeElementFromResults"]),
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,42 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full h-fit min-h-fit flex flex-row gap-2 bg-white rounded-lg items-center overflow-hidden p-4">
|
||||
<LinkSlashIcon v-if="connectedDevices == 0" class="w-5 h-5" />
|
||||
<LinkIcon v-else class="w-5 h-5" />
|
||||
<p class="grow">Externer Scanner aktiviert</p>
|
||||
<div title="Link zur Scan-Ansicht" @click="showQRCode">
|
||||
<QrCodeIcon class="w-6 h-6 cursor-pointer" />
|
||||
</div>
|
||||
<div title="Scan-Ergebnisse anzeigen" @click="showResults">
|
||||
<RectangleStackIcon class="w-6 h-6 cursor-pointer" />
|
||||
</div>
|
||||
<div title="Scan-Verbindung beenden" @click="endSession">
|
||||
<NoSymbolIcon class="w-6 h-6 cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useScannerStore } from "@/stores/admin/scanner";
|
||||
import { LinkIcon, LinkSlashIcon, NoSymbolIcon, QrCodeIcon, RectangleStackIcon } from "@heroicons/vue/24/outline";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
computed: {
|
||||
...mapState(useScannerStore, ["inUse", "results", "roomId", "connectedDevices"]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useScannerStore, ["endSession"]),
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
showQRCode() {
|
||||
this.openModal(markRaw(defineAsyncComponent(() => import("@/components/scanner/ScanQRCodeModal.vue"))));
|
||||
},
|
||||
showResults() {
|
||||
this.openModal(markRaw(defineAsyncComponent(() => import("@/components/scanner/ScanResultsModal.vue"))));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,77 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-2 w-full min-h-[455px]">
|
||||
<qrcode-stream
|
||||
class="grow"
|
||||
:constraints="selectedCamera?.constraints"
|
||||
:track="trackFunctionOptions[4].value"
|
||||
:formats="barcodeFormats"
|
||||
:paused="paused"
|
||||
@error="onError"
|
||||
@detect="onDetect"
|
||||
@camera-on="onCameraReady"
|
||||
/>
|
||||
<select v-model="selectedCamera">
|
||||
<option v-for="c in selecteableCameras" :value="c">{{ c.label }}</option>
|
||||
</select>
|
||||
<div>
|
||||
<label for="manual">Code eingeben</label>
|
||||
<input v-if="useInput" id="manual" type="text" v-model="detected" />
|
||||
</div>
|
||||
<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" :disabled="detected == ''">bestätigen</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import {
|
||||
barcodeFormats,
|
||||
defaultConstraintOptions,
|
||||
getAvailableCameras,
|
||||
handleScannerError,
|
||||
trackFunctionOptions,
|
||||
type Camera,
|
||||
} from "@/helpers/scanner";
|
||||
import { QrcodeStream, type DetectedBarcode } from "vue-qrcode-reader";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
useInput: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["code", "ready"],
|
||||
data() {
|
||||
return {
|
||||
selecteableCameras: defaultConstraintOptions,
|
||||
selectedCamera: undefined as undefined | Camera,
|
||||
paused: false,
|
||||
detected: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async onCameraReady() {
|
||||
this.selecteableCameras = await getAvailableCameras();
|
||||
if (!this.selectedCamera) {
|
||||
this.selectedCamera = this.selecteableCameras[0];
|
||||
}
|
||||
this.$emit("ready");
|
||||
},
|
||||
onDetect(result: Array<DetectedBarcode>) {
|
||||
this.paused = true;
|
||||
this.detected = result.map((r) => r.rawValue)[0];
|
||||
},
|
||||
onError(err: Error) {
|
||||
console.log(handleScannerError(err));
|
||||
},
|
||||
commit() {
|
||||
this.$emit("code", this.detected);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,233 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled" multiple>
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:displayValue="(e) => chosen.map((c) => c.title).join(', ')"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="available.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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="damageReport in available"
|
||||
as="template"
|
||||
:key="damageReport.id"
|
||||
:value="damageReport.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 }">
|
||||
{{ damageReport.title }} <span v-if="damageReport.reportedBy">von {{ damageReport.reportedBy }}</span>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import { useDamageReportStore } from "@/stores/admin/unit/damageReport";
|
||||
import type { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport.models";
|
||||
import difference from "lodash.difference";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: [],
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
related: {
|
||||
type: String as PropType<"vehicle" | "equipment" | "wearable">,
|
||||
default: "equipment",
|
||||
},
|
||||
relatedId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value", "add:difference", "remove:difference", "add:damageReport"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
// if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadDamageReportsInitial();
|
||||
},
|
||||
related() {
|
||||
this.reload();
|
||||
},
|
||||
relatedId() {
|
||||
this.reload();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<DamageReportViewModel>,
|
||||
filtered: [] as Array<DamageReportViewModel>,
|
||||
chosen: [] as Array<DamageReportViewModel>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: Array<string>) {
|
||||
this.$emit("update:model-value", val);
|
||||
if (this.modelValue.length < val.length) {
|
||||
let diff = difference(val, this.modelValue);
|
||||
if (diff.length != 1) return;
|
||||
|
||||
let diffObj = this.getDamageReportFromSearch(diff[0]);
|
||||
if (!diffObj) return;
|
||||
|
||||
this.$emit("add:difference", diff[0]);
|
||||
this.$emit("add:damageReport", diffObj);
|
||||
} else {
|
||||
let diff = difference(this.modelValue, val);
|
||||
if (diff.length != 1) return;
|
||||
this.$emit("remove:difference", diff[0]);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useDamageReportStore, [
|
||||
"searchDamageReports",
|
||||
"getDamageReportsByIds",
|
||||
"getAllDamageReportsWithRelated",
|
||||
"searchDamageReportsWithRelated",
|
||||
]),
|
||||
reload() {
|
||||
this.chosen = [];
|
||||
this.filtered = [];
|
||||
this.preloadAll();
|
||||
this.loadDamageReportsInitial();
|
||||
},
|
||||
preloadAll() {
|
||||
this.all = [];
|
||||
if (this.relatedId == "") return;
|
||||
this.loading = true;
|
||||
this.getAllDamageReportsWithRelated(this.related, this.relatedId)
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchDamageReportsWithRelated(this.related, this.relatedId, this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getDamageReportFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadDamageReportsInitial() {
|
||||
if (this.modelValue.length == 0) {
|
||||
this.chosen = [];
|
||||
} else {
|
||||
this.getDamageReportsByIds(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,216 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:display-value="() => chosen?.title ?? ''"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="available.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="available.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="damageReport in available"
|
||||
as="template"
|
||||
:key="damageReport.id"
|
||||
:value="damageReport.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 }">
|
||||
{{ damageReport.title }} <span v-if="damageReport.reportedBy">von {{ damageReport.reportedBy }}</span>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import { useDamageReportStore } from "@/stores/admin/unit/damageReport";
|
||||
import type { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport.models";
|
||||
import Spinner from "../Spinner.vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
related: {
|
||||
type: String as PropType<"vehicle" | "equipment" | "wearable">,
|
||||
default: "equipment",
|
||||
},
|
||||
relatedId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
//if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadDamageReportInitial();
|
||||
},
|
||||
related() {
|
||||
this.reload();
|
||||
},
|
||||
relatedId() {
|
||||
this.reload();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<DamageReportViewModel>,
|
||||
filtered: [] as Array<DamageReportViewModel>,
|
||||
chosen: undefined as undefined | DamageReportViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getDamageReportFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useDamageReportStore, [
|
||||
"searchDamageReports",
|
||||
"fetchDamageReportById",
|
||||
"getAllDamageReportsWithRelated",
|
||||
"searchDamageReportsWithRelated",
|
||||
]),
|
||||
reload() {
|
||||
this.chosen = undefined;
|
||||
this.filtered = [];
|
||||
this.preloadAll();
|
||||
this.loadDamageReportInitial();
|
||||
},
|
||||
preloadAll() {
|
||||
this.all = [];
|
||||
if (this.relatedId == "") return;
|
||||
this.loading = true;
|
||||
this.getAllDamageReportsWithRelated(this.related, this.relatedId)
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchDamageReportsWithRelated(this.related, this.relatedId, this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getDamageReportFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadDamageReportInitial() {
|
||||
if (this.modelValue == "" || this.modelValue == null) return;
|
||||
this.fetchDamageReportById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,212 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:class="useScanner ? 'pl-9!' : ''"
|
||||
:displayValue="() => chosen?.name ?? ''"
|
||||
@input="query = $event.target.value"
|
||||
/>
|
||||
<QrCodeIcon
|
||||
v-if="useScanner"
|
||||
class="absolute h-6 stroke-1 left-2 top-1/2 -translate-y-1/2 cursor-pointer z-10"
|
||||
@click="scanCode"
|
||||
/>
|
||||
<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="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 z-20"
|
||||
>
|
||||
<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="available.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="equipment in available"
|
||||
as="template"
|
||||
:key="equipment.id"
|
||||
:value="equipment.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 }">
|
||||
{{ equipment.name }}<span v-if="equipment.code"> - Code: {{ equipment.code }}</span>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, markRaw, type Prop } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
|
||||
import type { EquipmentViewModel } from "@/viewmodels/admin/unit/equipment/equipment.models";
|
||||
import { QrCodeIcon } from "@heroicons/vue/24/outline";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useScanner: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadEquipmentInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<EquipmentViewModel>,
|
||||
filtered: [] as Array<EquipmentViewModel>,
|
||||
chosen: undefined as undefined | EquipmentViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getEquipmentFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadEquipmentInitial();
|
||||
this.preloadAll();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useEquipmentStore, ["searchEquipments", "fetchEquipmentById", "getAllEquipments"]),
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
preloadAll() {
|
||||
this.loading = true;
|
||||
this.getAllEquipments()
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchEquipments(this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getEquipmentFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadEquipmentInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchEquipmentById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
scanCode() {
|
||||
this.openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/scanner/ManageScanModal.vue"))),
|
||||
"codeScanInput",
|
||||
(result: string) => {
|
||||
this.getEquipmentFromSearch(result);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,190 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:displayValue="() => chosen?.type ?? ''"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="available.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="equipmentType in available"
|
||||
as="template"
|
||||
:key="equipmentType.id"
|
||||
:value="equipmentType.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 }">
|
||||
{{ equipmentType.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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipmentType";
|
||||
import type { EquipmentTypeViewModel } from "@/viewmodels/admin/unit/equipment/equipmentType.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadEquipmentTypeInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<EquipmentTypeViewModel>,
|
||||
filtered: [] as Array<EquipmentTypeViewModel>,
|
||||
chosen: undefined as undefined | EquipmentTypeViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getEquipmentTypeFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadEquipmentTypeInitial();
|
||||
this.preloadAll();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useEquipmentTypeStore, ["searchEquipmentTypes", "fetchEquipmentTypeById", "getAllEquipmentTypes"]),
|
||||
preloadAll() {
|
||||
this.loading = true;
|
||||
this.getAllEquipmentTypes()
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
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;
|
||||
});
|
||||
},
|
||||
getEquipmentTypeFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadEquipmentTypeInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchEquipmentTypeById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,194 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:displayValue="() => chosen?.title ?? ''"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="available.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="inspectionPlan in available"
|
||||
as="template"
|
||||
:key="inspectionPlan.id"
|
||||
:value="inspectionPlan.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 }">
|
||||
{{ inspectionPlan.title }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useInspectionPlanStore } from "@/stores/admin/unit/inspectionPlan/inspectionPlan";
|
||||
import type { InspectionPlanViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadInspectionPlanInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<InspectionPlanViewModel>,
|
||||
filtered: [] as Array<InspectionPlanViewModel>,
|
||||
chosen: undefined as undefined | InspectionPlanViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getInspectionPlanFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadInspectionPlanInitial();
|
||||
this.preloadAll();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useInspectionPlanStore, [
|
||||
"searchInspectionPlans",
|
||||
"fetchInspectionPlanById",
|
||||
"getAllInspectionPlans",
|
||||
]),
|
||||
preloadAll() {
|
||||
this.loading = true;
|
||||
this.getAllInspectionPlans()
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchInspectionPlans(this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getInspectionPlanFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadInspectionPlanInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchInspectionPlanById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,215 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:displayValue="() => chosen?.title ?? ''"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="available.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="inspectionPlan in available"
|
||||
as="template"
|
||||
:key="inspectionPlan.id"
|
||||
:value="inspectionPlan.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 }">
|
||||
{{ inspectionPlan.title }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useInspectionPlanStore } from "@/stores/admin/unit/inspectionPlan/inspectionPlan";
|
||||
import type { InspectionPlanViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
relatedType: {
|
||||
type: String as PropType<"vehicleType" | "equipmentType" | "wearableType">,
|
||||
default: "equipmentType",
|
||||
},
|
||||
relatedTypeId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadInspectionPlanInitial();
|
||||
},
|
||||
relatedType() {
|
||||
this.reload();
|
||||
},
|
||||
relatedTypeId() {
|
||||
this.reload();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<InspectionPlanViewModel>,
|
||||
filtered: [] as Array<InspectionPlanViewModel>,
|
||||
chosen: undefined as undefined | InspectionPlanViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getInspectionPlanFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useInspectionPlanStore, [
|
||||
"fetchInspectionPlanById",
|
||||
"getAllInspectionPlansWithRelated",
|
||||
"searchInspectionPlansWithRelated",
|
||||
]),
|
||||
reload() {
|
||||
this.chosen = undefined;
|
||||
this.filtered = [];
|
||||
this.preloadAll();
|
||||
this.loadInspectionPlanInitial();
|
||||
},
|
||||
preloadAll() {
|
||||
this.all = [];
|
||||
if (this.relatedTypeId == "") return;
|
||||
this.loading = true;
|
||||
this.getAllInspectionPlansWithRelated(this.relatedType, this.relatedTypeId)
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "" || this.relatedTypeId == "") return;
|
||||
this.loading = true;
|
||||
this.searchInspectionPlansWithRelated(this.relatedType, this.relatedTypeId, this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getInspectionPlanFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadInspectionPlanInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchInspectionPlanById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,174 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:display-value="() => (chosen?.firstname ?? '') + ' ' + (chosen?.lastname ?? '')"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="member in filtered"
|
||||
as="template"
|
||||
:key="member.id"
|
||||
:value="member.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 }">
|
||||
{{ member.firstname }} {{ member.lastname }} {{ member.nameaffix }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import { useMemberStore } from "@/stores/admin/club/member/member";
|
||||
import type { MemberViewModel } from "@/viewmodels/admin/club/member/member.models";
|
||||
import Spinner from "../Spinner.vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
//if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadMemberInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
filtered: [] as Array<MemberViewModel>,
|
||||
chosen: undefined as undefined | MemberViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getMemberFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadMemberInitial();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useMemberStore, ["searchMembers", "fetchMemberById"]),
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchMembers(this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getMemberFromSearch(id: string) {
|
||||
return this.filtered.find((f) => f.id == id);
|
||||
},
|
||||
loadMemberInitial() {
|
||||
if (this.modelValue == "" || this.modelValue == null) return;
|
||||
this.fetchMemberById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,212 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:class="useScanner ? 'pl-9!' : ''"
|
||||
:displayValue="() => chosen?.name ?? ''"
|
||||
@input="query = $event.target.value"
|
||||
/>
|
||||
<QrCodeIcon
|
||||
v-if="useScanner"
|
||||
class="absolute h-6 stroke-1 left-2 top-1/2 -translate-y-1/2 cursor-pointer z-10"
|
||||
@click="scanCode"
|
||||
/>
|
||||
<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="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 z-20"
|
||||
>
|
||||
<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="available.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="vehicle in available"
|
||||
as="template"
|
||||
:key="vehicle.id"
|
||||
:value="vehicle.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 }">
|
||||
{{ vehicle.name }}<span v-if="vehicle.code"> - Code: {{ vehicle.code }}</span>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, markRaw, type Prop } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useVehicleStore } from "@/stores/admin/unit/vehicle/vehicle";
|
||||
import type { VehicleViewModel } from "@/viewmodels/admin/unit/vehicle/vehicle.models";
|
||||
import { QrCodeIcon } from "@heroicons/vue/24/outline";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useScanner: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadVehicleInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<VehicleViewModel>,
|
||||
filtered: [] as Array<VehicleViewModel>,
|
||||
chosen: undefined as undefined | VehicleViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getVehicleFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadVehicleInitial();
|
||||
this.preloadAll();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useVehicleStore, ["searchVehicles", "fetchVehicleById", "getAllVehicles"]),
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
preloadAll() {
|
||||
this.loading = true;
|
||||
this.getAllVehicles()
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchVehicles(this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getVehicleFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadVehicleInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchVehicleById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
scanCode() {
|
||||
this.openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/scanner/ManageScanModal.vue"))),
|
||||
"codeScanInput",
|
||||
(result: string) => {
|
||||
this.getVehicleFromSearch(result);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,190 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:displayValue="() => chosen?.type ?? ''"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="available.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="vehicleType in available"
|
||||
as="template"
|
||||
:key="vehicleType.id"
|
||||
:value="vehicleType.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 }">
|
||||
{{ vehicleType.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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useVehicleTypeStore } from "@/stores/admin/unit/vehicleType/vehicleType";
|
||||
import type { VehicleTypeViewModel } from "@/viewmodels/admin/unit/vehicle/vehicleType.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadVehicleTypeInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<VehicleTypeViewModel>,
|
||||
filtered: [] as Array<VehicleTypeViewModel>,
|
||||
chosen: undefined as undefined | VehicleTypeViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getVehicleTypeFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadVehicleTypeInitial();
|
||||
this.preloadAll();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useVehicleTypeStore, ["searchVehicleTypes", "fetchVehicleTypeById", "getAllVehicleTypes"]),
|
||||
preloadAll() {
|
||||
this.loading = true;
|
||||
this.getAllVehicleTypes()
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchVehicleTypes(this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getVehicleTypeFromSearch(id: string) {
|
||||
return this.available.find((f) => f.id == id);
|
||||
},
|
||||
loadVehicleTypeInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchVehicleTypeById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,212 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:class="useScanner ? 'pl-9!' : ''"
|
||||
:displayValue="() => chosen?.name ?? ''"
|
||||
@input="query = $event.target.value"
|
||||
/>
|
||||
<QrCodeIcon
|
||||
v-if="useScanner"
|
||||
class="absolute h-6 stroke-1 left-2 top-1/2 -translate-y-1/2 cursor-pointer z-10"
|
||||
@click="scanCode"
|
||||
/>
|
||||
<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="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 z-20"
|
||||
>
|
||||
<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="available.length === 0 && all.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="wearable in available"
|
||||
as="template"
|
||||
:key="wearable.id"
|
||||
:value="wearable.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 }">
|
||||
{{ wearable.name }}<span v-if="wearable.code"> - Code: {{ wearable.code }}</span>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, defineComponent, markRaw, type Prop } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useWearableStore } from "@/stores/admin/unit/wearable/wearable";
|
||||
import type { WearableViewModel } from "@/viewmodels/admin/unit/wearable/wearable.models";
|
||||
import { QrCodeIcon } from "@heroicons/vue/24/outline";
|
||||
import { useModalStore } from "@/stores/modal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useScanner: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadWearableInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<WearableViewModel>,
|
||||
filtered: [] as Array<WearableViewModel>,
|
||||
chosen: undefined as undefined | WearableViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getWearableFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadWearableInitial();
|
||||
this.preloadAll();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useWearableStore, ["searchWearables", "fetchWearableById", "getAllWearables"]),
|
||||
...mapActions(useModalStore, ["openModal"]),
|
||||
preloadAll() {
|
||||
this.loading = true;
|
||||
this.getAllWearables()
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchWearables(this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getWearableFromSearch(id: string) {
|
||||
return this.all.find((f) => f.id == id);
|
||||
},
|
||||
loadWearableInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchWearableById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
scanCode() {
|
||||
this.openModal(
|
||||
markRaw(defineAsyncComponent(() => import("@/components/scanner/ManageScanModal.vue"))),
|
||||
"codeScanInput",
|
||||
(result: string) => {
|
||||
this.getWearableFromSearch(result);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,190 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<Combobox v-model="selected" :disabled="disabled">
|
||||
<ComboboxLabel>{{ title }}</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"
|
||||
:displayValue="() => chosen?.type ?? ''"
|
||||
@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="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 z-20"
|
||||
>
|
||||
<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="available.length === 0 && all.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">tippe, um zu suchen...</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
<ComboboxOption v-else-if="available.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">Keine Auswahl gefunden.</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
|
||||
<ComboboxOption
|
||||
v-if="!(loading || deferingSearch)"
|
||||
v-for="wearableType in available"
|
||||
as="template"
|
||||
:key="wearableType.id"
|
||||
:value="wearableType.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 }">
|
||||
{{ wearableType.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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxLabel,
|
||||
ComboboxInput,
|
||||
ComboboxButton,
|
||||
ComboboxOptions,
|
||||
ComboboxOption,
|
||||
TransitionRoot,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import Spinner from "../Spinner.vue";
|
||||
import { useWearableTypeStore } from "@/stores/admin/unit/wearableType/wearableType";
|
||||
import type { WearableTypeViewModel } from "@/viewmodels/admin/unit/wearable/wearableType.models";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:model-value"],
|
||||
watch: {
|
||||
modelValue() {
|
||||
if (this.initialLoaded) return;
|
||||
this.initialLoaded = true;
|
||||
this.loadWearableTypeInitial();
|
||||
},
|
||||
query() {
|
||||
this.deferingSearch = true;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.deferingSearch = false;
|
||||
this.search();
|
||||
}, 600);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialLoaded: false as boolean,
|
||||
loading: false as boolean,
|
||||
deferingSearch: false as boolean,
|
||||
timer: undefined as any,
|
||||
query: "" as string,
|
||||
all: [] as Array<WearableTypeViewModel>,
|
||||
filtered: [] as Array<WearableTypeViewModel>,
|
||||
chosen: undefined as undefined | WearableTypeViewModel,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
available() {
|
||||
return this.query == "" ? this.all : this.filtered;
|
||||
},
|
||||
selected: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(val: string) {
|
||||
this.chosen = this.getWearableTypeFromSearch(val);
|
||||
this.$emit("update:model-value", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadWearableTypeInitial();
|
||||
this.preloadAll();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useWearableTypeStore, ["searchWearableTypes", "fetchWearableTypeById", "getAllWearableTypes"]),
|
||||
preloadAll() {
|
||||
this.loading = true;
|
||||
this.getAllWearableTypes()
|
||||
.then((res) => {
|
||||
this.all = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.filtered = [];
|
||||
if (this.query == "") return;
|
||||
this.loading = true;
|
||||
this.searchWearableTypes(this.query)
|
||||
.then((res) => {
|
||||
this.filtered = res.data;
|
||||
})
|
||||
.catch((err) => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getWearableTypeFromSearch(id: string) {
|
||||
return this.all.find((f) => f.id == id);
|
||||
},
|
||||
loadWearableTypeInitial() {
|
||||
if (this.modelValue == "") return;
|
||||
this.fetchWearableTypeById(this.modelValue)
|
||||
.then((res) => {
|
||||
this.chosen = res.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,6 +0,0 @@
|
|||
export enum InspectionPointEnum {
|
||||
oknok = "oknok",
|
||||
text = "text",
|
||||
number = "number",
|
||||
file = "file",
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export enum SocketConnectionTypes {
|
||||
scanner = "/scanner",
|
||||
pscanner = "/public_scanner",
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
declare global {
|
||||
//type Optional<T> = T | { [K in keyof T]?: never };
|
||||
type Optional<T> = T | never;
|
||||
}
|
|
@ -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;
|
||||
}
|
27
src/main.css
27
src/main.css
|
@ -68,33 +68,26 @@ body {
|
|||
|
||||
/*:not([headlessui]):not([id*="headlessui"]):not([class*="headlessui"])*/
|
||||
button:not([class*="ql"] *):not([class*="fc"]):not([id*="headlessui-combobox"]),
|
||||
[button] {
|
||||
a[button] {
|
||||
@apply cursor-pointer relative box-border h-10 w-full flex justify-center py-2 px-4 text-sm font-medium rounded-md focus:outline-hidden focus:ring-0;
|
||||
}
|
||||
|
||||
button[primary]:not([primary="false"]),
|
||||
[button][primary]:not([primary="false"]) {
|
||||
@apply border-2 border-transparent text-white bg-primary hover:bg-accent;
|
||||
a[button][primary]:not([primary="false"]) {
|
||||
@apply border-2 border-transparent text-white bg-primary hover:bg-primary;
|
||||
}
|
||||
|
||||
button[primary-outline]:not([primary-outline="false"]),
|
||||
[button][primary-outline]:not([primary-outline="false"]) {
|
||||
@apply border-2 border-primary text-black hover:bg-primary hover:text-white;
|
||||
a[button][primary-outline]:not([primary-outline="false"]) {
|
||||
@apply border-2 border-primary text-black hover:bg-primary;
|
||||
}
|
||||
|
||||
button:disabled,
|
||||
[button]:disabled,
|
||||
[button].disabled,
|
||||
[button][disabled="true"] {
|
||||
a[button]:disabled,
|
||||
a[button].disabled {
|
||||
@apply opacity-75 pointer-events-none;
|
||||
}
|
||||
|
||||
a:disabled,
|
||||
a.disabled,
|
||||
a[disabled="true"] {
|
||||
@apply cursor-default pointer-events-none;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]),
|
||||
textarea,
|
||||
select {
|
||||
|
@ -104,14 +97,12 @@ select {
|
|||
input[readonly],
|
||||
textarea[readonly],
|
||||
select[readonly] {
|
||||
@apply select-none focus:border-gray-300 cursor-default;
|
||||
@apply select-none;
|
||||
/* pointer-events-none; */
|
||||
}
|
||||
|
||||
input[disabled],
|
||||
textarea[disabled] {
|
||||
@apply opacity-75;
|
||||
}
|
||||
textarea[disabled],
|
||||
select[disabled] {
|
||||
@apply opacity-75 pointer-events-none;
|
||||
}
|
||||
|
|
|
@ -5,23 +5,10 @@ import { isAuthenticated } from "./authGuard";
|
|||
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 { 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 { setVehicleTypeId } from "./unit/vehicleType";
|
||||
import { resetInspectionStores, setInspectionId } from "./unit/inspection";
|
||||
import { setWearableTypeId } from "./unit/wearableType";
|
||||
import { resetDamageReportStores, setDamageReportId } from "./unit/damageReport";
|
||||
import { resetRepairStores, setRepairId } from "./unit/repair";
|
||||
import { resetMemberStores, setMemberId } from "./memberGuard";
|
||||
import { resetProtocolStores, setProtocolId } from "./protocolGuard";
|
||||
import { resetNewsletterStores, setNewsletterId } from "./newsletterGuard";
|
||||
import { setBackupPage } from "./backupGuard";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
@ -322,761 +309,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: "repair",
|
||||
name: "admin-unit-equipment-repair",
|
||||
component: () => import("@/views/admin/unit/equipment/Repair.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "inspection",
|
||||
name: "admin-unit-equipment-inspection",
|
||||
component: () => import("@/views/admin/unit/equipment/Inspection.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "report",
|
||||
name: "admin-unit-equipment-damage_report",
|
||||
component: () => import("@/views/admin/unit/equipment/DamageReport.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/unit/vehicle/CreateVehicle.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/unit/vehicle/Overview.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "maintenance",
|
||||
name: "admin-unit-vehicle-maintenance",
|
||||
component: () => import("@/views/admin/ViewSelect.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "repair",
|
||||
name: "admin-unit-vehicle-repair",
|
||||
component: () => import("@/views/admin/unit/vehicle/Repair.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "inspection",
|
||||
name: "admin-unit-vehicle-inspection",
|
||||
component: () => import("@/views/admin/unit/vehicle/Inspection.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "report",
|
||||
name: "admin-unit-vehicle-damage_report",
|
||||
component: () => import("@/views/admin/unit/vehicle/DamageReport.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "edit",
|
||||
name: "admin-unit-vehicle-edit",
|
||||
component: () => import("@/views/admin/unit/vehicle/UpdateVehicle.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: "maintenance",
|
||||
name: "admin-unit-wearable-maintenance",
|
||||
component: () => import("@/views/admin/ViewSelect.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "repair",
|
||||
name: "admin-unit-wearable-repair",
|
||||
component: () => import("@/views/admin/unit/wearable/Repair.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "inspection",
|
||||
name: "admin-unit-wearable-inspection",
|
||||
component: () => import("@/views/admin/unit/wearable/Inspection.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "report",
|
||||
name: "admin-unit-wearable-damage_report",
|
||||
component: () => import("@/views/admin/unit/wearable/DamageReport.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: "create",
|
||||
name: "admin-unit-respiratory_gear-create",
|
||||
component: () => import("@/views/admin/unit/respiratoryGear/CreateRespiratoryGear.vue"),
|
||||
meta: { type: "create", section: "unit", module: "respiratory_gear" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
},
|
||||
{
|
||||
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: "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: "create",
|
||||
name: "admin-unit-respiratory_wearer-create",
|
||||
component: () => import("@/views/admin/unit/respiratoryWearer/CreateRespiratoryWearer.vue"),
|
||||
meta: { type: "create", section: "unit", module: "respiratory_wearer" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
},
|
||||
{
|
||||
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: "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: "create",
|
||||
name: "admin-unit-respiratory_mission-create",
|
||||
component: () => import("@/views/admin/unit/respiratoryMission/CreateRespiratoryMission.vue"),
|
||||
meta: { type: "create", section: "unit", module: "respiratory_mission" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
},
|
||||
{
|
||||
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: "damage-report",
|
||||
name: "admin-unit-damage_report-route",
|
||||
component: () => import("@/views/RouterView.vue"),
|
||||
meta: { type: "read", section: "unit", module: "damage_report" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-damage_report",
|
||||
redirect: { name: "admin-unit-damage_report-open" },
|
||||
},
|
||||
{
|
||||
path: "status",
|
||||
name: "admin-unit-damage_report-statusrouting",
|
||||
component: () => import("@/views/admin/unit/damageReport/DamageReportStatusRouting.vue"),
|
||||
beforeEnter: [resetDamageReportStores],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-damage_report-status",
|
||||
redirect: { name: "admin-unit-damage_report-open" },
|
||||
},
|
||||
{
|
||||
path: "open",
|
||||
name: "admin-unit-damage_report-open",
|
||||
component: () => import("@/views/admin/unit/damageReport/DamageReport.vue"),
|
||||
},
|
||||
{
|
||||
path: "done",
|
||||
name: "admin-unit-damage_report-done",
|
||||
component: () => import("@/views/admin/unit/damageReport/DamageReportClosed.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":damageReportId",
|
||||
name: "admin-unit-damage_report-routing",
|
||||
component: () => import("@/views/admin/unit/damageReport/DamageReportRouting.vue"),
|
||||
beforeEnter: [setDamageReportId],
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-damage_report-overview",
|
||||
component: () => import("@/views/admin/unit/damageReport/Overview.vue"),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "repair",
|
||||
name: "admin-unit-repair-route",
|
||||
component: () => import("@/views/RouterView.vue"),
|
||||
meta: { type: "read", section: "unit", module: "repair" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-repair",
|
||||
redirect: { name: "admin-unit-repair-open" },
|
||||
},
|
||||
{
|
||||
path: "status",
|
||||
name: "admin-unit-repair-statusrouting",
|
||||
component: () => import("@/views/admin/unit/repair/RepairStatusRouting.vue"),
|
||||
beforeEnter: [resetRepairStores],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-repair-status",
|
||||
redirect: { name: "admin-unit-repair-open" },
|
||||
},
|
||||
{
|
||||
path: "open",
|
||||
name: "admin-unit-repair-open",
|
||||
component: () => import("@/views/admin/unit/repair/RepairOpen.vue"),
|
||||
},
|
||||
{
|
||||
path: "done",
|
||||
name: "admin-unit-repair-done",
|
||||
component: () => import("@/views/admin/unit/repair/RepairClosed.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "create/:type?/:relatedId?",
|
||||
name: "admin-unit-repair-create",
|
||||
component: () => import("@/views/admin/unit/repair/RepairCreate.vue"),
|
||||
meta: { type: "create", section: "unit", module: "repair" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "execute/:repairId",
|
||||
name: "admin-unit-repair-routing",
|
||||
component: () => import("@/views/admin/unit/repair/RepairRouting.vue"),
|
||||
beforeEnter: [setRepairId],
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-repair-overview",
|
||||
component: () => import("@/views/admin/unit/repair/Overview.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "reports",
|
||||
name: "admin-unit-repair-reports",
|
||||
component: () => import("@/views/admin/unit/repair/DamageReports.vue"),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "maintenance",
|
||||
name: "admin-unit-maintenance-route",
|
||||
component: () => import("@/views/admin/unit/maintenance/MaintenanceRouting.vue"),
|
||||
meta: { type: "read", section: "unit", module: "maintenance" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-maintenance",
|
||||
component: () => import("@/views/admin/unit/maintenance/Maintenance.vue"),
|
||||
},
|
||||
{
|
||||
path: "done",
|
||||
name: "admin-unit-maintenance-done",
|
||||
component: () => import("@/views/admin/unit/maintenance/Maintenance.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/unit/equipmentType/UpdateEquipmentType.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: [setVehicleTypeId],
|
||||
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/unit/vehicleType/UpdateVehicleType.vue"),
|
||||
meta: { type: "update", section: "unit", module: "vehicle_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",
|
||||
name: "admin-unit-wearable_type-routing",
|
||||
component: () => import("@/views/admin/unit/wearableType/WearableTypeRouting.vue"),
|
||||
beforeEnter: [setWearableTypeId],
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: "overview",
|
||||
name: "admin-unit-wearable_type-overview",
|
||||
component: () => import("@/views/admin/unit/wearableType/Overview.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "inspection-plan",
|
||||
name: "admin-unit-wearable_type-inspection_plan",
|
||||
component: () => import("@/views/admin/unit/wearableType/InspectionPlans.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "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: "inspection_plan" },
|
||||
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/unit/inspectionPlan/UpdateInspectionPlan.vue"),
|
||||
meta: { type: "update", section: "unit", module: "inspection_plan" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "pointedit",
|
||||
name: "admin-unit-inspection_plan-pointedit",
|
||||
component: () => import("@/views/admin/unit/inspectionPlan/UpdateInspectionPlanPoints.vue"),
|
||||
meta: { type: "update", section: "unit", module: "inspection_plan" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "inspection",
|
||||
name: "admin-unit-inspection-route",
|
||||
component: () => import("@/views/RouterView.vue"),
|
||||
meta: { type: "create", section: "unit", module: "inspection" },
|
||||
beforeEnter: [abilityAndNavUpdate, resetInspectionStores],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-unit-inspection-routing",
|
||||
component: () => import("@/views/admin/unit/inspection/InspectionRouting.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "next",
|
||||
name: "admin-unit-inspection",
|
||||
component: () => import("@/views/admin/unit/inspection/NextInspections.vue"),
|
||||
},
|
||||
{
|
||||
path: "running",
|
||||
name: "admin-unit-inspection-running",
|
||||
component: () => import("@/views/admin/unit/inspection/RunningInspections.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "start/:type?/:relatedId?/:inspectionPlanId?",
|
||||
name: "admin-unit-inspection-start",
|
||||
component: () => import("@/views/admin/unit/inspection/InspectionStart.vue"),
|
||||
meta: { type: "create", section: "unit", module: "inspection_plan" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "execute/:inspectionId",
|
||||
name: "admin-unit-inspection-execute",
|
||||
component: () => import("@/views/admin/unit/inspection/InspectionExecute.vue"),
|
||||
beforeEnter: [setInspectionId],
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "configuration",
|
||||
name: "admin-configuration",
|
||||
|
@ -1546,29 +778,6 @@ const router = createRouter({
|
|||
name: "public-calendar-explain",
|
||||
component: () => import("@/views/public/calendar/CalendarExplain.vue"),
|
||||
},
|
||||
{
|
||||
path: "scanner",
|
||||
name: "public-scanner-routing",
|
||||
component: () => import("@/views/public/scanner/ScannerRouting.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "public-scanner-select",
|
||||
component: () => import("@/views/public/scanner/RoomSelect.vue"),
|
||||
},
|
||||
{
|
||||
path: ":room",
|
||||
name: "public-scanner-room",
|
||||
component: () => import("@/views/public/scanner/Scanner.vue"),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "damagereport",
|
||||
name: "public-damage_report",
|
||||
component: () => import("@/views/public/damageReport/Report.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useMemberAwardStore } from "@/stores/admin/club/member/memberAward";
|
|||
import { useMemberExecutivePositionStore } from "@/stores/admin/club/member/memberExecutivePosition";
|
||||
import { useMemberQualificationStore } from "@/stores/admin/club/member/memberQualification";
|
||||
import { useMembershipStore } from "@/stores/admin/club/member/membership";
|
||||
import { useMemberEducationStore } from "@/stores/admin/club/member/memberEducation";
|
||||
import { useMemberEducationStore } from "../stores/admin/club/member/memberEducation";
|
||||
|
||||
export async function setMemberId(to: any, from: any, next: any) {
|
||||
const member = useMemberStore();
|
|
@ -1,20 +0,0 @@
|
|||
import { useDamageReportStore } from "@/stores/admin/unit/damageReport";
|
||||
|
||||
export async function setDamageReportId(to: any, from: any, next: any) {
|
||||
const damageReportStore = useDamageReportStore();
|
||||
damageReportStore.activeDamageReport = to.params?.damageReportId ?? null;
|
||||
|
||||
//xystore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetDamageReportStores(to: any, from: any, next: any) {
|
||||
const damageReportStore = useDamageReportStore();
|
||||
damageReportStore.activeDamageReport = null;
|
||||
damageReportStore.activeDamageReportObj = null;
|
||||
|
||||
//xystore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment";
|
||||
import { useEquipmentDamageReportStore } from "@/stores/admin/unit/equipment/damageReport";
|
||||
import { useEquipmentInspectionStore } from "@/stores/admin/unit/equipment/inspection";
|
||||
import { useEquipmentRepairStore } from "@/stores/admin/unit/equipment/repair";
|
||||
|
||||
export async function setEquipmentId(to: any, from: any, next: any) {
|
||||
const equipmentStore = useEquipmentStore();
|
||||
equipmentStore.activeEquipment = to.params?.equipmentId ?? null;
|
||||
|
||||
useEquipmentDamageReportStore().$reset();
|
||||
useEquipmentInspectionStore().$reset();
|
||||
useEquipmentRepairStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetEquipmentStores(to: any, from: any, next: any) {
|
||||
const equipmentStore = useEquipmentStore();
|
||||
equipmentStore.activeEquipment = null;
|
||||
equipmentStore.activeEquipmentObj = null;
|
||||
|
||||
useEquipmentDamageReportStore().$reset();
|
||||
useEquipmentInspectionStore().$reset();
|
||||
useEquipmentRepairStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipmentType";
|
||||
import { useEquipmentTypeInspectionPlanStore } from "@/stores/admin/unit/equipmentType/inspectionPlan";
|
||||
|
||||
export async function setEquipmentTypeId(to: any, from: any, next: any) {
|
||||
const equipmentTypeStore = useEquipmentTypeStore();
|
||||
equipmentTypeStore.activeEquipmentType = to.params?.equipmentTypeId ?? null;
|
||||
|
||||
useEquipmentTypeInspectionPlanStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetEquipmentTypeStores(to: any, from: any, next: any) {
|
||||
const equipmentTypeStore = useEquipmentTypeStore();
|
||||
equipmentTypeStore.activeEquipmentType = null;
|
||||
equipmentTypeStore.activeEquipmentTypeObj = null;
|
||||
|
||||
useEquipmentTypeInspectionPlanStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { useInspectionStore } from "@/stores/admin/unit/inspection/inspection";
|
||||
|
||||
export async function setInspectionId(to: any, from: any, next: any) {
|
||||
const inspectionStore = useInspectionStore();
|
||||
inspectionStore.activeInspection = to.params?.inspectionId ?? null;
|
||||
|
||||
//useXYStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetInspectionStores(to: any, from: any, next: any) {
|
||||
const inspectionStore = useInspectionStore();
|
||||
inspectionStore.activeInspection = null;
|
||||
inspectionStore.activeInspectionObj = null;
|
||||
|
||||
//useXYStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { useInspectionPlanStore } from "@/stores/admin/unit/inspectionPlan/inspectionPlan";
|
||||
import { useInspectionPointStore } from "@/stores/admin/unit/inspectionPlan/inspectionPoint";
|
||||
|
||||
export async function setInspectionPlanId(to: any, from: any, next: any) {
|
||||
const inspectionPlanStore = useInspectionPlanStore();
|
||||
inspectionPlanStore.activeInspectionPlan = to.params?.inspectionPlanId ?? null;
|
||||
|
||||
useInspectionPointStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetInspectionPlanStores(to: any, from: any, next: any) {
|
||||
const inspectionPlanStore = useInspectionPlanStore();
|
||||
inspectionPlanStore.activeInspectionPlan = null;
|
||||
inspectionPlanStore.activeInspectionPlanObj = null;
|
||||
|
||||
useInspectionPointStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { useRepairStore } from "@/stores/admin/unit/repair";
|
||||
|
||||
export async function setRepairId(to: any, from: any, next: any) {
|
||||
const repairStore = useRepairStore();
|
||||
repairStore.activeRepair = to.params?.repairId ?? null;
|
||||
|
||||
//xystore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetRepairStores(to: any, from: any, next: any) {
|
||||
const repairStore = useRepairStore();
|
||||
repairStore.activeRepair = null;
|
||||
repairStore.activeRepairObj = null;
|
||||
|
||||
//xystore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { useVehicleStore } from "@/stores/admin/unit/vehicle/vehicle";
|
||||
import { useVehicleDamageReportStore } from "@/stores/admin/unit/vehicle/damageReport";
|
||||
import { useVehicleInspectionStore } from "@/stores/admin/unit/vehicle/inspection";
|
||||
import { useVehicleRepairStore } from "@/stores/admin/unit/vehicle/repair";
|
||||
|
||||
export async function setVehicleId(to: any, from: any, next: any) {
|
||||
const vehicleStore = useVehicleStore();
|
||||
vehicleStore.activeVehicle = to.params?.vehicleId ?? null;
|
||||
|
||||
useVehicleDamageReportStore().$reset();
|
||||
useVehicleInspectionStore().$reset();
|
||||
useVehicleRepairStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetVehicleStores(to: any, from: any, next: any) {
|
||||
const vehicleStore = useVehicleStore();
|
||||
vehicleStore.activeVehicle = null;
|
||||
vehicleStore.activeVehicleObj = null;
|
||||
|
||||
useVehicleDamageReportStore().$reset();
|
||||
useVehicleInspectionStore().$reset();
|
||||
useVehicleRepairStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { useVehicleTypeStore } from "@/stores/admin/unit/vehicleType/vehicleType";
|
||||
import { useVehicleTypeInspectionPlanStore } from "@/stores/admin/unit/vehicleType/inspectionPlan";
|
||||
|
||||
export async function setVehicleTypeId(to: any, from: any, next: any) {
|
||||
const vehicleTypeStore = useVehicleTypeStore();
|
||||
vehicleTypeStore.activeVehicleType = to.params?.vehicleTypeId ?? null;
|
||||
|
||||
useVehicleTypeInspectionPlanStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetVehicleTypeStores(to: any, from: any, next: any) {
|
||||
const vehicleTypeStore = useVehicleTypeStore();
|
||||
vehicleTypeStore.activeVehicleType = null;
|
||||
vehicleTypeStore.activeVehicleTypeObj = null;
|
||||
|
||||
useVehicleTypeInspectionPlanStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { useWearableStore } from "@/stores/admin/unit/wearable/wearable";
|
||||
import { useWearableDamageReportStore } from "@/stores/admin/unit/wearable/damageReport";
|
||||
import { useWearableRepairStore } from "@/stores/admin/unit/wearable/repair";
|
||||
import { useWearableInspectionStore } from "@/stores/admin/unit/wearable/inspection";
|
||||
|
||||
export async function setWearableId(to: any, from: any, next: any) {
|
||||
const wearableStore = useWearableStore();
|
||||
wearableStore.activeWearable = to.params?.wearableId ?? null;
|
||||
|
||||
useWearableDamageReportStore().$reset();
|
||||
useWearableInspectionStore().$reset();
|
||||
useWearableRepairStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetWearableStores(to: any, from: any, next: any) {
|
||||
const wearableStore = useWearableStore();
|
||||
wearableStore.activeWearable = null;
|
||||
wearableStore.activeWearableObj = null;
|
||||
|
||||
useWearableDamageReportStore().$reset();
|
||||
useWearableInspectionStore().$reset();
|
||||
useWearableRepairStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { useWearableTypeStore } from "@/stores/admin/unit/wearableType/wearableType";
|
||||
import { useWearableTypeInspectionPlanStore } from "@/stores/admin/unit/wearableType/inspectionPlan";
|
||||
|
||||
export async function setWearableTypeId(to: any, from: any, next: any) {
|
||||
const wearableTypeStore = useWearableTypeStore();
|
||||
wearableTypeStore.activeWearableType = to.params?.wearableTypeId ?? null;
|
||||
|
||||
useWearableTypeInspectionPlanStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function resetWearableTypeStores(to: any, from: any, next: any) {
|
||||
const wearableTypeStore = useWearableTypeStore();
|
||||
wearableTypeStore.activeWearableType = null;
|
||||
wearableTypeStore.activeWearableTypeObj = null;
|
||||
|
||||
useWearableTypeInspectionPlanStore().$reset();
|
||||
|
||||
next();
|
||||
}
|
|
@ -78,7 +78,7 @@ http.interceptors.response.use(
|
|||
}
|
||||
);
|
||||
|
||||
async function refreshToken(): Promise<void> {
|
||||
export async function refreshToken(): Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
await http
|
||||
.post(`/auth/refresh`, {
|
||||
|
@ -135,4 +135,4 @@ async function* streamingFetch(path: string, abort?: AbortController) {
|
|||
}
|
||||
}
|
||||
|
||||
export { http, newEventSource, streamingFetch, host, url, refreshToken };
|
||||
export { http, newEventSource, streamingFetch, host, url };
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
import { Manager, Socket } from "socket.io-client";
|
||||
import { refreshToken, url } from "./serverCom";
|
||||
import { useNotificationStore } from "./stores/notification";
|
||||
import { SocketConnectionTypes } from "./enums/socketEnum";
|
||||
|
||||
export abstract class SocketManager {
|
||||
private static readonly manager = new Manager(url, {
|
||||
reconnection: true,
|
||||
reconnectionDelayMax: 10000,
|
||||
});
|
||||
private static readonly connections = new Map<SocketConnectionTypes, Socket>();
|
||||
|
||||
public static establishConnection(
|
||||
connection: SocketConnectionTypes,
|
||||
restoreAfterDisconnect: boolean = false
|
||||
): Socket {
|
||||
console.log("establish");
|
||||
const existingSocket = this.connections.get(connection);
|
||||
if (existingSocket !== undefined && existingSocket.connected) return existingSocket!;
|
||||
console.log("create");
|
||||
existingSocket?.removeAllListeners();
|
||||
|
||||
const notificationStore = useNotificationStore();
|
||||
let socket = this.manager.socket(connection, {
|
||||
auth: (cb) => {
|
||||
cb({ token: localStorage.getItem("accessToken") });
|
||||
},
|
||||
});
|
||||
socket.on("connect", () => {
|
||||
notificationStore.push("Socket-Erfolg", `Verbindung aufgebaut`, "success");
|
||||
});
|
||||
socket.on("connect_error", (err) => {
|
||||
this.socketHandleError(connection, err, true);
|
||||
});
|
||||
socket.on("disconnect", () => {
|
||||
if (restoreAfterDisconnect) this.establishConnection(connection, restoreAfterDisconnect);
|
||||
else notificationStore.push("Socket", `Verbindung getrennt`, "info");
|
||||
});
|
||||
socket.on("warning", (msg: string) => {
|
||||
notificationStore.push("Socket-Warnung", msg, "warning");
|
||||
});
|
||||
socket.on("error", (msg: string) => {
|
||||
this.socketHandleError(connection, {
|
||||
name: "Error",
|
||||
message: msg,
|
||||
});
|
||||
});
|
||||
|
||||
this.connections.set(connection, socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
public static getConnection(connection: SocketConnectionTypes) {
|
||||
return this.connections.get(connection);
|
||||
}
|
||||
|
||||
public static closeConnection(connection: SocketConnectionTypes) {
|
||||
let socket = this.connections.get(connection);
|
||||
if (socket) {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
this.connections.delete(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private static socketHandleError(connection: SocketConnectionTypes, err: Error, onConnect = false) {
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
if (err.message == "xhr poll error") {
|
||||
notificationStore.push("Socket-Netzwerk-Fehler", "Reconnect Versuch in 10s", "error");
|
||||
} else if (err.message == "Token expired") {
|
||||
notificationStore.push("Session", "Session wird verlängert", "info");
|
||||
refreshToken()
|
||||
.then(() => {
|
||||
notificationStore.push("Session", "Session erfolgreich verlängert", "success");
|
||||
this.closeConnection(connection);
|
||||
})
|
||||
.catch(() => {
|
||||
notificationStore.push("Session-Fehler", "Anmeldung wurde nicht verlängert", "error");
|
||||
});
|
||||
} else if (onConnect) {
|
||||
notificationStore.push("Socket-Fehler", `Verbindung fehlgeschlagen`, "error");
|
||||
} else {
|
||||
notificationStore.push("Socket-Fehler", err.message, "error");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,15 +62,6 @@ export const useNavigationStore = defineStore("navigation", {
|
|||
} as topLevelNavigationModel,
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.canAccessSection("unit")
|
||||
? [
|
||||
{
|
||||
key: "unit",
|
||||
title: "Wehr",
|
||||
levelDefault: "equipment",
|
||||
} as topLevelNavigationModel,
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.canAccessSection("configuration")
|
||||
? [
|
||||
{
|
||||
|
@ -109,46 +100,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" }] : []),
|
||||
...(false && abilityStore.can("read", "unit", "respiratory_gear")
|
||||
? [{ key: "respiratory_gear", title: "Atemschutz-Geräte" }]
|
||||
: []),
|
||||
...(false && abilityStore.can("read", "unit", "respiratory_wearer")
|
||||
? [{ key: "respiratory_wearer", title: "Atemschutz-Träger" }]
|
||||
: []),
|
||||
...(false && abilityStore.can("read", "unit", "respiratory_mission")
|
||||
? [{ key: "respiratory_mission", title: "Atemschutz-Einsätze" }]
|
||||
: []),
|
||||
...(abilityStore.can("create", "unit", "inspection") ? [{ key: "inspection", title: "Prüfungen" }] : []),
|
||||
...(false && abilityStore.can("read", "unit", "maintenance")
|
||||
? [{ key: "maintenance", title: "Wartungen" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "unit", "damage_report")
|
||||
? [{ key: "damage_report", title: "Schadensmeldungen" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "unit", "repair") ? [{ key: "repair", title: "Reparaturen" }] : []),
|
||||
{ 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")
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { SocketManager } from "@/socketManager";
|
||||
import { SocketConnectionTypes } from "@/enums/socketEnum";
|
||||
import { useNotificationStore } from "../notification";
|
||||
|
||||
export const useScannerStore = defineStore("scanner", {
|
||||
state: () => {
|
||||
return {
|
||||
inUse: false as boolean,
|
||||
roomId: undefined as undefined | string,
|
||||
results: [] as Array<string>,
|
||||
connectedDevices: 0 as number,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
startSession() {
|
||||
if (this.inUse) return;
|
||||
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
this.roomId = uuid();
|
||||
this.inUse = true;
|
||||
let connection = SocketManager.establishConnection(SocketConnectionTypes.scanner);
|
||||
connection.on("connect", () => {
|
||||
SocketManager.getConnection(SocketConnectionTypes.scanner)?.emit("session:create", this.roomId);
|
||||
});
|
||||
connection.on("status-session:create", () => {
|
||||
notificationStore.push("Socket-Erfolg", `Scan-Session gestartet`, "success");
|
||||
});
|
||||
connection.on("status-session:close", () => {
|
||||
notificationStore.push("Socket", `Scan-Session beendet`, "info");
|
||||
SocketManager.getConnection(SocketConnectionTypes.scanner)?.disconnect();
|
||||
});
|
||||
connection.on("package-scanner_join", (socketId: string) => {
|
||||
this.connectedDevices++;
|
||||
notificationStore.push("Scan-Verbindung", `Neuer Scanner verbunden`, "info");
|
||||
});
|
||||
connection.on("package-scanner_leave", (socketId: string) => {
|
||||
this.connectedDevices--;
|
||||
notificationStore.push("Scan-Verbindung", `Scanner getrennt`, "info");
|
||||
});
|
||||
connection.on("package-scan_receive", (result: string) => {
|
||||
this.results.push(result);
|
||||
notificationStore.push("Scan", `Neuen Scan erhalten`, "info");
|
||||
});
|
||||
},
|
||||
endSession() {
|
||||
this.inUse = false;
|
||||
this.roomId = undefined;
|
||||
this.results = [];
|
||||
SocketManager.getConnection(SocketConnectionTypes.scanner)?.emit("session:close");
|
||||
},
|
||||
removeElementFromResults(el: string) {
|
||||
this.results = this.results.filter((result) => result !== el);
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,123 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
import type { DamageReportViewModel, UpdateDamageReportViewModel } from "@/viewmodels/admin/unit/damageReport.models";
|
||||
import { http } from "@/serverCom";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
export const useDamageReportStore = defineStore("damageReport", {
|
||||
state: () => {
|
||||
return {
|
||||
damageReports: [] as Array<DamageReportViewModel & { tab_pos: number }>,
|
||||
totalCount: 0 as number,
|
||||
loading: "loading" as "loading" | "fetched" | "failed",
|
||||
activeDamageReport: null as string | null,
|
||||
activeDamageReportObj: null as DamageReportViewModel | null,
|
||||
loadingActive: "loading" as "loading" | "fetched" | "failed",
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
formatQueryReturnToPagination(result: AxiosResponse<any, any>, offset: number) {
|
||||
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);
|
||||
});
|
||||
},
|
||||
fetchOpenDamageReports(offset = 0, count = 25, search = "", clear = false) {
|
||||
if (clear) this.damageReports = [];
|
||||
this.loading = "loading";
|
||||
http
|
||||
.get(`/admin/damageReport?done=false&offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
|
||||
.then((result) => {
|
||||
this.formatQueryReturnToPagination(result, offset);
|
||||
this.loading = "fetched";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = "failed";
|
||||
});
|
||||
},
|
||||
fetchDoneDamageReports(offset = 0, count = 25, search = "", clear = false) {
|
||||
if (clear) this.damageReports = [];
|
||||
this.loading = "loading";
|
||||
http
|
||||
.get(`/admin/damageReport?done=true&offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`)
|
||||
.then((result) => {
|
||||
this.formatQueryReturnToPagination(result, offset);
|
||||
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 };
|
||||
});
|
||||
},
|
||||
async getAllDamageReportsWithRelated(
|
||||
related: "vehicle" | "equipment" | "wearable",
|
||||
relatedId: string
|
||||
): Promise<AxiosResponse<any, any>> {
|
||||
return await http.get(`/admin/damageReport/${related}/${relatedId}?noLimit=true`).then((res) => {
|
||||
return { ...res, data: res.data.damageReports };
|
||||
});
|
||||
},
|
||||
async searchDamageReportsWithRelated(
|
||||
related: "vehicle" | "equipment" | "wearable",
|
||||
relatedId: string,
|
||||
search: string
|
||||
): Promise<AxiosResponse<any, any>> {
|
||||
return await http.get(`/admin/damageReport/${related}/${relatedId}?search=${search}&noLimit=true`).then((res) => {
|
||||
return { ...res, data: res.data.damageReports };
|
||||
});
|
||||
},
|
||||
fetchDamageReportByActiveId() {
|
||||
this.loadingActive = "loading";
|
||||
http
|
||||
.get(`/admin/damageReport/${this.activeDamageReport}`)
|
||||
.then((res) => {
|
||||
this.activeDamageReportObj = res.data;
|
||||
this.loadingActive = "fetched";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loadingActive = "failed";
|
||||
});
|
||||
},
|
||||
fetchDamageReportById(id: string) {
|
||||
return http.get(`/admin/damageReport/${id}`);
|
||||
},
|
||||
loadDamageReportImage(url: string) {
|
||||
return http.get(`/admin/damageReport/${this.activeDamageReportObj?.id}/${url}`, {
|
||||
responseType: "blob",
|
||||
});
|
||||
},
|
||||
async updateDamageReport(damageReport: UpdateDamageReportViewModel): Promise<AxiosResponse<any, any>> {
|
||||
const result = await http.patch(`/admin/damageReport/${damageReport.id}`, {
|
||||
status: damageReport.status,
|
||||
noteByWorker: damageReport.noteByWorker,
|
||||
done: damageReport.done,
|
||||
});
|
||||
return result;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { http } from "@/serverCom";
|
||||
import { useEquipmentStore } from "./equipment";
|
||||
import type { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport.models";
|
||||
|
||||
export const useEquipmentDamageReportStore = defineStore("equipmentDamageReport", {
|
||||
state: () => {
|
||||
return {
|
||||
damageReports: [] as Array<DamageReportViewModel & { tab_pos: number }>,
|
||||
totalCount: 0 as number,
|
||||
loading: "loading" as "loading" | "fetched" | "failed",
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
fetchDamageReportForEquipment(offset = 0, count = 25, search = "", clear = false) {
|
||||
const equipmentId = useEquipmentStore().activeEquipment;
|
||||
if (clear) this.damageReports = [];
|
||||
this.loading = "loading";
|
||||
http
|
||||
.get(
|
||||
`/admin/damagereport/equipment/${equipmentId}?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";
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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";
|
||||
|
||||
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) {
|
||||
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.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`, {
|
||||
equipmentTypeId: equipment.equipmentTypeId,
|
||||
name: equipment.name,
|
||||
code: equipment.code,
|
||||
location: equipment.location,
|
||||
commissioned: equipment.commissioned,
|
||||
});
|
||||
this.fetchEquipments();
|
||||
return result;
|
||||
},
|
||||
async updateActiveEquipment(equipment: UpdateEquipmentViewModel): Promise<AxiosResponse<any, any>> {
|
||||
const result = await http.patch(`/admin/equipment/${equipment.id}`, {
|
||||
name: equipment.name,
|
||||
code: equipment.code,
|
||||
location: equipment.location,
|
||||
commissioned: equipment.commissioned,
|
||||
decommissioned: equipment.decommissioned,
|
||||
});
|
||||
this.fetchEquipments();
|
||||
return result;
|
||||
},
|
||||
async deleteEquipment(equipment: number): Promise<AxiosResponse<any, any>> {
|
||||
const result = await http.delete(`/admin/equipment/${equipment}`);
|
||||
this.fetchEquipments();
|
||||
return result;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { http } from "@/serverCom";
|
||||
import type { InspectionViewModel } from "@/viewmodels/admin/unit/inspection/inspection.models";
|
||||
import { useEquipmentStore } from "./equipment";
|
||||
|
||||
export const useEquipmentInspectionStore = defineStore("equipmentInspection", {
|
||||
state: () => {
|
||||
return {
|
||||
inspections: [] as Array<InspectionViewModel & { tab_pos: number }>,
|
||||
totalCount: 0 as number,
|
||||
loading: "loading" as "loading" | "fetched" | "failed",
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
fetchInspectionForEquipment(offset = 0, count = 25, clear = false) {
|
||||
const equipmentId = useEquipmentStore().activeEquipment;
|
||||
if (clear) this.inspections = [];
|
||||
this.loading = "loading";
|
||||
http
|
||||
.get(`/admin/inspection/equipment/${equipmentId}?offset=${offset}&count=${count}`)
|
||||
.then((result) => {
|
||||
this.totalCount = result.data.total;
|
||||
result.data.inspections
|
||||
.filter((elem: InspectionViewModel) => this.inspections.findIndex((m) => m.id == elem.id) == -1)
|
||||
.map((elem: InspectionViewModel, index: number): InspectionViewModel & { tab_pos: number } => {
|
||||
return {
|
||||
...elem,
|
||||
tab_pos: index + offset,
|
||||
};
|
||||
})
|
||||
.forEach((elem: InspectionViewModel & { tab_pos: number }) => {
|
||||
this.inspections.push(elem);
|
||||
});
|
||||
this.loading = "fetched";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = "failed";
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { http } from "@/serverCom";
|
||||
import { useEquipmentStore } from "./equipment";
|
||||
import type { RepairViewModel } from "@/viewmodels/admin/unit/repair.models";
|
||||
|
||||
export const useEquipmentRepairStore = defineStore("equipmentRepair", {
|
||||
state: () => {
|
||||
return {
|
||||
repairs: [] as Array<RepairViewModel & { tab_pos: number }>,
|
||||
totalCount: 0 as number,
|
||||
loading: "loading" as "loading" | "fetched" | "failed",
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
fetchRepairForEquipment(offset = 0, count = 25, search = "", clear = false) {
|
||||
const equipmentId = useEquipmentStore().activeEquipment;
|
||||
if (clear) this.repairs = [];
|
||||
this.loading = "loading";
|
||||
http
|
||||
.get(
|
||||
`/admin/repair/equipment/${equipmentId}?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`
|
||||
)
|
||||
.then((result) => {
|
||||
this.totalCount = result.data.total;
|
||||
result.data.repairs
|
||||
.filter((elem: RepairViewModel) => this.repairs.findIndex((m) => m.id == elem.id) == -1)
|
||||
.map((elem: RepairViewModel, index: number): RepairViewModel & { tab_pos: number } => {
|
||||
return {
|
||||
...elem,
|
||||
tab_pos: index + offset,
|
||||
};
|
||||
})
|
||||
.forEach((elem: RepairViewModel & { tab_pos: number }) => {
|
||||
this.repairs.push(elem);
|
||||
});
|
||||
this.loading = "fetched";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = "failed";
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,93 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
import type {
|
||||
EquipmentTypeViewModel,
|
||||
CreateEquipmentTypeViewModel,
|
||||
UpdateEquipmentTypeViewModel,
|
||||
} from "@/viewmodels/admin/unit/equipment/equipmentType.models";
|
||||
import { http } from "@/serverCom";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
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) {
|
||||
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.equipmentTypes
|
||||
.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.equipmentTypes };
|
||||
});
|
||||
},
|
||||
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.equipmentTypes };
|
||||
});
|
||||
},
|
||||
fetchEquipmentTypeByActiveId() {
|
||||
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`, {
|
||||
type: equipmentType.type,
|
||||
description: equipmentType.description,
|
||||
});
|
||||
this.fetchEquipmentTypes();
|
||||
return result;
|
||||
},
|
||||
async updateActiveEquipmentType(equipmentType: UpdateEquipmentTypeViewModel): Promise<AxiosResponse<any, any>> {
|
||||
const result = await http.patch(`/admin/equipmentType/${equipmentType.id}`, {
|
||||
type: equipmentType.type,
|
||||
description: equipmentType.description,
|
||||
});
|
||||
this.fetchEquipmentTypes();
|
||||
return result;
|
||||
},
|
||||
async deleteEquipmentType(equipmentType: number): Promise<AxiosResponse<any, any>> {
|
||||
const result = await http.delete(`/admin/equipmentType/${equipmentType}`);
|
||||
this.fetchEquipmentTypes();
|
||||
return result;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { http } from "@/serverCom";
|
||||
import type { InspectionPlanViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
|
||||
import { useEquipmentTypeStore } from "./equipmentType";
|
||||
|
||||
export const useEquipmentTypeInspectionPlanStore = defineStore("equipmentTypeInspectionPlan", {
|
||||
state: () => {
|
||||
return {
|
||||
inspectionPlans: [] as Array<InspectionPlanViewModel>,
|
||||
loading: "loading" as "loading" | "fetched" | "failed",
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
fetchInspectionPlanForEquipmentType() {
|
||||
const equipmentTypeId = useEquipmentTypeStore().activeEquipmentType;
|
||||
this.loading = "loading";
|
||||
http
|
||||
.get(`/admin/inspectionPlan/equipmentType/${equipmentTypeId}?noLimit=true`)
|
||||
.then((result) => {
|
||||
this.inspectionPlans = result.data.inspectionPlans;
|
||||
this.loading = "fetched";
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = "failed";
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue