HPG Warnung in Dispatch Settings, Status Notification

This commit is contained in:
PxlLoewe
2026-02-08 13:03:11 +01:00
parent 8340c2408c
commit aded6d1492
9 changed files with 78 additions and 25 deletions

View File

@@ -45,7 +45,7 @@ router.post("/rename", async (req: Request, res: Response) => {
console.log(`Member ${member.id} renamed to ${newName}`); console.log(`Member ${member.id} renamed to ${newName}`);
res.status(200).json({ message: "Member renamed successfully" }); res.status(200).json({ message: "Member renamed successfully" });
} catch (error) { } catch (error) {
console.error("Error renaming member:", error); console.error("Error renaming member:", (error as Error).message);
res.status(500).json({ error: "Failed to rename member" }); res.status(500).json({ error: "Failed to rename member" });
} }
}); });
@@ -84,7 +84,7 @@ const handleRoleChange = (action: "add" | "remove") => async (req: Request, res:
const result = await changeMemberRoles(memberId, roleIds, action); const result = await changeMemberRoles(memberId, roleIds, action);
res.status(200).json(result); res.status(200).json(result);
} catch (error) { } catch (error) {
console.error(`Error ${action}ing roles:`, error); console.error(`Error ${action}ing roles:`, (error as Error).message);
res.status(500).json({ error: `Failed to ${action} roles` }); res.status(500).json({ error: `Failed to ${action} roles` });
} }
}; };

View File

@@ -1,4 +1,4 @@
import axios from "axios"; import axios, { AxiosError } from "axios";
const discordAxiosClient = axios.create({ const discordAxiosClient = axios.create({
baseURL: process.env.CORE_SERVER_URL, baseURL: process.env.CORE_SERVER_URL,
@@ -11,7 +11,10 @@ export const renameMember = async (memberId: string, newName: string) => {
newName, newName,
}) })
.catch((error) => { .catch((error) => {
console.error("Error renaming member:", error); console.error(
"Error renaming member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };
@@ -22,7 +25,10 @@ export const addRolesToMember = async (memberId: string, roleIds: string[]) => {
roleIds, roleIds,
}) })
.catch((error) => { .catch((error) => {
console.error("Error adding roles to member:", error); console.error(
"Error adding roles to member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };
@@ -33,7 +39,10 @@ export const removeRolesFromMember = async (memberId: string, roleIds: string[])
roleIds, roleIds,
}) })
.catch((error) => { .catch((error) => {
console.error("Error removing roles from member:", error); console.error(
"Error removing roles from member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };
@@ -43,6 +52,9 @@ export const sendReportEmbed = async (reportId: number) => {
reportId, reportId,
}) })
.catch((error) => { .catch((error) => {
console.error("Error removing roles from member:", error); console.error(
"Error removing roles from member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };

View File

@@ -154,6 +154,15 @@ router.post("/:id/send-sds-message", async (req, res) => {
}, },
}); });
const user = await prisma.user.findFirst({
where: { publicId: sdsMessage.data.user.publicId, firstname: sdsMessage.data.user.firstname },
});
if (!user) {
res.status(404).json({ error: "User not found" });
return;
}
io.to( io.to(
sdsMessage.data.direction === "to-lst" ? "dispatchers" : `station:${sdsMessage.data.stationId}`, sdsMessage.data.direction === "to-lst" ? "dispatchers" : `station:${sdsMessage.data.stationId}`,
).emit(sdsMessage.data.direction === "to-lst" ? "notification" : "sds-status", { ).emit(sdsMessage.data.direction === "to-lst" ? "notification" : "sds-status", {
@@ -163,6 +172,7 @@ router.post("/:id/send-sds-message", async (req, res) => {
data: { data: {
aircraftId: parseInt(id), aircraftId: parseInt(id),
stationId: sdsMessage.data.stationId, stationId: sdsMessage.data.stationId,
userId: user.id,
}, },
} as NotificationPayload); } as NotificationPayload);

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { GearIcon } from "@radix-ui/react-icons"; import { GearIcon } from "@radix-ui/react-icons";
import { SettingsIcon, Volume2 } from "lucide-react"; import { Info, SettingsIcon, Volume2 } from "lucide-react";
import MicVolumeBar from "_components/MicVolumeIndication"; import MicVolumeBar from "_components/MicVolumeIndication";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { editUserAPI, getUserAPI } from "_querys/user"; import { editUserAPI, getUserAPI } from "_querys/user";
@@ -27,7 +27,6 @@ export const SettingsBtn = () => {
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["user", session.data?.user.id] }); await queryClient.invalidateQueries({ queryKey: ["user", session.data?.user.id] });
}, },
}); });
useEffect(() => { useEffect(() => {
@@ -82,10 +81,14 @@ export const SettingsBtn = () => {
useEffect(() => { useEffect(() => {
const setDevices = async () => { const setDevices = async () => {
if (typeof navigator !== "undefined" && navigator.mediaDevices?.enumerateDevices) { if (typeof navigator !== "undefined" && navigator.mediaDevices?.enumerateDevices) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }); const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
setInputDevices(devices.filter((d) => d.kind === "audioinput")); setInputDevices(devices.filter((d) => d.kind === "audioinput"));
stream.getTracks().forEach((track) => track.stop()); stream.getTracks().forEach((track) => track.stop());
} catch (error) {
console.error("Error accessing media devices.", error);
}
} }
}; };
@@ -214,7 +217,18 @@ export const SettingsBtn = () => {
setSettingsPartial({ useHPGAsDispatcher: e.target.checked }); setSettingsPartial({ useHPGAsDispatcher: e.target.checked });
}} }}
/> />
HPG als Disponent verwenden HPG Validierung verwenden{" "}
<div
className="tooltip tooltip-warning"
data-tip="Achtung! Mit der Client Version v2.0.1.0 werden Einsätze auch ohne Validierung an die
HPG gesendet. Die Validierung über die HPG kann zu Verzögerungen bei der
Einsatzübermittlung führen, insbesondere wenn das HPG script den Einsatz nicht sofort
validieren kann. Es wird empfohlen, diese Option nur zu aktivieren, wenn es Probleme
mit der Einsatzübermittlung gibt oder wenn die HPG Validierung ausdrücklich gewünscht
wird."
>
<Info className="text-error" size={16} />
</div>
</div> </div>
<div className="modal-action flex justify-between"> <div className="modal-action flex justify-between">

View File

@@ -78,6 +78,7 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) =>
}); });
}, },
}); });
console.log("Audio Room:", audioRoom, participants, livekitUser, event);
useEffect(() => { useEffect(() => {
let soundRef: React.RefObject<HTMLAudioElement | null> | null = null; let soundRef: React.RefObject<HTMLAudioElement | null> | null = null;
@@ -113,7 +114,6 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) =>
}; };
}, [event.status, livekitUser?.roomName, audioRoom, t.id]); }, [event.status, livekitUser?.roomName, audioRoom, t.id]);
console.log(connectedAircraft, station);
if (!connectedAircraft || !station || !session.data) return null; if (!connectedAircraft || !station || !session.data) return null;
return ( return (
<BaseNotification> <BaseNotification>

View File

@@ -201,11 +201,23 @@ export const useAudioStore = create<TalkState>((set, get) => ({
}); });
} }
const inputStream = await navigator.mediaDevices.getUserMedia({ let inputStream = await navigator.mediaDevices
.getUserMedia({
audio: { audio: {
deviceId: get().settings.micDeviceId ?? undefined, deviceId: get().settings.micDeviceId ?? undefined,
}, },
})
.catch((e) => {
console.error("Konnte das Audio-Gerät nicht öffnen:", e);
return null;
}); });
if (!inputStream) {
inputStream = await navigator.mediaDevices.getUserMedia({ audio: true }).catch((e) => {
console.error("Konnte das Audio-Gerät nicht öffnen:", e);
return null;
});
}
if (!inputStream) throw new Error("Konnte das Audio-Gerät nicht öffnen");
// Funk-Effekt anwenden // Funk-Effekt anwenden
const radioStream = getRadioStream(inputStream, get().settings.micVolume); const radioStream = getRadioStream(inputStream, get().settings.micVolume);
if (!radioStream) throw new Error("Konnte Funkstream nicht erzeugen"); if (!radioStream) throw new Error("Konnte Funkstream nicht erzeugen");

View File

@@ -5,6 +5,7 @@ import { renderPasswordChanged } from "./mail-templates/PasswordChanged";
import { renderVerificationCode } from "./mail-templates/ConfirmEmail"; import { renderVerificationCode } from "./mail-templates/ConfirmEmail";
import { renderBannNotice } from "modules/mail-templates/Bann"; import { renderBannNotice } from "modules/mail-templates/Bann";
import { renderTimeBanNotice } from "modules/mail-templates/TimeBann"; import { renderTimeBanNotice } from "modules/mail-templates/TimeBann";
import Mail from "nodemailer/lib/mailer";
let transporter: nodemailer.Transporter | null = null; let transporter: nodemailer.Transporter | null = null;
@@ -23,8 +24,9 @@ const initTransporter = () => {
pass: process.env.MAIL_PASSWORD, pass: process.env.MAIL_PASSWORD,
}, },
}); });
transporter.on("error", (err) => { transporter.on("error", (err) => {
console.error("Mail occurred:", err); console.error("Mail error:", err);
}); });
transporter.on("idle", () => { transporter.on("idle", () => {
console.log("Mail Idle"); console.log("Mail Idle");

View File

@@ -407,8 +407,11 @@ export const DeleteForm = ({
export const PasswordForm = (): React.JSX.Element => { export const PasswordForm = (): React.JSX.Element => {
const schema = z.object({ const schema = z.object({
password: z.string().min(2).max(30), password: z.string().min(2).max(30),
newPassword: z.string().min(2).max(30), newPassword: z
newPasswordConfirm: z.string().min(2).max(30), .string()
.min(8, { message: "Das Passwort muss mindestens 8 Zeichen lang sein" })
.max(30),
newPasswordConfirm: z.string().min(8).max(30),
}); });
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
type IFormInput = z.infer<typeof schema>; type IFormInput = z.infer<typeof schema>;

View File

@@ -49,8 +49,8 @@ export const Register = () => {
.refine((val) => val.length === 0 || val.includes(" ") || /^[A-ZÄÖÜ]/.test(val), { .refine((val) => val.length === 0 || val.includes(" ") || /^[A-ZÄÖÜ]/.test(val), {
message: "Der Nachname muss mit einem Großbuchstaben beginnen", message: "Der Nachname muss mit einem Großbuchstaben beginnen",
}), }),
password: z.string().min(12, { password: z.string().min(8, {
message: "Das Passwort muss mindestens 12 Zeichen lang sein", message: "Das Passwort muss mindestens 8 Zeichen lang sein",
}), }),
passwordConfirm: z.string(), passwordConfirm: z.string(),
}) })