Heliport Search, Station connection history table, reworked mission close functionality
This commit is contained in:
@@ -2,6 +2,39 @@ import { MissionLog, NotificationPayload, prisma } from "@repo/db";
|
|||||||
import { io } from "index";
|
import { io } from "index";
|
||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
|
|
||||||
|
const removeMission = async (id: number, reason: string) => {
|
||||||
|
const log: MissionLog = {
|
||||||
|
type: "completed-log",
|
||||||
|
auto: true,
|
||||||
|
timeStamp: new Date().toISOString(),
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedMission = await prisma.mission.update({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
state: "finished",
|
||||||
|
missionLog: {
|
||||||
|
push: log as any,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
io.to("dispatchers").emit("new-mission", { updatedMission });
|
||||||
|
io.to("dispatchers").emit("notification", {
|
||||||
|
type: "mission-auto-close",
|
||||||
|
status: "chron",
|
||||||
|
message: `Einsatz ${updatedMission.publicId} wurde aufgrund ${reason} geschlossen.`,
|
||||||
|
data: {
|
||||||
|
missionId: updatedMission.id,
|
||||||
|
publicMissionId: updatedMission.publicId,
|
||||||
|
},
|
||||||
|
} as NotificationPayload);
|
||||||
|
|
||||||
|
console.log(`Mission ${updatedMission.id} closed due to inactivity.`);
|
||||||
|
};
|
||||||
|
|
||||||
const removeClosedMissions = async () => {
|
const removeClosedMissions = async () => {
|
||||||
const oldMissions = await prisma.mission.findMany({
|
const oldMissions = await prisma.mission.findMany({
|
||||||
where: {
|
where: {
|
||||||
@@ -15,18 +48,6 @@ const removeClosedMissions = async () => {
|
|||||||
|
|
||||||
const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null;
|
const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null;
|
||||||
|
|
||||||
const aircraftsInMission = await prisma.connectedAircraft.findMany({
|
|
||||||
where: {
|
|
||||||
stationId: {
|
|
||||||
in: mission.missionStationIds,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const allConnectedAircraftsInIdleStatus = aircraftsInMission.every((a) =>
|
|
||||||
["1", "2", "6"].includes(a.fmsStatus),
|
|
||||||
);
|
|
||||||
|
|
||||||
const allStationsInMissionChangedFromStatus4to1Or8to1 = mission.missionStationIds.every(
|
const allStationsInMissionChangedFromStatus4to1Or8to1 = mission.missionStationIds.every(
|
||||||
(stationId) => {
|
(stationId) => {
|
||||||
const status4Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => {
|
const status4Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => {
|
||||||
@@ -69,67 +90,24 @@ const removeClosedMissions = async () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const missionHastManualReactivation = (mission.missionLog as unknown as MissionLog[]).some(
|
const missionHasManualReactivation = (mission.missionLog as unknown as MissionLog[]).some(
|
||||||
(l) => l.type === "reopened-log",
|
(l) => l.type === "reopened-log",
|
||||||
);
|
);
|
||||||
console.log({
|
|
||||||
missionId: mission.publicId,
|
|
||||||
allConnectedAircraftsInIdleStatus,
|
|
||||||
lastAlertTime,
|
|
||||||
allStationsInMissionChangedFromStatus4to1Or8to1,
|
|
||||||
missionHastManualReactivation,
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
!allConnectedAircraftsInIdleStatus // If some aircrafts are still active, do not close the mission
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const now = new Date();
|
if (missionHasManualReactivation) return;
|
||||||
|
|
||||||
if (!lastAlertTime) return;
|
if (!lastAlertTime) return;
|
||||||
|
|
||||||
// Case 1: Forgotten Mission, last alert more than 3 Hours ago
|
// Case 1: Forgotten Mission, last alert more than 3 Hours ago
|
||||||
// Case 2: All stations in mission changed from status 4 to 1 or from status 8 to 1
|
const now = new Date();
|
||||||
if (
|
if (now.getTime() - lastAlertTime.getTime() > 1000 * 60 * 180)
|
||||||
!(
|
return removeMission(mission.id, "inaktivität");
|
||||||
now.getTime() - lastAlertTime.getTime() > 1000 * 60 * 180 ||
|
|
||||||
allStationsInMissionChangedFromStatus4to1Or8to1
|
|
||||||
) ||
|
|
||||||
missionHastManualReactivation
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const log: MissionLog = {
|
// Case 2: All stations in mission changed from status 4 to 1/6 or from status 8 to 1/6
|
||||||
type: "completed-log",
|
if (allStationsInMissionChangedFromStatus4to1Or8to1)
|
||||||
auto: true,
|
return removeMission(mission.id, "dem freimelden aller Stationen");
|
||||||
timeStamp: new Date().toISOString(),
|
|
||||||
data: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatedMission = await prisma.mission.update({
|
|
||||||
where: {
|
|
||||||
id: mission.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
state: "finished",
|
|
||||||
missionLog: {
|
|
||||||
push: log as any,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
io.to("dispatchers").emit("new-mission", { updatedMission });
|
|
||||||
io.to("dispatchers").emit("notification", {
|
|
||||||
type: "mission-auto-close",
|
|
||||||
status: "chron",
|
|
||||||
message: `Einsatz ${updatedMission.publicId} wurde aufgrund ${allStationsInMissionChangedFromStatus4to1Or8to1 ? "des Freimeldens aller Stationen" : "von Inaktivität"} geschlossen.`,
|
|
||||||
data: {
|
|
||||||
missionId: updatedMission.id,
|
|
||||||
publicMissionId: updatedMission.publicId,
|
|
||||||
},
|
|
||||||
} as NotificationPayload);
|
|
||||||
console.log(`Mission ${mission.id} closed due to inactivity.`);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeConnectedAircrafts = async () => {
|
const removeConnectedAircrafts = async () => {
|
||||||
const connectedAircrafts = await prisma.connectedAircraft.findMany({
|
const connectedAircrafts = await prisma.connectedAircraft.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export const SettingsBtn = () => {
|
|||||||
|
|
||||||
<p className="label mt-2 w-full">
|
<p className="label mt-2 w-full">
|
||||||
<Link
|
<Link
|
||||||
href="https://docs.virtualairrescue.com/docs/Leitstelle/App-Alarmierung#download"
|
href="https://docs.virtualairrescue.com/pilotenbereich/app-alarmierung.html#download"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="link link-hover link-primary"
|
className="link link-hover link-primary"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const page = () => {
|
|||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
stickyHeaders
|
stickyHeaders
|
||||||
prismaModel="heliport"
|
prismaModel="heliport"
|
||||||
searchFields={["siteName", "info", "hospital"]}
|
searchFields={["siteName", "info", "hospital", "designator"]}
|
||||||
columns={
|
columns={
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -45,11 +45,11 @@ const page = () => {
|
|||||||
}
|
}
|
||||||
leftOfSearch={
|
leftOfSearch={
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<DatabaseBackupIcon className="w-5 h-5" /> Heliports
|
<DatabaseBackupIcon className="h-5 w-5" /> Heliports
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
rightOfSearch={
|
rightOfSearch={
|
||||||
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
|
<p className="flex items-center justify-between gap-2 text-left text-2xl font-semibold">
|
||||||
<Link href={"/admin/heliport/new"}>
|
<Link href={"/admin/heliport/new"}>
|
||||||
<button className="btn btn-sm btn-outline btn-primary">Erstellen</button>
|
<button className="btn btn-sm btn-outline btn-primary">Erstellen</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -2,20 +2,26 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { StationOptionalDefaultsSchema } from "@repo/db/zod";
|
import { StationOptionalDefaultsSchema } from "@repo/db/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { BosUse, Country, Station } from "@repo/db";
|
import { BosUse, ConnectedAircraft, Country, Station, User } from "@repo/db";
|
||||||
import { FileText, LocateIcon, PlaneIcon } from "lucide-react";
|
import { FileText, LocateIcon, PlaneIcon, UserIcon } from "lucide-react";
|
||||||
import { Input } from "../../../../_components/ui/Input";
|
import { Input } from "../../../../_components/ui/Input";
|
||||||
import { useState } from "react";
|
|
||||||
import { deleteStation, upsertStation } from "../action";
|
import { deleteStation, upsertStation } from "../action";
|
||||||
import { Button } from "../../../../_components/ui/Button";
|
import { Button } from "../../../../_components/ui/Button";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { deletePilotHistory } from "(app)/admin/user/action";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { cn } from "@repo/shared-components";
|
||||||
|
|
||||||
export const StationForm = ({ station }: { station?: Station }) => {
|
export const StationForm = ({ station }: { station?: Station }) => {
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(StationOptionalDefaultsSchema),
|
resolver: zodResolver(StationOptionalDefaultsSchema),
|
||||||
defaultValues: station,
|
defaultValues: station,
|
||||||
});
|
});
|
||||||
|
const dispoTableRef = useRef<PaginatedTableRef>(null);
|
||||||
// const [deleteLoading, setDeleteLoading] = useState(false);
|
// const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -27,10 +33,10 @@ export const StationForm = ({ station }: { station?: Station }) => {
|
|||||||
})}
|
})}
|
||||||
className="grid grid-cols-6 gap-3"
|
className="grid grid-cols-6 gap-3"
|
||||||
>
|
>
|
||||||
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
|
<div className="card bg-base-200 col-span-2 shadow-xl max-xl:col-span-6">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<FileText className="w-5 h-5" /> Allgemeines
|
<FileText className="h-5 w-5" /> Allgemeines
|
||||||
</h2>
|
</h2>
|
||||||
<Input form={form} label="BOS Rufname" name="bosCallsign" className="input-sm" />
|
<Input form={form} label="BOS Rufname" name="bosCallsign" className="input-sm" />
|
||||||
<Input
|
<Input
|
||||||
@@ -55,7 +61,7 @@ export const StationForm = ({ station }: { station?: Station }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<label className="form-control w-full">
|
<label className="form-control w-full">
|
||||||
<span className="label-text text-lg flex items-center gap-2">BOS Nutzung</span>
|
<span className="label-text flex items-center gap-2 text-lg">BOS Nutzung</span>
|
||||||
<select
|
<select
|
||||||
className="input-sm select select-bordered select-sm"
|
className="input-sm select select-bordered select-sm"
|
||||||
{...form.register("bosUse")}
|
{...form.register("bosUse")}
|
||||||
@@ -69,13 +75,13 @@ export const StationForm = ({ station }: { station?: Station }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
|
<div className="card bg-base-200 col-span-2 shadow-xl max-xl:col-span-6">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<LocateIcon className="w-5 h-5" /> Standort + Ausrüstung
|
<LocateIcon className="h-5 w-5" /> Standort + Ausrüstung
|
||||||
</h2>
|
</h2>
|
||||||
<label className="form-control w-full">
|
<label className="form-control w-full">
|
||||||
<span className="label-text text-lg flex items-center gap-2">Land</span>
|
<span className="label-text flex items-center gap-2 text-lg">Land</span>
|
||||||
<select
|
<select
|
||||||
className="input-sm select select-bordered select-sm"
|
className="input-sm select select-bordered select-sm"
|
||||||
{...form.register("country", {})}
|
{...form.register("country", {})}
|
||||||
@@ -94,21 +100,21 @@ export const StationForm = ({ station }: { station?: Station }) => {
|
|||||||
name="locationStateShort"
|
name="locationStateShort"
|
||||||
className="input-sm"
|
className="input-sm"
|
||||||
/>
|
/>
|
||||||
<span className="label-text text-lg flex items-center gap-2">Ausgerüstet mit:</span>
|
<span className="label-text flex items-center gap-2 text-lg">Ausgerüstet mit:</span>
|
||||||
<div className="form-control space-y-2">
|
<div className="form-control space-y-2">
|
||||||
<label className="label cursor-pointer flex">
|
<label className="label flex cursor-pointer">
|
||||||
<span className="flex-1 text-left">Winde</span>
|
<span className="flex-1 text-left">Winde</span>
|
||||||
<input type="checkbox" className="toggle" {...form.register("hasWinch")} />
|
<input type="checkbox" className="toggle" {...form.register("hasWinch")} />
|
||||||
</label>
|
</label>
|
||||||
<label className="label cursor-pointer flex">
|
<label className="label flex cursor-pointer">
|
||||||
<span className="flex-1 text-left">Nachtsicht-Gerät</span>
|
<span className="flex-1 text-left">Nachtsicht-Gerät</span>
|
||||||
<input type="checkbox" className="toggle" {...form.register("hasNvg")} />
|
<input type="checkbox" className="toggle" {...form.register("hasNvg")} />
|
||||||
</label>
|
</label>
|
||||||
<label className="label cursor-pointer flex">
|
<label className="label flex cursor-pointer">
|
||||||
<span className="flex-1 text-left">24-Stunden Einsatzfähig</span>
|
<span className="flex-1 text-left">24-Stunden Einsatzfähig</span>
|
||||||
<input type="checkbox" className="toggle" {...form.register("is24h")} />
|
<input type="checkbox" className="toggle" {...form.register("is24h")} />
|
||||||
</label>
|
</label>
|
||||||
<label className="label cursor-pointer flex">
|
<label className="label flex cursor-pointer">
|
||||||
<span className="flex-1 text-left">Bergetau</span>
|
<span className="flex-1 text-left">Bergetau</span>
|
||||||
<input type="checkbox" className="toggle" {...form.register("hasRope")} />
|
<input type="checkbox" className="toggle" {...form.register("hasRope")} />
|
||||||
</label>
|
</label>
|
||||||
@@ -132,16 +138,16 @@ export const StationForm = ({ station }: { station?: Station }) => {
|
|||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
/>
|
/>
|
||||||
<label className="label cursor-pointer flex">
|
<label className="label flex cursor-pointer">
|
||||||
<span className="text-lg flex-1 text-left">Reichweiten ausblenden</span>
|
<span className="flex-1 text-left text-lg">Reichweiten ausblenden</span>
|
||||||
<input type="checkbox" className="toggle" {...form.register("hideRangeRings")} />
|
<input type="checkbox" className="toggle" {...form.register("hideRangeRings")} />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
|
<div className="card bg-base-200 col-span-2 shadow-xl max-xl:col-span-6">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<PlaneIcon className="w-5 h-5" /> Hubschrauber
|
<PlaneIcon className="h-5 w-5" /> Hubschrauber
|
||||||
</h2>
|
</h2>
|
||||||
<Input form={form} label="Hubschrauber Typ" name="aircraft" className="input-sm" />
|
<Input form={form} label="Hubschrauber Typ" name="aircraft" className="input-sm" />
|
||||||
<Input
|
<Input
|
||||||
@@ -160,8 +166,8 @@ export const StationForm = ({ station }: { station?: Station }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl col-span-6">
|
<div className="card bg-base-200 col-span-6 shadow-xl">
|
||||||
<div className="card-body ">
|
<div className="card-body">
|
||||||
<div className="flex w-full gap-4">
|
<div className="flex w-full gap-4">
|
||||||
<Button
|
<Button
|
||||||
isLoading={form.formState.isSubmitting}
|
isLoading={form.formState.isSubmitting}
|
||||||
@@ -184,6 +190,93 @@ export const StationForm = ({ station }: { station?: Station }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="card bg-base-200 col-span-6 shadow-xl">
|
||||||
|
<PaginatedTable
|
||||||
|
leftOfSearch={
|
||||||
|
<div className="flex items-center gap-2 text-lg font-bold">
|
||||||
|
<UserIcon className="h-5 w-5" />
|
||||||
|
Verbundene Piloten
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
filter={{
|
||||||
|
stationId: station?.id,
|
||||||
|
}}
|
||||||
|
searchFields={["User.firstname", "User.lastname", "User.publicId"]}
|
||||||
|
prismaModel={"connectedAircraft"}
|
||||||
|
include={{ Station: true, User: true }}
|
||||||
|
columns={
|
||||||
|
[
|
||||||
|
{
|
||||||
|
accessorKey: "User.firstname",
|
||||||
|
header: "Nutzer",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className="link link-hover"
|
||||||
|
href={`/admin/user/${row.original.User.id}`}
|
||||||
|
>
|
||||||
|
{row.original.User.firstname} {row.original.User.lastname} (
|
||||||
|
{row.original.User.publicId})
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "loginTime",
|
||||||
|
header: "Login",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return new Date(row.getValue("loginTime")).toLocaleString("de-DE");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "logoutTime",
|
||||||
|
header: "Logout",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return new Date(row.getValue("logoutTime")).toLocaleString("de-DE");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Time Online",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
if (!row.original.logoutTime) {
|
||||||
|
return <span className="text-success">Online</span>;
|
||||||
|
}
|
||||||
|
const loginTime = new Date(row.original.loginTime).getTime();
|
||||||
|
const logoutTime = new Date(row.original.logoutTime).getTime();
|
||||||
|
const timeOnline = logoutTime - loginTime;
|
||||||
|
|
||||||
|
const hours = Math.floor(timeOnline / 1000 / 60 / 60);
|
||||||
|
const minutes = Math.floor((timeOnline / 1000 / 60) % 60);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={cn(hours > 2 && "text-error")}>
|
||||||
|
{hours}h {minutes}min
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Aktionen",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-error"
|
||||||
|
onClick={async () => {
|
||||||
|
await deletePilotHistory(row.original.id);
|
||||||
|
dispoTableRef.current?.refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
löschen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as ColumnDef<ConnectedAircraft & { Station: Station; User: User }>[]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Station,
|
Station,
|
||||||
User,
|
User,
|
||||||
} from "@repo/db";
|
} from "@repo/db";
|
||||||
import { useRef, useState } from "react";
|
import { useRef } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
deleteDispoHistory,
|
deleteDispoHistory,
|
||||||
@@ -84,11 +84,11 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormPro
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<MixerHorizontalIcon className="w-5 h-5" /> User bearbeiten
|
<MixerHorizontalIcon className="h-5 w-5" /> User bearbeiten
|
||||||
</h2>
|
</h2>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<label className="floating-label w-full mb-5 mt-5">
|
<label className="floating-label mb-5 mt-5 w-full">
|
||||||
<span className="text-lg flex items-center gap-2">
|
<span className="flex items-center gap-2 text-lg">
|
||||||
<PersonIcon /> Vorname
|
<PersonIcon /> Vorname
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
@@ -102,8 +102,8 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormPro
|
|||||||
{form.formState.errors.firstname && (
|
{form.formState.errors.firstname && (
|
||||||
<p className="text-error">{form.formState.errors.firstname.message}</p>
|
<p className="text-error">{form.formState.errors.firstname.message}</p>
|
||||||
)}
|
)}
|
||||||
<label className="floating-label w-full mb-5">
|
<label className="floating-label mb-5 w-full">
|
||||||
<span className="text-lg flex items-center gap-2">
|
<span className="flex items-center gap-2 text-lg">
|
||||||
<PersonIcon /> Nachname
|
<PersonIcon /> Nachname
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
@@ -120,13 +120,13 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormPro
|
|||||||
{session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
{session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
||||||
<>
|
<>
|
||||||
<label className="floating-label w-full">
|
<label className="floating-label w-full">
|
||||||
<span className="text-lg flex items-center gap-2">
|
<span className="flex items-center gap-2 text-lg">
|
||||||
<EnvelopeClosedIcon /> E-Mail
|
<EnvelopeClosedIcon /> E-Mail
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
{...form.register("email")}
|
{...form.register("email")}
|
||||||
type="text"
|
type="text"
|
||||||
className="input input-bordered w-full mb-2"
|
className="input input-bordered mb-2 w-full"
|
||||||
defaultValue={user?.email}
|
defaultValue={user?.email}
|
||||||
placeholder="E-Mail"
|
placeholder="E-Mail"
|
||||||
/>
|
/>
|
||||||
@@ -166,7 +166,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormPro
|
|||||||
{session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
{session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
<p className="text-gray-400">Berechtigung Schnellauswahl</p>
|
<p className="text-gray-400">Berechtigung Schnellauswahl</p>
|
||||||
<div className="flex gap-2 justify-evenly">
|
<div className="flex justify-evenly gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm btn-outline"
|
className="btn btn-sm btn-outline"
|
||||||
@@ -251,7 +251,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
|
|||||||
<div className="card-body flex-row flex-wrap">
|
<div className="card-body flex-row flex-wrap">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<MixerHorizontalIcon className="w-5 h-5" /> Dispo-Verbindungs Historie
|
<MixerHorizontalIcon className="h-5 w-5" /> Dispo-Verbindungs Historie
|
||||||
</h2>
|
</h2>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
ref={dispoTableRef}
|
ref={dispoTableRef}
|
||||||
@@ -313,7 +313,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<PlaneIcon className="w-5 h-5" /> Pilot-Verbindungs Historie
|
<PlaneIcon className="h-5 w-5" /> Pilot-Verbindungs Historie
|
||||||
</h2>
|
</h2>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
ref={dispoTableRef}
|
ref={dispoTableRef}
|
||||||
@@ -396,7 +396,7 @@ export const UserPenalties = ({ user }: { user: User }) => {
|
|||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title flex justify-between">
|
<h2 className="card-title flex justify-between">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<ExclamationTriangleIcon className="w-5 h-5" /> Audit-log
|
<ExclamationTriangleIcon className="h-5 w-5" /> Audit-log
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<PenaltyDropdown
|
<PenaltyDropdown
|
||||||
@@ -478,7 +478,7 @@ export const UserReports = ({ user }: { user: User }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<ExclamationTriangleIcon className="w-5 h-5" /> Nutzer Reports
|
<ExclamationTriangleIcon className="h-5 w-5" /> Nutzer Reports
|
||||||
</h2>
|
</h2>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
prismaModel="report"
|
prismaModel="report"
|
||||||
@@ -528,10 +528,10 @@ export const AdminForm = ({
|
|||||||
return (
|
return (
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<LightningBoltIcon className="w-5 h-5" /> Administration
|
<LightningBoltIcon className="h-5 w-5" /> Administration
|
||||||
</h2>
|
</h2>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="card-actions pt-6 flex flex-wrap gap-2">
|
<div className="card-actions flex flex-wrap gap-2 pt-6">
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const { password } = await resetPassword(user.id);
|
const { password } = await resetPassword(user.id);
|
||||||
@@ -547,13 +547,13 @@ export const AdminForm = ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="btn-sm flex-1 min-w-[250px] btn-outline btn-success"
|
className="btn-sm btn-outline btn-success min-w-[250px] flex-1"
|
||||||
>
|
>
|
||||||
<LockOpen1Icon /> Passwort zurücksetzen
|
<LockOpen1Icon /> Passwort zurücksetzen
|
||||||
</Button>
|
</Button>
|
||||||
{session?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
{session?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
|
||||||
<div
|
<div
|
||||||
className="tooltip flex-1 min-w-[250px] tooltip-warning"
|
className="tooltip tooltip-warning min-w-[250px] flex-1"
|
||||||
data-tip="Dies löscht den Nutzer sofort, außerdem alle Reports, Verbindungs-Verlauf und Chat-Nachrichten"
|
data-tip="Dies löscht den Nutzer sofort, außerdem alle Reports, Verbindungs-Verlauf und Chat-Nachrichten"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -581,14 +581,14 @@ export const AdminForm = ({
|
|||||||
router.refresh();
|
router.refresh();
|
||||||
}}
|
}}
|
||||||
role="submit"
|
role="submit"
|
||||||
className="btn-sm flex-1 min-w-[250px] btn-outline btn-warning"
|
className="btn-sm btn-outline btn-warning min-w-[250px] flex-1"
|
||||||
>
|
>
|
||||||
<HobbyKnifeIcon /> Account entsperren
|
<HobbyKnifeIcon /> Account entsperren
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{discordAccount && (
|
{discordAccount && (
|
||||||
<div
|
<div
|
||||||
className="tooltip flex-1 min-w-[250px]"
|
className="tooltip min-w-[250px] flex-1"
|
||||||
data-tip={`Name: ${discordAccount.username}`}
|
data-tip={`Name: ${discordAccount.username}`}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -604,7 +604,7 @@ export const AdminForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="btn-sm w-full btn-outline btn-info"
|
className="btn-sm btn-outline btn-info w-full"
|
||||||
>
|
>
|
||||||
<DiscordLogoIcon /> Name und Berechtigungen setzen
|
<DiscordLogoIcon /> Name und Berechtigungen setzen
|
||||||
</Button>
|
</Button>
|
||||||
@@ -613,12 +613,12 @@ export const AdminForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<ChartBarBigIcon className="w-5 h-5" /> Aktivität
|
<ChartBarBigIcon className="h-5 w-5" /> Aktivität
|
||||||
</h2>
|
</h2>
|
||||||
<div className="stats flex">
|
<div className="stats flex">
|
||||||
<div className="stat">
|
<div className="stat">
|
||||||
<div className="stat-figure text-primary">
|
<div className="stat-figure text-primary">
|
||||||
<LightningBoltIcon className="w-8 h-8" />
|
<LightningBoltIcon className="h-8 w-8" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-value text-primary">
|
<div className="stat-value text-primary">
|
||||||
{dispoTime.hours}h {dispoTime.minutes}min
|
{dispoTime.hours}h {dispoTime.minutes}min
|
||||||
@@ -630,7 +630,7 @@ export const AdminForm = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="stat">
|
||||||
<div className="stat-figure text-primary">
|
<div className="stat-figure text-primary">
|
||||||
<PlaneIcon className="w-8 h-8" />
|
<PlaneIcon className="h-8 w-8" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-value text-primary">
|
<div className="stat-value text-primary">
|
||||||
{pilotTime.hours}h {pilotTime.minutes}min
|
{pilotTime.hours}h {pilotTime.minutes}min
|
||||||
@@ -642,12 +642,12 @@ export const AdminForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<ExclamationTriangleIcon className="w-5 h-5" /> Reports
|
<ExclamationTriangleIcon className="h-5 w-5" /> Reports
|
||||||
</h2>
|
</h2>
|
||||||
<div className="stats flex">
|
<div className="stats flex">
|
||||||
<div className="stat">
|
<div className="stat">
|
||||||
<div className="stat-figure text-primary">
|
<div className="stat-figure text-primary">
|
||||||
<ExclamationTriangleIcon className="w-8 h-8" />
|
<ExclamationTriangleIcon className="h-8 w-8" />
|
||||||
</div>
|
</div>
|
||||||
<div className={cn("stat-value text-primary", reports.open && "text-warning")}>
|
<div className={cn("stat-value text-primary", reports.open && "text-warning")}>
|
||||||
{reports.open}
|
{reports.open}
|
||||||
@@ -656,7 +656,7 @@ export const AdminForm = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="stat">
|
<div className="stat">
|
||||||
<div className="stat-figure text-primary">
|
<div className="stat-figure text-primary">
|
||||||
<Timer className="w-8 h-8" />
|
<Timer className="h-8 w-8" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-value text-primary">{reports.total60Days}</div>
|
<div className="stat-value text-primary">{reports.total60Days}</div>
|
||||||
<div className="stat-title">in den letzten 60 Tagen</div>
|
<div className="stat-title">in den letzten 60 Tagen</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user