From 3e87bbc2679326b5ec74f818a24b756c4f5feb16 Mon Sep 17 00:00:00 2001 From: Julian Krauser <jkrauser209@gmail.com> Date: Tue, 25 Mar 2025 10:42:40 +0100 Subject: [PATCH] base components on collections --- src/components/CodeDetector.vue | 8 +- .../damageReport/DamageReportListItem.vue | 34 ++++ .../CreateRespiratoryGearModal.vue | 157 ++++++++++++++++++ .../RespiratoryGearListItem.vue | 34 ++++ .../CreateRespiratoryMissionModal.vue | 157 ++++++++++++++++++ .../RespiratoryMissionListItem.vue | 34 ++++ .../CreateRespiratoryWearerModal.vue | 157 ++++++++++++++++++ .../RespiratoryWearerListItem.vue | 34 ++++ src/helpers/codeScanner.ts | 6 +- src/router/index.ts | 11 +- .../admin/unit/damageReport/damageReport.ts | 99 +++++++++++ .../unit/respiratoryGear/respiratoryGear.ts | 120 +++++++++++++ .../respiratoryMission/respiratoryMission.ts | 126 ++++++++++++++ .../respiratoryWearer/respiratoryWearer.ts | 126 ++++++++++++++ .../unit/damageReport/damageReport.models.ts | 39 +++++ .../respiratoryGear/respiratoryGear.models.ts | 39 +++++ .../respiratoryMission.models.ts | 39 +++++ .../respiratoryWearer.models.ts | 39 +++++ .../admin/unit/damageReport/DamageReport.vue | 58 ++++++- .../unit/equipmentType/EquipmentType.vue | 1 - .../unit/respiratoryGear/RespiratoryGear.vue | 27 +-- .../respiratoryMission/RespiratoryMission.vue | 29 ++-- .../respiratoryWearer/RespiratoryWearer.vue | 29 ++-- src/views/admin/unit/vehicle/Vehicle.vue | 1 - 24 files changed, 1347 insertions(+), 57 deletions(-) create mode 100644 src/components/admin/unit/damageReport/DamageReportListItem.vue create mode 100644 src/components/admin/unit/respiratoryGear/CreateRespiratoryGearModal.vue create mode 100644 src/components/admin/unit/respiratoryGear/RespiratoryGearListItem.vue create mode 100644 src/components/admin/unit/respiratoryMission/CreateRespiratoryMissionModal.vue create mode 100644 src/components/admin/unit/respiratoryMission/RespiratoryMissionListItem.vue create mode 100644 src/components/admin/unit/respiratoryWearer/CreateRespiratoryWearerModal.vue create mode 100644 src/components/admin/unit/respiratoryWearer/RespiratoryWearerListItem.vue create mode 100644 src/stores/admin/unit/damageReport/damageReport.ts create mode 100644 src/stores/admin/unit/respiratoryGear/respiratoryGear.ts create mode 100644 src/stores/admin/unit/respiratoryMission/respiratoryMission.ts create mode 100644 src/stores/admin/unit/respiratoryWearer/respiratoryWearer.ts create mode 100644 src/viewmodels/admin/unit/damageReport/damageReport.models.ts create mode 100644 src/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models.ts create mode 100644 src/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models.ts create mode 100644 src/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models.ts diff --git a/src/components/CodeDetector.vue b/src/components/CodeDetector.vue index 1832f1a..373fc9d 100644 --- a/src/components/CodeDetector.vue +++ b/src/components/CodeDetector.vue @@ -2,7 +2,7 @@ <div class="w-full md:max-w-md"> <XMarkIcon class="ml-auto mb-2 w-5 h-5 cursor-pointer" @click="closeModal" /> <qrcode-stream - :constraints="selectedCamera.constraints" + :constraints="selectedCamera?.constraints" :track="trackFunctionOptions[4].value" :formats="barcodeFormats" :paused="paused" @@ -31,6 +31,7 @@ import { getAvailableCameras, handleScannerError, trackFunctionOptions, + type Camera, } from "../helpers/codeScanner"; import { QrcodeStream, type DetectedBarcode } from "vue-qrcode-reader"; import { XMarkIcon } from "@heroicons/vue/24/outline"; @@ -47,7 +48,7 @@ export default defineComponent({ data() { return { selecteableCameras: defaultConstraintOptions, - selectedCamera: defaultConstraintOptions[0], + selectedCamera: undefined as undefined | Camera, paused: false, detected: "", }; @@ -56,6 +57,9 @@ export default defineComponent({ ...mapActions(useModalStore, ["closeModal"]), async onCameraReady() { this.selecteableCameras = await getAvailableCameras(); + if (!this.selectedCamera) { + this.selectedCamera = this.selecteableCameras[0]; + } }, onDetect(result: Array<DetectedBarcode>) { this.paused = true; diff --git a/src/components/admin/unit/damageReport/DamageReportListItem.vue b/src/components/admin/unit/damageReport/DamageReportListItem.vue new file mode 100644 index 0000000..d2515ae --- /dev/null +++ b/src/components/admin/unit/damageReport/DamageReportListItem.vue @@ -0,0 +1,34 @@ +<template> + <RouterLink + :to="{ name: 'admin-unit-damageReport-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.lastname }}, {{ damageReport.firstname }} + {{ damageReport.nameaffix ? `- ${damageReport.nameaffix}` : "" }} + </p> + </div> + <div class="p-2"> + <p v-if="damageReport.internalId">ID: {{ damageReport.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 { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport/damageReport.models"; +</script> + +<script lang="ts"> +export default defineComponent({ + props: { + damageReport: { type: Object as PropType<DamageReportViewModel>, default: {} }, + }, + computed: { + ...mapState(useAbilityStore, ["can"]), + }, +}); +</script> diff --git a/src/components/admin/unit/respiratoryGear/CreateRespiratoryGearModal.vue b/src/components/admin/unit/respiratoryGear/CreateRespiratoryGearModal.vue new file mode 100644 index 0000000..4f934d7 --- /dev/null +++ b/src/components/admin/unit/respiratoryGear/CreateRespiratoryGearModal.vue @@ -0,0 +1,157 @@ +<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-sm 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-none 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-none 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 = { + salutationId: this.selectedSalutation.id, + firstname: formData.firstname.value, + lastname: formData.lastname.value, + nameaffix: formData.nameaffix.value, + birthdate: formData.birthdate.value, + internalId: formData.internalId.value, + }; + this.status = "loading"; + this.createEquipment(createEquipment) + .then(() => { + this.status = { status: "success" }; + this.timeout = setTimeout(() => { + this.closeModal(); + }, 1500); + }) + .catch(() => { + this.status = { status: "failed" }; + }); + }, + }, +}); +</script> diff --git a/src/components/admin/unit/respiratoryGear/RespiratoryGearListItem.vue b/src/components/admin/unit/respiratoryGear/RespiratoryGearListItem.vue new file mode 100644 index 0000000..9e2cb47 --- /dev/null +++ b/src/components/admin/unit/respiratoryGear/RespiratoryGearListItem.vue @@ -0,0 +1,34 @@ +<template> + <RouterLink + :to="{ name: 'admin-unit-respiratoryGear-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.lastname }}, {{ respiratoryGear.firstname }} + {{ respiratoryGear.nameaffix ? `- ${respiratoryGear.nameaffix}` : "" }} + </p> + </div> + <div class="p-2"> + <p v-if="respiratoryGear.internalId">ID: {{ respiratoryGear.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 { RespiratoryGearViewModel } from "@/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models"; +</script> + +<script lang="ts"> +export default defineComponent({ + props: { + respiratoryGear: { type: Object as PropType<RespiratoryGearViewModel>, default: {} }, + }, + computed: { + ...mapState(useAbilityStore, ["can"]), + }, +}); +</script> diff --git a/src/components/admin/unit/respiratoryMission/CreateRespiratoryMissionModal.vue b/src/components/admin/unit/respiratoryMission/CreateRespiratoryMissionModal.vue new file mode 100644 index 0000000..4f934d7 --- /dev/null +++ b/src/components/admin/unit/respiratoryMission/CreateRespiratoryMissionModal.vue @@ -0,0 +1,157 @@ +<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-sm 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-none 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-none 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 = { + salutationId: this.selectedSalutation.id, + firstname: formData.firstname.value, + lastname: formData.lastname.value, + nameaffix: formData.nameaffix.value, + birthdate: formData.birthdate.value, + internalId: formData.internalId.value, + }; + this.status = "loading"; + this.createEquipment(createEquipment) + .then(() => { + this.status = { status: "success" }; + this.timeout = setTimeout(() => { + this.closeModal(); + }, 1500); + }) + .catch(() => { + this.status = { status: "failed" }; + }); + }, + }, +}); +</script> diff --git a/src/components/admin/unit/respiratoryMission/RespiratoryMissionListItem.vue b/src/components/admin/unit/respiratoryMission/RespiratoryMissionListItem.vue new file mode 100644 index 0000000..324265a --- /dev/null +++ b/src/components/admin/unit/respiratoryMission/RespiratoryMissionListItem.vue @@ -0,0 +1,34 @@ +<template> + <RouterLink + :to="{ name: 'admin-unit-respiratoryMission-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.lastname }}, {{ respiratoryMission.firstname }} + {{ respiratoryMission.nameaffix ? `- ${respiratoryMission.nameaffix}` : "" }} + </p> + </div> + <div class="p-2"> + <p v-if="respiratoryMission.internalId">ID: {{ respiratoryMission.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 { RespiratoryMissionViewModel } from "@/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models"; +</script> + +<script lang="ts"> +export default defineComponent({ + props: { + respiratoryMission: { type: Object as PropType<RespiratoryMissionViewModel>, default: {} }, + }, + computed: { + ...mapState(useAbilityStore, ["can"]), + }, +}); +</script> diff --git a/src/components/admin/unit/respiratoryWearer/CreateRespiratoryWearerModal.vue b/src/components/admin/unit/respiratoryWearer/CreateRespiratoryWearerModal.vue new file mode 100644 index 0000000..4f934d7 --- /dev/null +++ b/src/components/admin/unit/respiratoryWearer/CreateRespiratoryWearerModal.vue @@ -0,0 +1,157 @@ +<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-sm 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-none 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-none 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 = { + salutationId: this.selectedSalutation.id, + firstname: formData.firstname.value, + lastname: formData.lastname.value, + nameaffix: formData.nameaffix.value, + birthdate: formData.birthdate.value, + internalId: formData.internalId.value, + }; + this.status = "loading"; + this.createEquipment(createEquipment) + .then(() => { + this.status = { status: "success" }; + this.timeout = setTimeout(() => { + this.closeModal(); + }, 1500); + }) + .catch(() => { + this.status = { status: "failed" }; + }); + }, + }, +}); +</script> diff --git a/src/components/admin/unit/respiratoryWearer/RespiratoryWearerListItem.vue b/src/components/admin/unit/respiratoryWearer/RespiratoryWearerListItem.vue new file mode 100644 index 0000000..69d8628 --- /dev/null +++ b/src/components/admin/unit/respiratoryWearer/RespiratoryWearerListItem.vue @@ -0,0 +1,34 @@ +<template> + <RouterLink + :to="{ name: 'admin-unit-respiratoryWearer-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.lastname }}, {{ respiratoryWearer.firstname }} + {{ respiratoryWearer.nameaffix ? `- ${respiratoryWearer.nameaffix}` : "" }} + </p> + </div> + <div class="p-2"> + <p v-if="respiratoryWearer.internalId">ID: {{ respiratoryWearer.internalId }}</p> + </div> + </RouterLink> +</template> + +<script setup lang="ts"> +import { defineComponent, type PropType } from "vue"; +import { mapState, mapActions } from "pinia"; +import { useAbilityStore } from "@/stores/ability"; +import type { RespiratoryWearerViewModel } from "@/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models"; +</script> + +<script lang="ts"> +export default defineComponent({ + props: { + respiratoryWearer: { type: Object as PropType<RespiratoryWearerViewModel>, default: {} }, + }, + computed: { + ...mapState(useAbilityStore, ["can"]), + }, +}); +</script> diff --git a/src/helpers/codeScanner.ts b/src/helpers/codeScanner.ts index bf3c026..6894088 100644 --- a/src/helpers/codeScanner.ts +++ b/src/helpers/codeScanner.ts @@ -13,7 +13,7 @@ export const defaultConstraintOptions: Array<Camera> = [ { label: "front camera", constraints: { facingMode: "user" } }, ]; -export async function getAvailableCameras(): Promise<Array<Camera>> { +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 @@ -22,9 +22,9 @@ export async function getAvailableCameras(): Promise<Array<Camera>> { const videoDevices = devices.filter(({ kind }) => kind === "videoinput"); return [ - ...defaultConstraintOptions, + ...(useDefault ? defaultConstraintOptions : []), ...videoDevices.map(({ deviceId, label }) => ({ - label: `${label} (ID: ${deviceId})`, + label: `${label}`, //(ID: ${deviceId}) constraints: { deviceId, facingMode: "custom" }, })), ]; diff --git a/src/router/index.ts b/src/router/index.ts index b0d0cfb..e72d6a3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -404,17 +404,10 @@ const router = createRouter({ }, { path: "equipment-type", - name: "admin-unit-equipment_type-route", - component: () => import("@/views/RouterView.vue"), + name: "admin-unit-equipment_type", + component: () => import("@/views/admin/unit/equipmentType/EquipmentType.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"), - }, - ], }, ], }, diff --git a/src/stores/admin/unit/damageReport/damageReport.ts b/src/stores/admin/unit/damageReport/damageReport.ts new file mode 100644 index 0000000..aa761e7 --- /dev/null +++ b/src/stores/admin/unit/damageReport/damageReport.ts @@ -0,0 +1,99 @@ +import { defineStore } from "pinia"; +import type { + DamageReportViewModel, + CreateDamageReportViewModel, + UpdateDamageReportViewModel, +} from "@/viewmodels/admin/unit/damageReport/damageReport.models"; +import { http } from "@/serverCom"; +import type { AxiosResponse } from "axios"; + +export const useDamageReportStore = defineStore("damageReport", { + state: () => { + return { + damageReports: [] as Array<DamageReportViewModel & { tab_pos: number }>, + totalCount: 0 as number, + loading: "loading" as "loading" | "fetched" | "failed", + loadingActive: "loading" as "loading" | "fetched" | "failed", + }; + }, + actions: { + fetchDamageReports(offset = 0, count = 25, search = "", clear = false) { + if (clear) this.damageReports = []; + this.loading = "loading"; + http + .get(`/admin/damageReport?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`) + .then((result) => { + this.totalCount = result.data.total; + result.data.damageReports + .filter((elem: DamageReportViewModel) => this.damageReports.findIndex((m) => m.id == elem.id) == -1) + .map((elem: DamageReportViewModel, index: number): DamageReportViewModel & { tab_pos: number } => { + return { + ...elem, + tab_pos: index + offset, + }; + }) + .forEach((elem: DamageReportViewModel & { tab_pos: number }) => { + this.damageReports.push(elem); + }); + this.loading = "fetched"; + }) + .catch((err) => { + this.loading = "failed"; + }); + }, + async getAllDamageReports(): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/damageReport?noLimit=true`).then((res) => { + return { ...res, data: res.data.damageReports }; + }); + }, + async getDamageReportsByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> { + return await http + .post(`/admin/damageReport/ids`, { + ids, + }) + .then((res) => { + return { ...res, data: res.data.damageReports }; + }); + }, + async searchDamageReports(search: string): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/damageReport?search=${search}&noLimit=true`).then((res) => { + return { ...res, data: res.data.damageReports }; + }); + }, + fetchDamageReportById(id: string) { + return http.get(`/admin/damageReport/${id}`); + }, + fetchDamageReportStatisticsById(id: string) { + return http.get(`/admin/damageReport/${id}/statistics`); + }, + async createDamageReport(damageReport: CreateDamageReportViewModel): Promise<AxiosResponse<any, any>> { + const result = await http.post(`/admin/damageReport`, { + salutationId: damageReport.salutationId, + firstname: damageReport.firstname, + lastname: damageReport.lastname, + nameaffix: damageReport.nameaffix, + birthdate: damageReport.birthdate, + internalId: damageReport.internalId, + }); + this.fetchDamageReports(); + return result; + }, + async updateDamageReport(damageReport: UpdateDamageReportViewModel): Promise<AxiosResponse<any, any>> { + const result = await http.patch(`/admin/damageReport/${damageReport.id}`, { + salutationId: damageReport.salutationId, + firstname: damageReport.firstname, + lastname: damageReport.lastname, + nameaffix: damageReport.nameaffix, + birthdate: damageReport.birthdate, + internalId: damageReport.internalId, + }); + this.fetchDamageReports(); + return result; + }, + async deleteDamageReport(damageReport: number): Promise<AxiosResponse<any, any>> { + const result = await http.delete(`/admin/damageReport/${damageReport}`); + this.fetchDamageReports(); + return result; + }, + }, +}); diff --git a/src/stores/admin/unit/respiratoryGear/respiratoryGear.ts b/src/stores/admin/unit/respiratoryGear/respiratoryGear.ts new file mode 100644 index 0000000..94bdc98 --- /dev/null +++ b/src/stores/admin/unit/respiratoryGear/respiratoryGear.ts @@ -0,0 +1,120 @@ +import { defineStore } from "pinia"; +import type { + RespiratoryGearViewModel, + CreateRespiratoryGearViewModel, + UpdateRespiratoryGearViewModel, +} from "@/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models"; +import { http } from "@/serverCom"; +import type { AxiosResponse } from "axios"; + +export const useRespiratoryGearStore = defineStore("respiratoryGear", { + state: () => { + return { + respiratoryGears: [] as Array<RespiratoryGearViewModel & { tab_pos: number }>, + totalCount: 0 as number, + loading: "loading" as "loading" | "fetched" | "failed", + activeRespiratoryGear: null as string | null, + activeRespiratoryGearObj: null as RespiratoryGearViewModel | null, + loadingActive: "loading" as "loading" | "fetched" | "failed", + }; + }, + actions: { + fetchRespiratoryGears(offset = 0, count = 25, search = "", clear = false) { + if (clear) this.respiratoryGears = []; + this.loading = "loading"; + http + .get(`/admin/respiratoryGear?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`) + .then((result) => { + this.totalCount = result.data.total; + result.data.respiratoryGears + .filter((elem: RespiratoryGearViewModel) => this.respiratoryGears.findIndex((m) => m.id == elem.id) == -1) + .map((elem: RespiratoryGearViewModel, index: number): RespiratoryGearViewModel & { tab_pos: number } => { + return { + ...elem, + tab_pos: index + offset, + }; + }) + .forEach((elem: RespiratoryGearViewModel & { tab_pos: number }) => { + this.respiratoryGears.push(elem); + }); + this.loading = "fetched"; + }) + .catch((err) => { + this.loading = "failed"; + }); + }, + async getAllRespiratoryGears(): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/respiratoryGear?noLimit=true`).then((res) => { + return { ...res, data: res.data.respiratoryGears }; + }); + }, + async getRespiratoryGearsByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> { + return await http + .post(`/admin/respiratoryGear/ids`, { + ids, + }) + .then((res) => { + return { ...res, data: res.data.respiratoryGears }; + }); + }, + async searchRespiratoryGears(search: string): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/respiratoryGear?search=${search}&noLimit=true`).then((res) => { + return { ...res, data: res.data.respiratoryGears }; + }); + }, + fetchRespiratoryGearByActiveId() { + this.loadingActive = "loading"; + http + .get(`/admin/respiratoryGear/${this.activeRespiratoryGear}`) + .then((res) => { + this.activeRespiratoryGearObj = res.data; + this.loadingActive = "fetched"; + }) + .catch((err) => { + this.loadingActive = "failed"; + }); + }, + fetchRespiratoryGearById(id: string) { + return http.get(`/admin/respiratoryGear/${id}`); + }, + async printRespiratoryGearByActiveId() { + return http.get(`/admin/respiratoryGear/${this.activeRespiratoryGear}/print`, { + responseType: "blob", + }); + }, + fetchRespiratoryGearStatisticsById(id: string) { + return http.get(`/admin/respiratoryGear/${id}/statistics`); + }, + async createRespiratoryGear(respiratoryGear: CreateRespiratoryGearViewModel): Promise<AxiosResponse<any, any>> { + const result = await http.post(`/admin/respiratoryGear`, { + salutationId: respiratoryGear.salutationId, + firstname: respiratoryGear.firstname, + lastname: respiratoryGear.lastname, + nameaffix: respiratoryGear.nameaffix, + birthdate: respiratoryGear.birthdate, + internalId: respiratoryGear.internalId, + }); + this.fetchRespiratoryGears(); + return result; + }, + async updateActiveRespiratoryGear( + respiratoryGear: UpdateRespiratoryGearViewModel + ): Promise<AxiosResponse<any, any>> { + const result = await http.patch(`/admin/respiratoryGear/${respiratoryGear.id}`, { + salutationId: respiratoryGear.salutationId, + firstname: respiratoryGear.firstname, + lastname: respiratoryGear.lastname, + nameaffix: respiratoryGear.nameaffix, + birthdate: respiratoryGear.birthdate, + internalId: respiratoryGear.internalId, + }); + this.fetchRespiratoryGears(); + return result; + }, + async deleteRespiratoryGear(respiratoryGear: number): Promise<AxiosResponse<any, any>> { + const result = await http.delete(`/admin/respiratoryGear/${respiratoryGear}`); + this.fetchRespiratoryGears(); + return result; + }, + }, +}); diff --git a/src/stores/admin/unit/respiratoryMission/respiratoryMission.ts b/src/stores/admin/unit/respiratoryMission/respiratoryMission.ts new file mode 100644 index 0000000..712a843 --- /dev/null +++ b/src/stores/admin/unit/respiratoryMission/respiratoryMission.ts @@ -0,0 +1,126 @@ +import { defineStore } from "pinia"; +import type { + RespiratoryMissionViewModel, + CreateRespiratoryMissionViewModel, + UpdateRespiratoryMissionViewModel, +} from "@/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models"; +import { http } from "@/serverCom"; +import type { AxiosResponse } from "axios"; + +export const useRespiratoryMissionStore = defineStore("respiratoryMission", { + state: () => { + return { + respiratoryMissions: [] as Array<RespiratoryMissionViewModel & { tab_pos: number }>, + totalCount: 0 as number, + loading: "loading" as "loading" | "fetched" | "failed", + activeRespiratoryMission: null as string | null, + activeRespiratoryMissionObj: null as RespiratoryMissionViewModel | null, + loadingActive: "loading" as "loading" | "fetched" | "failed", + }; + }, + actions: { + fetchRespiratoryMissions(offset = 0, count = 25, search = "", clear = false) { + if (clear) this.respiratoryMissions = []; + this.loading = "loading"; + http + .get(`/admin/respiratoryMission?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`) + .then((result) => { + this.totalCount = result.data.total; + result.data.respiratoryMissions + .filter( + (elem: RespiratoryMissionViewModel) => this.respiratoryMissions.findIndex((m) => m.id == elem.id) == -1 + ) + .map( + (elem: RespiratoryMissionViewModel, index: number): RespiratoryMissionViewModel & { tab_pos: number } => { + return { + ...elem, + tab_pos: index + offset, + }; + } + ) + .forEach((elem: RespiratoryMissionViewModel & { tab_pos: number }) => { + this.respiratoryMissions.push(elem); + }); + this.loading = "fetched"; + }) + .catch((err) => { + this.loading = "failed"; + }); + }, + async getAllRespiratoryMissions(): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/respiratoryMission?noLimit=true`).then((res) => { + return { ...res, data: res.data.respiratoryMissions }; + }); + }, + async getRespiratoryMissionsByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> { + return await http + .post(`/admin/respiratoryMission/ids`, { + ids, + }) + .then((res) => { + return { ...res, data: res.data.respiratoryMissions }; + }); + }, + async searchRespiratoryMissions(search: string): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/respiratoryMission?search=${search}&noLimit=true`).then((res) => { + return { ...res, data: res.data.respiratoryMissions }; + }); + }, + fetchRespiratoryMissionByActiveId() { + this.loadingActive = "loading"; + http + .get(`/admin/respiratoryMission/${this.activeRespiratoryMission}`) + .then((res) => { + this.activeRespiratoryMissionObj = res.data; + this.loadingActive = "fetched"; + }) + .catch((err) => { + this.loadingActive = "failed"; + }); + }, + fetchRespiratoryMissionById(id: string) { + return http.get(`/admin/respiratoryMission/${id}`); + }, + async printRespiratoryMissionByActiveId() { + return http.get(`/admin/respiratoryMission/${this.activeRespiratoryMission}/print`, { + responseType: "blob", + }); + }, + fetchRespiratoryMissionStatisticsById(id: string) { + return http.get(`/admin/respiratoryMission/${id}/statistics`); + }, + async createRespiratoryMission( + respiratoryMission: CreateRespiratoryMissionViewModel + ): Promise<AxiosResponse<any, any>> { + const result = await http.post(`/admin/respiratoryMission`, { + salutationId: respiratoryMission.salutationId, + firstname: respiratoryMission.firstname, + lastname: respiratoryMission.lastname, + nameaffix: respiratoryMission.nameaffix, + birthdate: respiratoryMission.birthdate, + internalId: respiratoryMission.internalId, + }); + this.fetchRespiratoryMissions(); + return result; + }, + async updateActiveRespiratoryMission( + respiratoryMission: UpdateRespiratoryMissionViewModel + ): Promise<AxiosResponse<any, any>> { + const result = await http.patch(`/admin/respiratoryMission/${respiratoryMission.id}`, { + salutationId: respiratoryMission.salutationId, + firstname: respiratoryMission.firstname, + lastname: respiratoryMission.lastname, + nameaffix: respiratoryMission.nameaffix, + birthdate: respiratoryMission.birthdate, + internalId: respiratoryMission.internalId, + }); + this.fetchRespiratoryMissions(); + return result; + }, + async deleteRespiratoryMission(respiratoryMission: number): Promise<AxiosResponse<any, any>> { + const result = await http.delete(`/admin/respiratoryMission/${respiratoryMission}`); + this.fetchRespiratoryMissions(); + return result; + }, + }, +}); diff --git a/src/stores/admin/unit/respiratoryWearer/respiratoryWearer.ts b/src/stores/admin/unit/respiratoryWearer/respiratoryWearer.ts new file mode 100644 index 0000000..18bd220 --- /dev/null +++ b/src/stores/admin/unit/respiratoryWearer/respiratoryWearer.ts @@ -0,0 +1,126 @@ +import { defineStore } from "pinia"; +import type { + RespiratoryWearerViewModel, + CreateRespiratoryWearerViewModel, + UpdateRespiratoryWearerViewModel, +} from "@/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models"; +import { http } from "@/serverCom"; +import type { AxiosResponse } from "axios"; + +export const useRespiratoryWearerStore = defineStore("respiratoryWearer", { + state: () => { + return { + respiratoryWearers: [] as Array<RespiratoryWearerViewModel & { tab_pos: number }>, + totalCount: 0 as number, + loading: "loading" as "loading" | "fetched" | "failed", + activeRespiratoryWearer: null as string | null, + activeRespiratoryWearerObj: null as RespiratoryWearerViewModel | null, + loadingActive: "loading" as "loading" | "fetched" | "failed", + }; + }, + actions: { + fetchRespiratoryWearers(offset = 0, count = 25, search = "", clear = false) { + if (clear) this.respiratoryWearers = []; + this.loading = "loading"; + http + .get(`/admin/respiratoryWearer?offset=${offset}&count=${count}${search != "" ? "&search=" + search : ""}`) + .then((result) => { + this.totalCount = result.data.total; + result.data.respiratoryWearers + .filter( + (elem: RespiratoryWearerViewModel) => this.respiratoryWearers.findIndex((m) => m.id == elem.id) == -1 + ) + .map( + (elem: RespiratoryWearerViewModel, index: number): RespiratoryWearerViewModel & { tab_pos: number } => { + return { + ...elem, + tab_pos: index + offset, + }; + } + ) + .forEach((elem: RespiratoryWearerViewModel & { tab_pos: number }) => { + this.respiratoryWearers.push(elem); + }); + this.loading = "fetched"; + }) + .catch((err) => { + this.loading = "failed"; + }); + }, + async getAllRespiratoryWearers(): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/respiratoryWearer?noLimit=true`).then((res) => { + return { ...res, data: res.data.respiratoryWearers }; + }); + }, + async getRespiratoryWearersByIds(ids: Array<string>): Promise<AxiosResponse<any, any>> { + return await http + .post(`/admin/respiratoryWearer/ids`, { + ids, + }) + .then((res) => { + return { ...res, data: res.data.respiratoryWearers }; + }); + }, + async searchRespiratoryWearers(search: string): Promise<AxiosResponse<any, any>> { + return await http.get(`/admin/respiratoryWearer?search=${search}&noLimit=true`).then((res) => { + return { ...res, data: res.data.respiratoryWearers }; + }); + }, + fetchRespiratoryWearerByActiveId() { + this.loadingActive = "loading"; + http + .get(`/admin/respiratoryWearer/${this.activeRespiratoryWearer}`) + .then((res) => { + this.activeRespiratoryWearerObj = res.data; + this.loadingActive = "fetched"; + }) + .catch((err) => { + this.loadingActive = "failed"; + }); + }, + fetchRespiratoryWearerById(id: string) { + return http.get(`/admin/respiratoryWearer/${id}`); + }, + async printRespiratoryWearerByActiveId() { + return http.get(`/admin/respiratoryWearer/${this.activeRespiratoryWearer}/print`, { + responseType: "blob", + }); + }, + fetchRespiratoryWearerStatisticsById(id: string) { + return http.get(`/admin/respiratoryWearer/${id}/statistics`); + }, + async createRespiratoryWearer( + respiratoryWearer: CreateRespiratoryWearerViewModel + ): Promise<AxiosResponse<any, any>> { + const result = await http.post(`/admin/respiratoryWearer`, { + salutationId: respiratoryWearer.salutationId, + firstname: respiratoryWearer.firstname, + lastname: respiratoryWearer.lastname, + nameaffix: respiratoryWearer.nameaffix, + birthdate: respiratoryWearer.birthdate, + internalId: respiratoryWearer.internalId, + }); + this.fetchRespiratoryWearers(); + return result; + }, + async updateActiveRespiratoryWearer( + respiratoryWearer: UpdateRespiratoryWearerViewModel + ): Promise<AxiosResponse<any, any>> { + const result = await http.patch(`/admin/respiratoryWearer/${respiratoryWearer.id}`, { + salutationId: respiratoryWearer.salutationId, + firstname: respiratoryWearer.firstname, + lastname: respiratoryWearer.lastname, + nameaffix: respiratoryWearer.nameaffix, + birthdate: respiratoryWearer.birthdate, + internalId: respiratoryWearer.internalId, + }); + this.fetchRespiratoryWearers(); + return result; + }, + async deleteRespiratoryWearer(respiratoryWearer: number): Promise<AxiosResponse<any, any>> { + const result = await http.delete(`/admin/respiratoryWearer/${respiratoryWearer}`); + this.fetchRespiratoryWearers(); + return result; + }, + }, +}); diff --git a/src/viewmodels/admin/unit/damageReport/damageReport.models.ts b/src/viewmodels/admin/unit/damageReport/damageReport.models.ts new file mode 100644 index 0000000..7551f19 --- /dev/null +++ b/src/viewmodels/admin/unit/damageReport/damageReport.models.ts @@ -0,0 +1,39 @@ +export interface DamageReportViewModel { + id: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface DamageReportStatisticsViewModel { + id: string; + salutation: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + todayAge: number; + ageThisYear: number; + exactAge: string; +} + +export interface CreateDamageReportViewModel { + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface UpdateDamageReportViewModel { + id: string; + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} diff --git a/src/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models.ts b/src/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models.ts new file mode 100644 index 0000000..b37c28b --- /dev/null +++ b/src/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models.ts @@ -0,0 +1,39 @@ +export interface RespiratoryGearViewModel { + id: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface RespiratoryGearStatisticsViewModel { + id: string; + salutation: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + todayAge: number; + ageThisYear: number; + exactAge: string; +} + +export interface CreateRespiratoryGearViewModel { + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface UpdateRespiratoryGearViewModel { + id: string; + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} diff --git a/src/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models.ts b/src/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models.ts new file mode 100644 index 0000000..2a19bae --- /dev/null +++ b/src/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models.ts @@ -0,0 +1,39 @@ +export interface RespiratoryMissionViewModel { + id: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface RespiratoryMissionStatisticsViewModel { + id: string; + salutation: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + todayAge: number; + ageThisYear: number; + exactAge: string; +} + +export interface CreateRespiratoryMissionViewModel { + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface UpdateRespiratoryMissionViewModel { + id: string; + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} diff --git a/src/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models.ts b/src/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models.ts new file mode 100644 index 0000000..113ee71 --- /dev/null +++ b/src/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models.ts @@ -0,0 +1,39 @@ +export interface RespiratoryWearerViewModel { + id: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface RespiratoryWearerStatisticsViewModel { + id: string; + salutation: string; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + todayAge: number; + ageThisYear: number; + exactAge: string; +} + +export interface CreateRespiratoryWearerViewModel { + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} + +export interface UpdateRespiratoryWearerViewModel { + id: string; + salutationId: number; + firstname: string; + lastname: string; + nameaffix: string; + birthdate: Date; + internalId?: string; +} diff --git a/src/views/admin/unit/damageReport/DamageReport.vue b/src/views/admin/unit/damageReport/DamageReport.vue index 8d99d16..99aabac 100644 --- a/src/views/admin/unit/damageReport/DamageReport.vue +++ b/src/views/admin/unit/damageReport/DamageReport.vue @@ -6,7 +6,38 @@ </div> </template> <template #diffMain> - <div class="flex flex-col w-full h-full gap-2 justify-center px-7"></div> + <div class="flex flex-col w-full h-full gap-2 justify-center px-7"> + <div class="w-full flex flex-row max-lg:flex-wrap justify-center"> + <div + v-for="tab in tabs" + :key="tab.route" + @click="isActive = tab.route" + class="w-1/2 md:w-1/3 lg:w-full p-0.5 first:pl-0 last:pr-0 cursor-pointer" + > + <p + :class="[ + 'w-full rounded-lg py-2.5 text-sm text-center font-medium leading-5 focus:ring-0 outline-none', + isActive == tab.route + ? 'bg-red-200 shadow border-b-2 border-primary rounded-b-none' + : ' hover:bg-red-200', + ]" + > + {{ tab.title }} + </p> + </div> + </div> + <Pagination + :items="damageReports" + :totalCount="totalCount" + :indicateLoading="loading == 'loading'" + @load-data="(offset, count, search) => fetchDamageReports(offset, count, search)" + @search="(search) => fetchDamageReports(0, maxEntriesPerPage, search, true)" + > + <template #pageRow="{ row }: { row: DamageReportViewModel }"> + <DamageReportListItem :damageReport="row" /> + </template> + </Pagination> + </div> </template> </MainTemplate> </template> @@ -15,12 +46,35 @@ import { defineComponent } from "vue"; import { mapActions, mapState } from "pinia"; import MainTemplate from "@/templates/Main.vue"; +import { useAbilityStore } from "@/stores/ability"; +import { useDamageReportStore } from "@/stores/admin/unit/damageReport/damageReport"; +import type { DamageReportViewModel } from "@/viewmodels/admin/unit/damageReport/damageReport.models"; +import Pagination from "@/components/Pagination.vue"; +import DamageReportListItem from "../../../../components/admin/unit/damageReport/DamageReportListItem.vue"; </script> <script lang="ts"> export default defineComponent({ data() { - return {}; + return { + tabs: [ + { route: "overview", title: "offen" }, + { route: "membership", title: "bearbeitet" }, + ], + isActive: "overview", + currentPage: 0, + maxEntriesPerPage: 25, + }; + }, + computed: { + ...mapState(useDamageReportStore, ["damageReports", "totalCount", "loading"]), + ...mapState(useAbilityStore, ["can"]), + }, + mounted() { + this.fetchDamageReports(0, this.maxEntriesPerPage, "", true); + }, + methods: { + ...mapActions(useDamageReportStore, ["fetchDamageReports"]), }, }); </script> diff --git a/src/views/admin/unit/equipmentType/EquipmentType.vue b/src/views/admin/unit/equipmentType/EquipmentType.vue index 3c09340..921ee77 100644 --- a/src/views/admin/unit/equipmentType/EquipmentType.vue +++ b/src/views/admin/unit/equipmentType/EquipmentType.vue @@ -12,7 +12,6 @@ :totalCount="totalCount" :indicateLoading="loading == 'loading'" useSearch - useScanner @load-data="(offset, count, search) => fetchEquipmentTypes(offset, count, search)" @search="(search) => fetchEquipmentTypes(0, maxEntriesPerPage, search, true)" > diff --git a/src/views/admin/unit/respiratoryGear/RespiratoryGear.vue b/src/views/admin/unit/respiratoryGear/RespiratoryGear.vue index c1531cc..ebf2f4f 100644 --- a/src/views/admin/unit/respiratoryGear/RespiratoryGear.vue +++ b/src/views/admin/unit/respiratoryGear/RespiratoryGear.vue @@ -8,16 +8,15 @@ <template #diffMain> <div class="flex flex-col w-full h-full gap-2 justify-center px-7"> <Pagination - :items="equipments" + :items="respiratoryGears" :totalCount="totalCount" :indicateLoading="loading == 'loading'" useSearch - useScanner - @load-data="(offset, count, search) => fetchEquipments(offset, count, search)" - @search="(search) => fetchEquipments(0, maxEntriesPerPage, search, true)" + @load-data="(offset, count, search) => fetchRespiratoryGears(offset, count, search)" + @search="(search) => fetchRespiratoryGears(0, maxEntriesPerPage, search, true)" > - <template #pageRow="{ row }: { row: EquipmentViewModel }"> - <EquipmentListItem :equipment="row" /> + <template #pageRow="{ row }: { row: RespiratoryGearViewModel }"> + <RespiratoryGearListItem :respiratoryGear="row" /> </template> </Pagination> @@ -35,12 +34,12 @@ import { defineAsyncComponent, defineComponent, markRaw } from "vue"; import { mapActions, mapState } from "pinia"; import MainTemplate from "@/templates/Main.vue"; -import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment"; +import { useRespiratoryGearStore } from "@/stores/admin/unit/respiratoryGear/respiratoryGear"; import { useModalStore } from "@/stores/modal"; import Pagination from "@/components/Pagination.vue"; import { useAbilityStore } from "@/stores/ability"; -import type { EquipmentViewModel } from "../../../../viewmodels/admin/unit/equipment/equipment.models"; -import EquipmentListItem from "../../../../components/admin/unit/equipment/EquipmentListItem.vue"; +import type { RespiratoryGearViewModel } from "@/viewmodels/admin/unit/respiratoryGear/respiratoryGear.models"; +import RespiratoryGearListItem from "@/components/admin/unit/respiratoryGear/RespiratoryGearListItem.vue"; </script> <script lang="ts"> @@ -52,18 +51,20 @@ export default defineComponent({ }; }, computed: { - ...mapState(useEquipmentStore, ["equipments", "totalCount", "loading"]), + ...mapState(useRespiratoryGearStore, ["respiratoryGears", "totalCount", "loading"]), ...mapState(useAbilityStore, ["can"]), }, mounted() { - this.fetchEquipments(0, this.maxEntriesPerPage, "", true); + this.fetchRespiratoryGears(0, this.maxEntriesPerPage, "", true); }, methods: { - ...mapActions(useEquipmentStore, ["fetchEquipments"]), + ...mapActions(useRespiratoryGearStore, ["fetchRespiratoryGears"]), ...mapActions(useModalStore, ["openModal"]), openCreateModal() { this.openModal( - markRaw(defineAsyncComponent(() => import("@/components/admin/unit/equipment/CreateEquipmentModal.vue"))) + markRaw( + defineAsyncComponent(() => import("@/components/admin/unit/respiratoryGear/CreateRespiratoryGearModal.vue")) + ) ); }, }, diff --git a/src/views/admin/unit/respiratoryMission/RespiratoryMission.vue b/src/views/admin/unit/respiratoryMission/RespiratoryMission.vue index 0d2d5db..9ed1c0b 100644 --- a/src/views/admin/unit/respiratoryMission/RespiratoryMission.vue +++ b/src/views/admin/unit/respiratoryMission/RespiratoryMission.vue @@ -8,16 +8,15 @@ <template #diffMain> <div class="flex flex-col w-full h-full gap-2 justify-center px-7"> <Pagination - :items="equipments" + :items="respiratoryMissions" :totalCount="totalCount" :indicateLoading="loading == 'loading'" useSearch - useScanner - @load-data="(offset, count, search) => fetchEquipments(offset, count, search)" - @search="(search) => fetchEquipments(0, maxEntriesPerPage, search, true)" + @load-data="(offset, count, search) => fetchRespiratoryMissions(offset, count, search)" + @search="(search) => fetchRespiratoryMissions(0, maxEntriesPerPage, search, true)" > - <template #pageRow="{ row }: { row: EquipmentViewModel }"> - <EquipmentListItem :equipment="row" /> + <template #pageRow="{ row }: { row: RespiratoryMissionViewModel }"> + <RespiratoryMissionListItem :respiratoryMission="row" /> </template> </Pagination> @@ -35,12 +34,12 @@ import { defineAsyncComponent, defineComponent, markRaw } from "vue"; import { mapActions, mapState } from "pinia"; import MainTemplate from "@/templates/Main.vue"; -import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment"; +import { useRespiratoryMissionStore } from "@/stores/admin/unit/respiratoryMission/respiratoryMission"; import { useModalStore } from "@/stores/modal"; import Pagination from "@/components/Pagination.vue"; import { useAbilityStore } from "@/stores/ability"; -import type { EquipmentViewModel } from "../../../../viewmodels/admin/unit/equipment/equipment.models"; -import EquipmentListItem from "../../../../components/admin/unit/equipment/EquipmentListItem.vue"; +import type { RespiratoryMissionViewModel } from "@/viewmodels/admin/unit/respiratoryMission/respiratoryMission.models"; +import RespiratoryMissionListItem from "@/components/admin/unit/respiratoryMission/RespiratoryMissionListItem.vue"; </script> <script lang="ts"> @@ -52,18 +51,22 @@ export default defineComponent({ }; }, computed: { - ...mapState(useEquipmentStore, ["equipments", "totalCount", "loading"]), + ...mapState(useRespiratoryMissionStore, ["respiratoryMissions", "totalCount", "loading"]), ...mapState(useAbilityStore, ["can"]), }, mounted() { - this.fetchEquipments(0, this.maxEntriesPerPage, "", true); + this.fetchRespiratoryMissions(0, this.maxEntriesPerPage, "", true); }, methods: { - ...mapActions(useEquipmentStore, ["fetchEquipments"]), + ...mapActions(useRespiratoryMissionStore, ["fetchRespiratoryMissions"]), ...mapActions(useModalStore, ["openModal"]), openCreateModal() { this.openModal( - markRaw(defineAsyncComponent(() => import("@/components/admin/unit/equipment/CreateEquipmentModal.vue"))) + markRaw( + defineAsyncComponent( + () => import("@/components/admin/unit/respiratoryMission/CreateRespiratoryMissionModal.vue") + ) + ) ); }, }, diff --git a/src/views/admin/unit/respiratoryWearer/RespiratoryWearer.vue b/src/views/admin/unit/respiratoryWearer/RespiratoryWearer.vue index c7d4112..30f48d2 100644 --- a/src/views/admin/unit/respiratoryWearer/RespiratoryWearer.vue +++ b/src/views/admin/unit/respiratoryWearer/RespiratoryWearer.vue @@ -8,16 +8,15 @@ <template #diffMain> <div class="flex flex-col w-full h-full gap-2 justify-center px-7"> <Pagination - :items="equipments" + :items="respiratoryWearers" :totalCount="totalCount" :indicateLoading="loading == 'loading'" useSearch - useScanner - @load-data="(offset, count, search) => fetchEquipments(offset, count, search)" - @search="(search) => fetchEquipments(0, maxEntriesPerPage, search, true)" + @load-data="(offset, count, search) => fetchRespiratoryWearers(offset, count, search)" + @search="(search) => fetchRespiratoryWearers(0, maxEntriesPerPage, search, true)" > - <template #pageRow="{ row }: { row: EquipmentViewModel }"> - <EquipmentListItem :equipment="row" /> + <template #pageRow="{ row }: { row: RespiratoryWearerViewModel }"> + <RespiratoryWearerListItem :respiratoryWearer="row" /> </template> </Pagination> @@ -35,12 +34,12 @@ import { defineAsyncComponent, defineComponent, markRaw } from "vue"; import { mapActions, mapState } from "pinia"; import MainTemplate from "@/templates/Main.vue"; -import { useEquipmentStore } from "@/stores/admin/unit/equipment/equipment"; +import { useRespiratoryWearerStore } from "@/stores/admin/unit/respiratoryWearer/respiratoryWearer"; import { useModalStore } from "@/stores/modal"; import Pagination from "@/components/Pagination.vue"; import { useAbilityStore } from "@/stores/ability"; -import type { EquipmentViewModel } from "../../../../viewmodels/admin/unit/equipment/equipment.models"; -import EquipmentListItem from "../../../../components/admin/unit/equipment/EquipmentListItem.vue"; +import type { RespiratoryWearerViewModel } from "@/viewmodels/admin/unit/respiratoryWearer/respiratoryWearer.models"; +import RespiratoryWearerListItem from "@/components/admin/unit/respiratoryWearer/RespiratoryWearerListItem.vue"; </script> <script lang="ts"> @@ -52,18 +51,22 @@ export default defineComponent({ }; }, computed: { - ...mapState(useEquipmentStore, ["equipments", "totalCount", "loading"]), + ...mapState(useRespiratoryWearerStore, ["respiratoryWearers", "totalCount", "loading"]), ...mapState(useAbilityStore, ["can"]), }, mounted() { - this.fetchEquipments(0, this.maxEntriesPerPage, "", true); + this.fetchRespiratoryWearers(0, this.maxEntriesPerPage, "", true); }, methods: { - ...mapActions(useEquipmentStore, ["fetchEquipments"]), + ...mapActions(useRespiratoryWearerStore, ["fetchRespiratoryWearers"]), ...mapActions(useModalStore, ["openModal"]), openCreateModal() { this.openModal( - markRaw(defineAsyncComponent(() => import("@/components/admin/unit/equipment/CreateEquipmentModal.vue"))) + markRaw( + defineAsyncComponent( + () => import("@/components/admin/unit/respiratoryWearer/CreateRespiratoryWearerModal.vue") + ) + ) ); }, }, diff --git a/src/views/admin/unit/vehicle/Vehicle.vue b/src/views/admin/unit/vehicle/Vehicle.vue index 538e2db..3998025 100644 --- a/src/views/admin/unit/vehicle/Vehicle.vue +++ b/src/views/admin/unit/vehicle/Vehicle.vue @@ -12,7 +12,6 @@ :totalCount="totalCount" :indicateLoading="loading == 'loading'" useSearch - useScanner @load-data="(offset, count, search) => fetchVehicles(offset, count, search)" @search="(search) => fetchVehicles(0, maxEntriesPerPage, search, true)" >