added mission fields

This commit is contained in:
PxlLoewe
2025-04-23 19:01:26 -07:00
parent 3761943cc2
commit f6e4449f68
12 changed files with 198 additions and 113 deletions

View File

@@ -1,4 +1,4 @@
import { Prisma } from "@repo/db"; import { Mission, Prisma } from "@repo/db";
import { MissionOptionalDefaults } from "@repo/db/zod"; import { MissionOptionalDefaults } from "@repo/db/zod";
import { create } from "zustand"; import { create } from "zustand";
@@ -6,29 +6,29 @@ interface MissionStore {
missions: MissionOptionalDefaults[]; missions: MissionOptionalDefaults[];
setMissions: (missions: MissionOptionalDefaults[]) => void; setMissions: (missions: MissionOptionalDefaults[]) => void;
getMissions: () => Promise<undefined>; getMissions: () => Promise<undefined>;
createMission: (mission: MissionOptionalDefaults) => Promise<Mission>;
setMission: (mission: MissionOptionalDefaults) => void; setMission: (mission: MissionOptionalDefaults) => void;
} }
export const useMissionsStore = create<MissionStore>((set) => ({ export const useMissionsStore = create<MissionStore>((set) => ({
missions: [ missions: [],
{
state: "draft",
id: "01250325",
addressLat: 52.520008,
addressLng: 13.404954,
addressStreet: "Alexanderplatz",
addressCity: "Berlin",
addressZip: "10178",
missionAdditionalInfo: "Additional info",
missionCategory: "AB_Atmung",
missionKeyword: "Zunehmende Beschwerden",
missionSummary: "AB1_0",
missionPatientInfo: "M/10",
},
],
setMissions: (missions) => set({ missions }), setMissions: (missions) => set({ missions }),
createMission: async (mission) => {
const res = await fetch("/api/mission", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(mission),
});
if (!res.ok) return undefined;
const data = await res.json();
set((state) => ({ missions: [...state.missions, data] }));
return data;
},
getMissions: async () => { getMissions: async () => {
const res = await fetch("/api/mission", { const res = await fetch("/api/mission", {
method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -62,3 +62,10 @@ export const useMissionsStore = create<MissionStore>((set) => ({
} }
}), }),
})); }));
useMissionsStore
.getState()
.getMissions()
.then(() => {
console.log("Missions loaded");
});

View File

@@ -1,14 +1,24 @@
import { Prisma, prisma } from "@repo/db"; import { Prisma, prisma } from "@repo/db";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
export const GET = (req: NextRequest) => { export const POST = async (req: NextRequest) => {
const filter = req.nextUrl.searchParams.get("filter") as console.log(req.body);
| Prisma.MissionWhereInput const body = await req.json();
| undefined; console.log(body);
const missions = prisma.mission.findMany({ const missions = await prisma.mission.findMany({
where: filter, where: (body.filter as Prisma.MissionWhereInput) || {},
}); });
return NextResponse.json(missions); return NextResponse.json(missions);
}; };
export const PUT = async (req: NextRequest) => {
const body = await req.json();
const newMission = await prisma.mission.create({
data: body,
});
return NextResponse.json(newMission);
};

View File

@@ -1,11 +1,13 @@
"use client"; "use client";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { MissionSchema } from "@repo/db/zod";
import { BellRing, BookmarkPlus, Trash2 } from "lucide-react"; import { BellRing, BookmarkPlus, Trash2 } from "lucide-react";
import { Select } from "_components/Select"; import { Select } from "_components/Select";
import { Keyword, Station } from "@repo/db";
import { getKeywords, getStations } from "dispatch/_components/pannel/action";
import { MissionCreateInputSchema } from "@repo/db/zod";
const clearBtn = () => { const clearBtn = () => {
return ( return (
@@ -15,59 +17,35 @@ const clearBtn = () => {
); );
}; };
const missionFormSchema = MissionSchema.pick({ export const MissionForm = () => {
addressLat: true, type MissionFormValues = z.infer<typeof MissionCreateInputSchema>;
addressLng: true,
addressStreet: true,
addressCity: true,
addressZip: true,
missionCategory: true,
missionKeyword: true,
missionAdditionalInfo: true,
missionPatientInfo: true,
});
type MissionFormValues = z.infer<typeof missionFormSchema>;
const dummyRettungsmittel = [
"RTW",
"Feuerwehr",
"Polizei",
"Christoph 31",
"Christoph 100",
"Christoph Berlin",
"Christophorus 1",
];
export const MissionForm: React.FC = () => {
const [missionCategory, setMissionCategory] = useState<"PRIMÄR" | "SEKUNDÄR">(
"PRIMÄR",
);
const [missionKeyword, setMissionKeyword] = useState<
"AB_ATMUNG" | "C_BLUTUNG"
>("AB_ATMUNG");
const [missionType, setMissionType] = useState<"typ1" | "typ2" | "typ3">(
"typ1",
);
const [selectedRettungsmittel, setSelectedRettungsmittel] = useState<
{ label: string; value: string }[]
>([]);
const form = useForm<MissionFormValues>({ const form = useForm<MissionFormValues>({
resolver: zodResolver(missionFormSchema), resolver: zodResolver(MissionCreateInputSchema),
defaultValues: { defaultValues: {},
addressLat: 0,
addressLng: 0,
missionCategory: "PRIMÄR",
},
}); });
const [stations, setStations] = useState<Station[]>([]);
const [keywords, setKeywords] = useState<Keyword[]>([]);
useEffect(() => {
console.log("useEffect");
getKeywords().then((data) => {
setKeywords(data);
});
getStations().then((data) => {
setStations(data);
});
}, []);
const onSubmit = (data: MissionFormValues) => { const onSubmit = (data: MissionFormValues) => {
console.log({ console.log({
...data, ...data,
rettungsmittel: selectedRettungsmittel.map((item) => item.value),
}); });
}; };
console.log(form.formState.errors);
return ( return (
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{/* Koorinaten Section */} {/* Koorinaten Section */}
@@ -78,13 +56,13 @@ export const MissionForm: React.FC = () => {
type="text" type="text"
{...form.register("addressLat")} {...form.register("addressLat")}
className="input input-sm input-neutral input-bordered w-full" className="input input-sm input-neutral input-bordered w-full"
readOnly disabled
/> />
<input <input
type="text" type="text"
{...form.register("addressLng")} {...form.register("addressLng")}
className="input input-sm input-neutral input-bordered w-full" className="input input-sm input-neutral input-bordered w-full"
readOnly disabled
/> />
</div> </div>
</div> </div>
@@ -128,9 +106,9 @@ export const MissionForm: React.FC = () => {
label={""} label={""}
isMulti isMulti
form={form} form={form}
options={dummyRettungsmittel.map((key, val) => ({ options={stations.map((s) => ({
label: key, label: s.bosCallsign,
value: val, value: s.id,
}))} }))}
/> />
</div> </div>
@@ -139,30 +117,32 @@ export const MissionForm: React.FC = () => {
<div className="form-control"> <div className="form-control">
<h2 className="text-lg font-bold mb-2">Einsatzdaten</h2> <h2 className="text-lg font-bold mb-2">Einsatzdaten</h2>
<select <select
{...form.register("missionCategory")} {...form.register("type")}
className="select select-primary select-bordered w-full mb-4" className="select select-primary select-bordered w-full mb-4"
onChange={(e) =>
setMissionCategory(e.target.value as "PRIMÄR" | "SEKUNDÄR")
}
> >
<option value="PRIMÄR">PRIMÄR</option> <option value="primär">PRIMÄR</option>
<option value="SEKUNDÄR">SEKUNDÄR</option> <option value="sekundär">SEKUNDÄR</option>
</select> </select>
{form.watch("type") === "primär" && (
<>
<select <select
{...form.register("missionKeyword")} {...form.register("missionKeyword")}
className="select select-primary select-bordered w-full mb-4" className="select select-primary select-bordered w-full mb-4"
onChange={(e) => onChange={(e) =>
setMissionKeyword(e.target.value as "AB_ATMUNG" | "C_BLUTUNG") form.setValue("missionKeyword", e.target.value as string)
} }
> >
<option value="AB_ATMUNG">AB_ATMUNG</option> {keywords.map((keyword) => (
<option value="C_BLUTUNG">C_BLUTUNG</option> <option key={keyword.id} value={keyword.name}>
{keyword.name}
</option>
))}
</select> </select>
<select <select
/* {...form.register("missionKeyword")} */ /* {...form.register("missionKeyword")} */
className="select select-primary select-bordered w-full mb-4" className="select select-primary select-bordered w-full mb-4"
onChange={(e) => onChange={(e) =>
setMissionType(e.target.value as "typ1" | "typ2" | "typ3") form.setValue("missionKeyword", e.target.value as string)
} }
> >
<option defaultChecked disabled value=""> <option defaultChecked disabled value="">
@@ -172,12 +152,14 @@ export const MissionForm: React.FC = () => {
<option value="typ2">typ2</option> <option value="typ2">typ2</option>
<option value="typ3">typ3</option> <option value="typ3">typ3</option>
</select> </select>
</>
)}
<textarea <textarea
{...form.register("missionAdditionalInfo")} {...form.register("missionAdditionalInfo")}
placeholder="Einsatzinformationen" placeholder="Einsatzinformationen"
className="textarea textarea-primary textarea-bordered w-full mb-4" className="textarea textarea-primary textarea-bordered w-full mb-4"
/> />
{missionCategory === "SEKUNDÄR" && ( {form.watch("type") === "sekundär" && (
<input <input
type="text" type="text"
placeholder="Zielkrankenhaus" placeholder="Zielkrankenhaus"

View File

@@ -0,0 +1,15 @@
"use server";
import { prisma } from "@repo/db";
export const getKeywords = async () => {
const keywords = prisma.keyword.findMany();
return keywords;
};
export const getStations = async () => {
const stations = await prisma.station.findMany();
console.log(stations);
return stations;
};

Binary file not shown.

View File

@@ -0,0 +1,24 @@
import { Station, User } from "../../generated/client";
export interface MissionStationLog {
type: "station-log";
auto: true;
data: {
stationId: string;
oldFMSstatus: string;
newFMSstatus: string;
station: Station;
user: User;
};
}
export interface MissionMessageLog {
type: "message-log";
auto: false;
data: {
message: string;
user: User;
};
}
export type MissionLog = MissionStationLog | MissionMessageLog;

View File

@@ -0,0 +1,8 @@
export interface MissionVehicleLog {
wayID: string;
tags: string[];
nodes: {
lat: number;
lon: number;
};
}

View File

@@ -17,6 +17,6 @@ model Keyword {
abreviation String abreviation String
name String name String
description String? description String?
hpgMissionsType String[] hpgMissionTypes String[]
// relations: // relations:
} }

View File

@@ -1,21 +1,55 @@
model Mission { model Mission {
id String @id @default(uuid()) id Int @id @default(autoincrement())
type missionType @default(primär)
state MissionState @default(draft) state MissionState @default(draft)
addressLat Float addressLat Float
addressLng Float addressLng Float
addressStreet String addressStreet String?
addressCity String addressCity String?
addressZip String addressZip String?
missionCategory String addressOSMways Json[] @default([])
missionKeyword String missionCategory String?
missionSummary String missionKeyword String?
missionSummary String?
missionPatientInfo String missionPatientInfo String
missionAdditionalInfo String missionAdditionalInfo String
missionStationIds String[]
missionLog Json[] @default([])
hpgAmbulanceState HpgState? @default(ready) hpgAmbulanceState HpgState? @default(ready)
hpgFireEngineState HpgState? @default(ready) hpgFireEngineState HpgState? @default(ready)
hpgPoliceState HpgState? @default(ready) hpgPoliceState HpgState? @default(ready)
hpgLocationLat Float? @default(0) hpgLocationLat Float? @default(0)
hpgLocationLng Float? @default(0) hpgLocationLng Float? @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdUserId String
// relations:
/**
* /**
* /**
* /**
* /**
* /**
* createdUser User @relation(fields: [createdUserId], references: [id])
*/
MissionsOnStations MissionsOnStations[]
}
model MissionsOnStations {
missionId Int
stationId Int
// relations:
Mission Mission @relation(fields: [missionId], references: [id])
Station Station @relation(fields: [stationId], references: [id])
@@id([missionId, stationId])
}
enum missionType {
primär
sekundär
} }
enum MissionState { enum MissionState {

View File

@@ -32,4 +32,6 @@ model Station {
longitude Float longitude Float
atcCallsign String atcCallsign String
hideRangeRings Boolean hideRangeRings Boolean
MissionsOnStations MissionsOnStations[]
} }

View File

@@ -43,6 +43,9 @@ model User {
EventAppointment EventAppointment[] EventAppointment EventAppointment[]
SentMessages ChatMessage[] @relation("SentMessages") SentMessages ChatMessage[] @relation("SentMessages")
ReceivedMessages ChatMessage[] @relation("ReceivedMessages") ReceivedMessages ChatMessage[] @relation("ReceivedMessages")
/**
* Missions Mission[]
*/
@@map(name: "users") @@map(name: "users")
} }

View File

@@ -18,7 +18,7 @@ export const config = [
turbo: turboPlugin, turbo: turboPlugin,
}, },
rules: { rules: {
"turbo/no-undeclared-env-vars": ["error", { allowList: true }], "turbo/no-undeclared-env-vars": ["error", { allowList: [] }],
}, },
}, },
{ {