extend inspection_plan by wearable and enable optional interval

This commit is contained in:
Julian Krauser 2025-07-25 12:21:56 +02:00
parent 91ad11e20c
commit 583233b95e
6 changed files with 74 additions and 35 deletions

View file

@ -7,7 +7,12 @@
<p>{{ inspectionPlan.title }} - {{ inspectionPlan.related.type }}</p> <p>{{ inspectionPlan.title }} - {{ inspectionPlan.related.type }}</p>
</div> </div>
<div class="p-2"> <div class="p-2">
<p>Interval: {{ inspectionPlan.inspectionInterval }}</p> <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> </div>
</RouterLink> </RouterLink>
</template> </template>
@ -17,6 +22,7 @@ import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia"; import { mapState, mapActions } from "pinia";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";
import type { InspectionPlanViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models"; import type { InspectionPlanViewModel } from "@/viewmodels/admin/unit/inspection/inspectionPlan.models";
import { ExclamationTriangleIcon } from "@heroicons/vue/24/outline";
</script> </script>
<script lang="ts"> <script lang="ts">

View file

@ -52,6 +52,11 @@ export default defineComponent({
showQRCode: false as boolean, showQRCode: false as boolean,
}; };
}, },
watch: {
linkToScan() {
this.renderQRCode();
},
},
computed: { computed: {
...mapState(useScannerStore, ["inUse", "roomId", "results"]), ...mapState(useScannerStore, ["inUse", "roomId", "results"]),
linkToScan() { linkToScan() {
@ -59,25 +64,30 @@ export default defineComponent({
}, },
}, },
mounted() { mounted() {
QRCode.toDataURL(this.linkToScan, { this.renderQRCode();
width: 300,
margin: 2,
color: {
dark: "#000000",
light: "#FFFFFF",
},
errorCorrectionLevel: "M",
})
.then((res) => {
(this.$refs.qr as HTMLImageElement).src = res;
})
.catch(() => {});
}, },
methods: { methods: {
...mapActions(useScannerStore, ["startSession", "endSession", "removeElementFromResults"]), ...mapActions(useScannerStore, ["startSession", "endSession", "removeElementFromResults"]),
commit(c: string) { commit(c: string) {
this.$emit("code", c); 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) => {
console.log(err);
});
},
}, },
}); });
</script> </script>

View file

@ -37,12 +37,21 @@
v-model="inspectionPlan" v-model="inspectionPlan"
/> />
<div v-if="selectedInspectionPlan?.inspectionPoints.length == 0">
Dieser Prüfplan ist noch nicht fertig gestellt!
</div>
<div> <div>
<label for="nextInspection" class="flex flex-row justify-between"> <label for="nextInspection" class="flex flex-row gap-2 justify-between">
Nächste Prüfung (optional) - Intervall: {{ selectedInspectionPlan?.inspectionInterval ?? "xx" }} Nächste Prüfung
<span v-if="selectedInspectionPlan?.inspectionInterval">
- Intervall: {{ selectedInspectionPlan.inspectionInterval }}
</span>
<span v-else> (optional) </span>
<div class="grow"></div>
<InspectionTimeFormatExplainIcon /> <InspectionTimeFormatExplainIcon />
</label> </label>
<input id="nextInspection" type="date" /> <input id="nextInspection" type="date" :required="selectedInspectionPlan?.inspectionInterval != undefined" />
</div> </div>
<div> <div>

View file

@ -27,7 +27,8 @@
</div> </div>
<EquipmentTypeSearchSelect v-if="active == 'equipment'" title="Typ" v-model="selectedType" /> <EquipmentTypeSearchSelect v-if="active == 'equipment'" title="Typ" v-model="selectedType" />
<VehicleTypeSearchSelect v-else title="Typ" v-model="selectedType" /> <VehicleTypeSearchSelect v-else-if="active == 'vehicle'" title="Typ" v-model="selectedType" />
<WearableTypeSearchSelect v-else title="Typ" v-model="selectedType" />
<div> <div>
<label for="name">Bezeichnung</label> <label for="name">Bezeichnung</label>
@ -35,28 +36,26 @@
</div> </div>
<div> <div>
<label for="interval" class="flex flex-row justify-between"> <label for="interval" class="flex flex-row justify-between">
Intervall Prüfintervall (optional)
<InspectionTimeFormatExplainIcon /> <InspectionTimeFormatExplainIcon />
</label> </label>
<input <input
type="text" type="text"
id="interval" id="interval"
placeholder="<zahl>-(d|m|y) oder DD/MM oder DD/*" placeholder="<zahl>-(d|m|y) oder DD/MM oder DD/*"
required
pattern="^\d+-(d|m|y)$|^\d{2}/\d{2}$|^\d{2}/\*$" pattern="^\d+-(d|m|y)$|^\d{2}/\d{2}$|^\d{2}/\*$"
title="Eingabe muss im Format <zahl>-(d|m|y), DD/MM oder DD/* sein" title="Eingabe muss im Format <zahl>-(d|m|y), DD/MM oder DD/* sein"
/> />
</div> </div>
<div> <div>
<label for="remind" class="flex flex-row justify-between"> <label for="remind" class="flex flex-row justify-between">
Erinnerung vor Fälligkeit Erinnerung vor Fälligkeit (optional)
<InspectionTimeFormatExplainIcon /> <InspectionTimeFormatExplainIcon />
</label> </label>
<input <input
type="text" type="text"
id="remind" id="remind"
placeholder="<zahl>-(d|m|y) oder DD/MM oder DD/*" placeholder="<zahl>-(d|m|y) oder DD/MM oder DD/*"
required
pattern="^\d+-(d|m|y)$|^\d{2}/\d{2}$|^\d{2}/\*$" pattern="^\d+-(d|m|y)$|^\d{2}/\d{2}$|^\d{2}/\*$"
title="Eingabe muss im Format <zahl>-(d|m|y), DD/MM oder DD/* sein" title="Eingabe muss im Format <zahl>-(d|m|y), DD/MM oder DD/* sein"
/> />
@ -95,6 +94,7 @@ import { useEquipmentTypeStore } from "@/stores/admin/unit/equipmentType/equipme
import EquipmentTypeSearchSelect from "@/components/search/EquipmentTypeSearchSelect.vue"; import EquipmentTypeSearchSelect from "@/components/search/EquipmentTypeSearchSelect.vue";
import VehicleTypeSearchSelect from "@/components/search/VehicleTypeSearchSelect.vue"; import VehicleTypeSearchSelect from "@/components/search/VehicleTypeSearchSelect.vue";
import InspectionTimeFormatExplainIcon from "@/components/admin/unit/InspectionTimeFormatExplainIcon.vue"; import InspectionTimeFormatExplainIcon from "@/components/admin/unit/InspectionTimeFormatExplainIcon.vue";
import WearableTypeSearchSelect from "@/components/search/WearableTypeSearchSelect.vue";
</script> </script>
<script lang="ts"> <script lang="ts">
@ -104,7 +104,7 @@ export default defineComponent({
status: null as null | "loading" | { status: "success" | "failed"; reason?: string }, status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
timeout: null as any, timeout: null as any,
selectedType: "" as string, selectedType: "" as string,
active: "equipment" as "equipment" | "vehicle", active: "equipment" as "equipment" | "vehicle" | "wearable",
tabs: [ tabs: [
{ {
key: "equipment", key: "equipment",
@ -114,7 +114,11 @@ export default defineComponent({
key: "vehicle", key: "vehicle",
title: "Fahrzeug", title: "Fahrzeug",
}, },
] as Array<{ key: "equipment" | "vehicle"; title: string }>, {
key: "wearable",
title: "Kleidung",
},
] as Array<{ key: "equipment" | "vehicle" | "wearable"; title: string }>,
}; };
}, },
computed: { computed: {
@ -124,8 +128,8 @@ export default defineComponent({
let query = this.$route.query; let query = this.$route.query;
const queryType = Array.isArray(query.type) ? query.type[0] : query.type; const queryType = Array.isArray(query.type) ? query.type[0] : query.type;
const queryId = Array.isArray(query.id) ? query.id[0] : query.id; const queryId = Array.isArray(query.id) ? query.id[0] : query.id;
if (["vehicle", "equipment"].includes(queryType ?? "")) { if (["vehicle", "equipment", "wearable"].includes(queryType ?? "")) {
this.active = queryType as "equipment" | "vehicle"; this.active = queryType as "equipment" | "vehicle" | "wearable";
this.selectedType = queryId as string; this.selectedType = queryId as string;
} }
}, },

View file

@ -10,19 +10,28 @@
Intervall Intervall
<InspectionTimeFormatExplainIcon /> <InspectionTimeFormatExplainIcon />
</label> </label>
<input type="text" id="interval" :value="activeInspectionPlanObj.inspectionInterval" readonly /> <input
type="text"
id="interval"
:value="activeInspectionPlanObj.inspectionInterval"
readonly
placeholder="---"
/>
</div> </div>
<div> <div>
<label for="remind" class="flex flex-row justify-between"> <label for="remind" class="flex flex-row justify-between">
Erinnerung vor Fälligkeit Erinnerung vor Fälligkeit
<InspectionTimeFormatExplainIcon /> <InspectionTimeFormatExplainIcon />
</label> </label>
<input type="text" id="remind" :value="activeInspectionPlanObj.remindTime" readonly /> <input type="text" id="remind" :value="activeInspectionPlanObj.remindTime" readonly placeholder="---" />
</div> </div>
</div> </div>
<p>Prüfungspunkte:</p> <p>Prüfungspunkte:</p>
<div v-if="activeInspectionPlanObj?.inspectionPoints" class="flex flex-col gap-2 grow overflow-y-scroll"> <div v-if="activeInspectionPlanObj?.inspectionPoints" class="flex flex-col gap-2 grow overflow-y-scroll">
<small v-if="activeInspectionPlanObj?.inspectionPoints.length == 0">keine Prüfpunkte enthalten</small> <div v-if="activeInspectionPlanObj?.inspectionPoints.length == 0" class="flex flex-row gap-2 items-center">
<ExclamationTriangleIcon class="h-5 w-5 text-error" />
<p>keine Prüfpunkte enthalten</p>
</div>
<div <div
v-for="point in activeInspectionPlanObj?.inspectionPoints" v-for="point in activeInspectionPlanObj?.inspectionPoints"
class="flex flex-col h-fit w-full border border-primary rounded-md" class="flex flex-col h-fit w-full border border-primary rounded-md"
@ -50,6 +59,7 @@ import { mapActions, mapState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { useInspectionPlanStore } from "@/stores/admin/unit/inspectionPlan/inspectionPlan"; import { useInspectionPlanStore } from "@/stores/admin/unit/inspectionPlan/inspectionPlan";
import InspectionTimeFormatExplainIcon from "@/components/admin/unit/InspectionTimeFormatExplainIcon.vue"; import InspectionTimeFormatExplainIcon from "@/components/admin/unit/InspectionTimeFormatExplainIcon.vue";
import { ExclamationTriangleIcon } from "@heroicons/vue/24/outline";
</script> </script>
<script lang="ts"> <script lang="ts">

View file

@ -9,7 +9,7 @@
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<RouterLink <RouterLink
:to="{ name: 'admin-unit-inspection_plan-create', query: { type: 'vehicle', id: vehicleTypeId } }" :to="{ name: 'admin-unit-inspection_plan-create', query: { type: 'wearable', id: wearableTypeId } }"
:disabled="!can('read', 'unit', 'inspection_plan')" :disabled="!can('read', 'unit', 'inspection_plan')"
button button
primary primary
@ -24,7 +24,7 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapActions, mapState } from "pinia"; import { mapActions, mapState } from "pinia";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";
import { useVehicleTypeInspectionPlanStore } from "@/stores/admin/unit/vehicleType/inspectionPlan"; import { useWearableTypeInspectionPlanStore } from "@/stores/admin/unit/wearableType/inspectionPlan";
import TypeInspectionPlanListItem from "@/components/admin/unit/inspectionPlan/TypeInspectionPlanListItem.vue"; import TypeInspectionPlanListItem from "@/components/admin/unit/inspectionPlan/TypeInspectionPlanListItem.vue";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
</script> </script>
@ -32,19 +32,19 @@ import Spinner from "@/components/Spinner.vue";
<script lang="ts"> <script lang="ts">
export default defineComponent({ export default defineComponent({
props: { props: {
vehicleTypeId: String, wearableTypeId: String,
}, },
computed: { computed: {
...mapState(useAbilityStore, ["can"]), ...mapState(useAbilityStore, ["can"]),
...mapState(useVehicleTypeInspectionPlanStore, ["inspectionPlans", "loading"]), ...mapState(useWearableTypeInspectionPlanStore, ["inspectionPlans", "loading"]),
}, },
mounted() { mounted() {
this.fetchItem(); this.fetchItem();
}, },
methods: { methods: {
...mapActions(useVehicleTypeInspectionPlanStore, ["fetchInspectionPlanForVehicleType"]), ...mapActions(useWearableTypeInspectionPlanStore, ["fetchInspectionPlanForWearableType"]),
fetchItem() { fetchItem() {
this.fetchInspectionPlanForVehicleType(); this.fetchInspectionPlanForWearableType();
}, },
}, },
}); });