Merge branch 'main' of https://github.com/VAR-Virtual-Air-Rescue/var-monorepo
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { prisma } from "@repo/db";
|
import { HpgValidationState, Prisma, prisma } from "@repo/db";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { io } from "../index";
|
import { io } from "../index";
|
||||||
import { sendNtfyMission } from "modules/ntfy";
|
import { sendNtfyMission } from "modules/ntfy";
|
||||||
@@ -203,4 +203,75 @@ router.post("/:id/send-alert", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/:id/validate-hpg", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const mission = await prisma.mission.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: Number(id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeAircraftinMission = await prisma.connectedAircraft.findFirst({
|
||||||
|
where: {
|
||||||
|
stationId: {
|
||||||
|
in: mission?.missionStationIds,
|
||||||
|
},
|
||||||
|
posH145active: true,
|
||||||
|
logoutTime: null,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Station: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/* if (activeAircraftinMission.length === 0) {
|
||||||
|
res.status(400).json({ error: "No active aircraft in mission" });
|
||||||
|
return;
|
||||||
|
} */
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: "HPG validation started",
|
||||||
|
});
|
||||||
|
|
||||||
|
io.to(`desktop:${activeAircraftinMission}`).emit(
|
||||||
|
"hpg-validation",
|
||||||
|
{
|
||||||
|
hpgMissionType: mission?.hpgMissionString,
|
||||||
|
lat: mission?.addressLat,
|
||||||
|
lng: mission?.addressLng,
|
||||||
|
},
|
||||||
|
async (result: {
|
||||||
|
state: HpgValidationState;
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
}) => {
|
||||||
|
console.log("response from user:", result);
|
||||||
|
|
||||||
|
const newMission = await prisma.mission.update({
|
||||||
|
where: { id: Number(id) },
|
||||||
|
data: {
|
||||||
|
// save position of new mission
|
||||||
|
addressLat:
|
||||||
|
result.state === "POSITION_AMANDED"
|
||||||
|
? result.lat
|
||||||
|
: mission.addressLat,
|
||||||
|
addressLng:
|
||||||
|
result.state === "POSITION_AMANDED"
|
||||||
|
? result.lng
|
||||||
|
: mission.addressLng,
|
||||||
|
hpgLocationLat: result.lat,
|
||||||
|
hpgLocationLng: result.lng,
|
||||||
|
hpgValidationState: result.state,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
io.to("dispatchers").emit("update-mission", newMission);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.json({ error: (error as Error).message || "Failed to validate HPG" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,13 +1,34 @@
|
|||||||
import { User } from "@repo/db";
|
import { prisma, User } from "@repo/db";
|
||||||
import { Socket, Server } from "socket.io";
|
import { Socket, Server } from "socket.io";
|
||||||
|
|
||||||
export const handleConnectDesktop = (socket: Socket, io: Server) => () => {
|
export const handleConnectDesktop = (socket: Socket, io: Server) => () => {
|
||||||
const user = socket.data.user as User;
|
const user = socket.data.user as User;
|
||||||
console.log("connect-desktop", user.publicId);
|
|
||||||
|
const connectedAircraft = prisma.connectedAircraft.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
logoutTime: null,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Station: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const conenctedDispatchers = prisma.connectedDispatcher.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
logoutTime: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
socket.join(`user:${user.id}`);
|
socket.join(`user:${user.id}`);
|
||||||
socket.join(`desktop:${user.id}`);
|
socket.join(`desktop:${user.id}`);
|
||||||
|
|
||||||
socket.on("ptt", (data) => {
|
socket.on("ptt", (data) => {
|
||||||
socket.to(`user:${user.id}`).emit("ptt", data);
|
socket.to(`user:${user.id}`).emit("ptt", data);
|
||||||
|
socket.to("pilots").emit("other-ptt", {
|
||||||
|
publicUser: user.publicId,
|
||||||
|
source:
|
||||||
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { ROOMS } from "_data/livekitRooms";
|
|||||||
|
|
||||||
export const Audio = () => {
|
export const Audio = () => {
|
||||||
const connection = usePilotConnectionStore();
|
const connection = usePilotConnectionStore();
|
||||||
|
const [showSource, setShowSource] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isTalking,
|
isTalking,
|
||||||
toggleTalking,
|
toggleTalking,
|
||||||
@@ -31,9 +33,16 @@ export const Audio = () => {
|
|||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
room,
|
room,
|
||||||
message,
|
message,
|
||||||
|
source,
|
||||||
} = useAudioStore();
|
} = useAudioStore();
|
||||||
const [selectedRoom, setSelectedRoom] = useState<string>("LST_01");
|
const [selectedRoom, setSelectedRoom] = useState<string>("LST_01");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShowSource(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowSource(false);
|
||||||
|
}, 2000);
|
||||||
|
}, [source, isTalking]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const joinRoom = async () => {
|
const joinRoom = async () => {
|
||||||
if (connection.status != "connected") return;
|
if (connection.status != "connected") return;
|
||||||
@@ -55,6 +64,9 @@ export const Audio = () => {
|
|||||||
{state === "error" && (
|
{state === "error" && (
|
||||||
<div className="h-4 flex items-center">{message}</div>
|
<div className="h-4 flex items-center">{message}</div>
|
||||||
)}
|
)}
|
||||||
|
{showSource && source && (
|
||||||
|
<div className="h-4 flex items-center">{source}</div>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (state === "connected") toggleTalking();
|
if (state === "connected") toggleTalking();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { toast } from "react-hot-toast";
|
|||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactNode, useEffect, useState } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
import { dispatchSocket } from "dispatch/socket";
|
import { dispatchSocket } from "dispatch/socket";
|
||||||
|
import { Mission } from "@repo/db";
|
||||||
|
|
||||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
@@ -22,7 +23,7 @@ export function QueryProvider({ children }: { children: ReactNode }) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const invalidateMission = () => {
|
const invalidateMission = (mission: Mission) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["missions"],
|
queryKey: ["missions"],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
if (!room) return;
|
if (!room) return;
|
||||||
room.localParticipant.setMicrophoneEnabled(!isTalking);
|
room.localParticipant.setMicrophoneEnabled(!isTalking);
|
||||||
|
|
||||||
set((state) => ({ isTalking: !state.isTalking }));
|
set((state) => ({ isTalking: !state.isTalking, source: "web-app" }));
|
||||||
},
|
},
|
||||||
connect: async (roomName) => {
|
connect: async (roomName) => {
|
||||||
set({ state: "connecting" });
|
set({ state: "connecting" });
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ export const MissionForm = () => {
|
|||||||
});
|
});
|
||||||
const { missionFormValues, setOpen } = usePannelStore((state) => state);
|
const { missionFormValues, setOpen } = usePannelStore((state) => state);
|
||||||
|
|
||||||
|
const missionInfoText = form.watch("missionAdditionalInfo");
|
||||||
|
const hpgMissionString = form.watch("hpgMissionString");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session.data?.user.id) {
|
if (session.data?.user.id) {
|
||||||
form.setValue("createdUserId", session.data.user.id);
|
form.setValue("createdUserId", session.data.user.id);
|
||||||
@@ -178,7 +181,7 @@ export const MissionForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...form.register("missionAdditionalInfo")}
|
{...form.register("addressAdditionalInfo")}
|
||||||
placeholder="Zusätzliche Adressinformationen"
|
placeholder="Zusätzliche Adressinformationen"
|
||||||
className="input input-primary input-bordered w-full mt-4"
|
className="input input-primary input-bordered w-full mt-4"
|
||||||
/>
|
/>
|
||||||
@@ -342,10 +345,16 @@ export const MissionForm = () => {
|
|||||||
onClick={form.handleSubmit(
|
onClick={form.handleSubmit(
|
||||||
async (mission: MissionOptionalDefaults) => {
|
async (mission: MissionOptionalDefaults) => {
|
||||||
try {
|
try {
|
||||||
|
const hpgSzenario = mission.hpgMissionString?.split(":")[0];
|
||||||
const newMission = await editMissionMutation.mutateAsync({
|
const newMission = await editMissionMutation.mutateAsync({
|
||||||
id: Number(editingMissionId),
|
id: Number(editingMissionId),
|
||||||
mission:
|
mission: {
|
||||||
mission as unknown as Partial<Prisma.MissionUpdateInput>,
|
...(mission as unknown as Prisma.MissionCreateInput),
|
||||||
|
missionAdditionalInfo:
|
||||||
|
!mission.missionAdditionalInfo.length && hpgSzenario
|
||||||
|
? `HPG-Szenario: ${hpgSzenario}`
|
||||||
|
: mission.missionAdditionalInfo,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
toast.success(
|
toast.success(
|
||||||
`Einsatz ${newMission.id} erfolgreich aktualisiert`,
|
`Einsatz ${newMission.id} erfolgreich aktualisiert`,
|
||||||
@@ -372,14 +381,18 @@ export const MissionForm = () => {
|
|||||||
onClick={form.handleSubmit(
|
onClick={form.handleSubmit(
|
||||||
async (mission: MissionOptionalDefaults) => {
|
async (mission: MissionOptionalDefaults) => {
|
||||||
try {
|
try {
|
||||||
|
const hpgSzenario =
|
||||||
|
mission.hpgMissionString?.split(":")[0];
|
||||||
const newMission =
|
const newMission =
|
||||||
await createMissionMutation.mutateAsync(
|
await createMissionMutation.mutateAsync({
|
||||||
mission as unknown as Prisma.MissionCreateInput,
|
...(mission as unknown as Prisma.MissionCreateInput),
|
||||||
);
|
missionAdditionalInfo:
|
||||||
|
!mission.missionAdditionalInfo.length && hpgSzenario
|
||||||
|
? `HPG-Szenario: ${hpgSzenario}`
|
||||||
|
: mission.missionAdditionalInfo,
|
||||||
|
});
|
||||||
await sendAlertMutation.mutateAsync(newMission.id);
|
await sendAlertMutation.mutateAsync(newMission.id);
|
||||||
setSeachOSMElements([]); // Reset search elements
|
setSeachOSMElements([]); // Reset search elements
|
||||||
toast.success(`Einsatz ${newMission.id} erstellt`);
|
|
||||||
// TODO: Einsatz alarmieren
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -397,10 +410,16 @@ export const MissionForm = () => {
|
|||||||
onClick={form.handleSubmit(
|
onClick={form.handleSubmit(
|
||||||
async (mission: MissionOptionalDefaults) => {
|
async (mission: MissionOptionalDefaults) => {
|
||||||
try {
|
try {
|
||||||
|
const hpgSzenario =
|
||||||
|
mission.hpgMissionString?.split(":")[0];
|
||||||
const newMission =
|
const newMission =
|
||||||
await createMissionMutation.mutateAsync(
|
await createMissionMutation.mutateAsync({
|
||||||
mission as unknown as Prisma.MissionCreateInput,
|
...(mission as unknown as Prisma.MissionCreateInput),
|
||||||
);
|
missionAdditionalInfo:
|
||||||
|
!mission.missionAdditionalInfo.length && hpgSzenario
|
||||||
|
? `HPG-Szenario: ${hpgSzenario}`
|
||||||
|
: mission.missionAdditionalInfo,
|
||||||
|
});
|
||||||
setSeachOSMElements([]); // Reset search elements
|
setSeachOSMElements([]); // Reset search elements
|
||||||
|
|
||||||
toast.success(`Einsatz ${newMission.publicId} erstellt`);
|
toast.success(`Einsatz ${newMission.publicId} erstellt`);
|
||||||
|
|||||||
Binary file not shown.
@@ -8,6 +8,7 @@ model Mission {
|
|||||||
addressStreet String?
|
addressStreet String?
|
||||||
addressCity String?
|
addressCity String?
|
||||||
addressZip String?
|
addressZip String?
|
||||||
|
addressAdditionalInfo String?
|
||||||
addressOSMways Json[] @default([])
|
addressOSMways Json[] @default([])
|
||||||
missionKeywordCategory String?
|
missionKeywordCategory String?
|
||||||
missionKeywordName String?
|
missionKeywordName String?
|
||||||
@@ -23,6 +24,7 @@ model Mission {
|
|||||||
hpgPoliceState HpgState?
|
hpgPoliceState HpgState?
|
||||||
hpgLocationLat Float? @default(0)
|
hpgLocationLat Float? @default(0)
|
||||||
hpgLocationLng Float? @default(0)
|
hpgLocationLng Float? @default(0)
|
||||||
|
hpgValidationState HpgValidationState @default(NOT_VALIDATED)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
createdUserId String
|
createdUserId String
|
||||||
@@ -75,3 +77,14 @@ enum HpgState {
|
|||||||
arrived
|
arrived
|
||||||
onway
|
onway
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum HpgValidationState {
|
||||||
|
NOT_VALIDATED
|
||||||
|
PENDING
|
||||||
|
VALID
|
||||||
|
INVALID
|
||||||
|
HPG_DISCONNECT
|
||||||
|
HPG_BUSY
|
||||||
|
HPG_INVALID_MISSION
|
||||||
|
POSITION_AMANDED
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user