diff --git a/apps/core-server/modules/chron.ts b/apps/core-server/modules/chron.ts index 7f8764db..8cfe33c6 100644 --- a/apps/core-server/modules/chron.ts +++ b/apps/core-server/modules/chron.ts @@ -2,6 +2,39 @@ import { MissionLog, NotificationPayload, prisma } from "@repo/db"; import { io } from "index"; 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 oldMissions = await prisma.mission.findMany({ where: { @@ -15,18 +48,6 @@ const removeClosedMissions = async () => { 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( (stationId) => { 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", ); - 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; // 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 - if ( - !( - now.getTime() - lastAlertTime.getTime() > 1000 * 60 * 180 || - allStationsInMissionChangedFromStatus4to1Or8to1 - ) || - missionHastManualReactivation - ) - return; + const now = new Date(); + if (now.getTime() - lastAlertTime.getTime() > 1000 * 60 * 180) + return removeMission(mission.id, "inaktivität"); - const log: MissionLog = { - type: "completed-log", - auto: true, - 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.`); + // Case 2: All stations in mission changed from status 4 to 1/6 or from status 8 to 1/6 + if (allStationsInMissionChangedFromStatus4to1Or8to1) + return removeMission(mission.id, "dem freimelden aller Stationen"); }); }; - const removeConnectedAircrafts = async () => { const connectedAircrafts = await prisma.connectedAircraft.findMany({ where: { diff --git a/apps/dispatch/app/_components/navbar/Settings.tsx b/apps/dispatch/app/_components/navbar/Settings.tsx index 07a3bbd7..15f42bbb 100644 --- a/apps/dispatch/app/_components/navbar/Settings.tsx +++ b/apps/dispatch/app/_components/navbar/Settings.tsx @@ -225,7 +225,7 @@ export const SettingsBtn = () => {

{ { } leftOfSearch={ - Heliports + Heliports } rightOfSearch={ -

+

diff --git a/apps/hub/app/(app)/admin/station/_components/Form.tsx b/apps/hub/app/(app)/admin/station/_components/Form.tsx index a6e2d8eb..207fae6a 100644 --- a/apps/hub/app/(app)/admin/station/_components/Form.tsx +++ b/apps/hub/app/(app)/admin/station/_components/Form.tsx @@ -2,20 +2,26 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { StationOptionalDefaultsSchema } from "@repo/db/zod"; import { useForm } from "react-hook-form"; -import { BosUse, Country, Station } from "@repo/db"; -import { FileText, LocateIcon, PlaneIcon } from "lucide-react"; +import { BosUse, ConnectedAircraft, Country, Station, User } from "@repo/db"; +import { FileText, LocateIcon, PlaneIcon, UserIcon } from "lucide-react"; import { Input } from "../../../../_components/ui/Input"; -import { useState } from "react"; import { deleteStation, upsertStation } from "../action"; import { Button } from "../../../../_components/ui/Button"; import { redirect } from "next/navigation"; 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 }) => { const form = useForm({ resolver: zodResolver(StationOptionalDefaultsSchema), defaultValues: station, }); + const dispoTableRef = useRef(null); // const [deleteLoading, setDeleteLoading] = useState(false); return ( <> @@ -27,10 +33,10 @@ export const StationForm = ({ station }: { station?: Station }) => { })} className="grid grid-cols-6 gap-3" > -

+

- Allgemeines + Allgemeines

{ />
-
+

- Hubschrauber + Hubschrauber

{ />
-
-
+
+
+
+ + + Verbundene Piloten +
+ } + 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 ( + + {row.original.User.firstname} {row.original.User.lastname} ( + {row.original.User.publicId}) + + ); + }, + }, + { + 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 Online; + } + 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 ( + 2 && "text-error")}> + {hours}h {minutes}min + + ); + }, + }, + { + header: "Aktionen", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + }, + ] as ColumnDef[] + } + /> +
); diff --git a/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx b/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx index 0309d537..053c88f5 100644 --- a/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx +++ b/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx @@ -10,7 +10,7 @@ import { Station, User, } from "@repo/db"; -import { useRef, useState } from "react"; +import { useRef } from "react"; import { useForm } from "react-hook-form"; import { deleteDispoHistory, @@ -84,11 +84,11 @@ export const ProfileForm: React.FC = ({ user }: ProfileFormPro })} >

- User bearbeiten + User bearbeiten

-