changes pilot socket to reperate pilto socket, added pilot stats
This commit is contained in:
@@ -32,7 +32,6 @@ const router = Router();
|
|||||||
|
|
||||||
router.get("/token", async (req, res) => {
|
router.get("/token", async (req, res) => {
|
||||||
const roomName = req.query.roomName as string;
|
const roomName = req.query.roomName as string;
|
||||||
console.log("roomName", roomName);
|
|
||||||
res.send({
|
res.send({
|
||||||
token: await createToken(roomName),
|
token: await createToken(roomName),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,4 +80,86 @@ router.delete("/:id", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send mission
|
||||||
|
|
||||||
|
router.post("/:id/send-alert", async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
try {
|
||||||
|
const mission = await prisma.mission.findUnique({
|
||||||
|
where: { id: Number(id) },
|
||||||
|
});
|
||||||
|
const Stations = await prisma.station.findMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: mission?.missionStationIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!mission) {
|
||||||
|
res.status(404).json({ error: "Mission not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const connectedAircrafts = await prisma.connectedAircraft.findMany({
|
||||||
|
where: {
|
||||||
|
stationId: {
|
||||||
|
in: mission.missionStationIds,
|
||||||
|
},
|
||||||
|
logoutTime: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const aircraft of connectedAircrafts) {
|
||||||
|
console.log(`Sending mission to: station:${aircraft.stationId}`);
|
||||||
|
io.to(`station:${aircraft.stationId}`).emit("mission-alert", {
|
||||||
|
...mission,
|
||||||
|
Stations,
|
||||||
|
});
|
||||||
|
const existingMissionOnStationUser =
|
||||||
|
await prisma.missionOnStationUsers.findFirst({
|
||||||
|
where: {
|
||||||
|
missionId: mission.id,
|
||||||
|
userId: aircraft.userId,
|
||||||
|
stationId: aircraft.stationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!existingMissionOnStationUser)
|
||||||
|
await prisma.missionOnStationUsers.create({
|
||||||
|
data: {
|
||||||
|
missionId: mission.id,
|
||||||
|
userId: aircraft.userId,
|
||||||
|
stationId: aircraft.stationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// for statistics only
|
||||||
|
await prisma.missionsOnStations
|
||||||
|
.createMany({
|
||||||
|
data: mission.missionStationIds.map((stationId) => ({
|
||||||
|
missionId: mission.id,
|
||||||
|
stationId,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Ignore if the entry already exists
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.mission.update({
|
||||||
|
where: { id: Number(id) },
|
||||||
|
data: {
|
||||||
|
state: "running",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
message: `Einsatz gesendet (${connectedAircrafts.length} Nutzer) `,
|
||||||
|
});
|
||||||
|
io.to("dispatchers").emit("update-mission", mission);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: "Failed to send mission" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export const handleConnectPilot =
|
|||||||
const user = socket.data.user; // User ID aus dem JWT-Token
|
const user = socket.data.user; // User ID aus dem JWT-Token
|
||||||
const userId = socket.data.user.id; // User ID aus dem JWT-Token
|
const userId = socket.data.user.id; // User ID aus dem JWT-Token
|
||||||
|
|
||||||
|
console.log("Pilot connected:", userId);
|
||||||
|
|
||||||
if (!user) return Error("User not found");
|
if (!user) return Error("User not found");
|
||||||
|
|
||||||
const existingConnection = await prisma.connectedAircraft.findFirst({
|
const existingConnection = await prisma.connectedAircraft.findFirst({
|
||||||
@@ -62,8 +64,6 @@ export const handleConnectPilot =
|
|||||||
userId: userId,
|
userId: userId,
|
||||||
loginTime: new Date().toISOString(),
|
loginTime: new Date().toISOString(),
|
||||||
stationId: parseInt(stationId),
|
stationId: parseInt(stationId),
|
||||||
/* user: { connect: { id: userId } }, // Ensure the user relationship is set
|
|
||||||
station: { connect: { id: stationId } }, // Ensure the station relationship is set */
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,6 +71,8 @@ export const handleConnectPilot =
|
|||||||
socket.join(`user:${userId}`); // Join the user-specific room
|
socket.join(`user:${userId}`); // Join the user-specific room
|
||||||
socket.join(`station:${stationId}`); // Join the station-specific room
|
socket.join(`station:${stationId}`); // Join the station-specific room
|
||||||
|
|
||||||
|
console.log(`Pilot in: station:${stationId}`);
|
||||||
|
|
||||||
io.to("dispatchers").emit("pilots-update");
|
io.to("dispatchers").emit("pilots-update");
|
||||||
io.to("pilots").emit("pilots-update");
|
io.to("pilots").emit("pilots-update");
|
||||||
|
|
||||||
@@ -83,14 +85,16 @@ export const handleConnectPilot =
|
|||||||
|
|
||||||
socket.on("disconnect", async () => {
|
socket.on("disconnect", async () => {
|
||||||
console.log("Disconnected from dispatch server");
|
console.log("Disconnected from dispatch server");
|
||||||
await prisma.connectedAircraft.update({
|
await prisma.connectedAircraft
|
||||||
where: {
|
.update({
|
||||||
id: connectedAircraftEntry.id,
|
where: {
|
||||||
},
|
id: connectedAircraftEntry.id,
|
||||||
data: {
|
},
|
||||||
logoutTime: new Date().toISOString(),
|
data: {
|
||||||
},
|
logoutTime: new Date().toISOString(),
|
||||||
});
|
},
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
io.to("dispatchers").emit("pilots-update");
|
io.to("dispatchers").emit("pilots-update");
|
||||||
io.to("pilots").emit("pilots-update");
|
io.to("pilots").emit("pilots-update");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { dispatchSocket } from "../../dispatch/socket";
|
import { dispatchSocket } from "../../dispatch/socket";
|
||||||
import { Station } from "@repo/db";
|
import { Mission, Station } from "@repo/db";
|
||||||
import { pilotSocket } from "pilot/socket";
|
import { pilotSocket } from "pilot/socket";
|
||||||
|
|
||||||
interface ConnectionStore {
|
interface ConnectionStore {
|
||||||
status: "connected" | "disconnected" | "connecting" | "error";
|
status: "connected" | "disconnected" | "connecting" | "error";
|
||||||
message: string;
|
message: string;
|
||||||
selectedStation: Station | null;
|
selectedStation: Station | null;
|
||||||
|
activeMission:
|
||||||
|
| (Mission & {
|
||||||
|
Stations: Station[];
|
||||||
|
})
|
||||||
|
| null;
|
||||||
connect: (
|
connect: (
|
||||||
uid: string,
|
uid: string,
|
||||||
stationId: string,
|
stationId: string,
|
||||||
@@ -20,13 +25,14 @@ export const usePilotConnectionStore = create<ConnectionStore>((set) => ({
|
|||||||
status: "disconnected",
|
status: "disconnected",
|
||||||
message: "",
|
message: "",
|
||||||
selectedStation: null,
|
selectedStation: null,
|
||||||
|
activeMission: null,
|
||||||
connect: async (uid, stationId, logoffTime, station) =>
|
connect: async (uid, stationId, logoffTime, station) =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
set({ status: "connecting", message: "", selectedStation: station });
|
set({ status: "connecting", message: "", selectedStation: station });
|
||||||
dispatchSocket.auth = { uid };
|
pilotSocket.auth = { uid };
|
||||||
dispatchSocket.connect();
|
pilotSocket.connect();
|
||||||
dispatchSocket.once("connect", () => {
|
pilotSocket.once("connect", () => {
|
||||||
dispatchSocket.emit("connect-pilot", {
|
pilotSocket.emit("connect-pilot", {
|
||||||
logoffTime,
|
logoffTime,
|
||||||
stationId,
|
stationId,
|
||||||
});
|
});
|
||||||
@@ -34,30 +40,36 @@ export const usePilotConnectionStore = create<ConnectionStore>((set) => ({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
disconnect: () => {
|
disconnect: () => {
|
||||||
dispatchSocket.disconnect();
|
pilotSocket.disconnect();
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatchSocket.on("connect", () => {
|
pilotSocket.on("connect", () => {
|
||||||
pilotSocket.disconnect();
|
dispatchSocket.disconnect();
|
||||||
usePilotConnectionStore.setState({ status: "connected", message: "" });
|
usePilotConnectionStore.setState({ status: "connected", message: "" });
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatchSocket.on("connect_error", (err) => {
|
pilotSocket.on("connect_error", (err) => {
|
||||||
usePilotConnectionStore.setState({
|
usePilotConnectionStore.setState({
|
||||||
status: "error",
|
status: "error",
|
||||||
message: err.message,
|
message: err.message,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatchSocket.on("disconnect", () => {
|
pilotSocket.on("disconnect", () => {
|
||||||
usePilotConnectionStore.setState({ status: "disconnected", message: "" });
|
usePilotConnectionStore.setState({ status: "disconnected", message: "" });
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatchSocket.on("force-disconnect", (reason: string) => {
|
pilotSocket.on("force-disconnect", (reason: string) => {
|
||||||
console.log("force-disconnect", reason);
|
console.log("force-disconnect", reason);
|
||||||
usePilotConnectionStore.setState({
|
usePilotConnectionStore.setState({
|
||||||
status: "disconnected",
|
status: "disconnected",
|
||||||
message: reason,
|
message: reason,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pilotSocket.on("mission-alert", (data) => {
|
||||||
|
usePilotConnectionStore.setState({
|
||||||
|
activeMission: data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { FMS_STATUS_TEXT_COLORS } from "../AircraftMarker";
|
import { FMS_STATUS_TEXT_COLORS } from "../AircraftMarker";
|
||||||
/* import { Select } from "_components/Select";
|
import { toast } from "react-hot-toast";
|
||||||
import { Station } from "@repo/db";
|
|
||||||
import { getStations } from "dispatch/_components/pannel/action"; */
|
|
||||||
import {
|
import {
|
||||||
Ban,
|
Ban,
|
||||||
BellRing,
|
BellRing,
|
||||||
@@ -29,7 +27,11 @@ import {
|
|||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { deleteMissionAPI, editMissionAPI } from "querys/missions";
|
import {
|
||||||
|
deleteMissionAPI,
|
||||||
|
editMissionAPI,
|
||||||
|
sendMissionAPI,
|
||||||
|
} from "querys/missions";
|
||||||
|
|
||||||
const Einsatzdetails = ({ mission }: { mission: Mission }) => {
|
const Einsatzdetails = ({ mission }: { mission: Mission }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -42,6 +44,20 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const sendAlertMutation = useMutation({
|
||||||
|
mutationKey: ["missions"],
|
||||||
|
mutationFn: sendMissionAPI,
|
||||||
|
onError: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Fehler beim Alarmieren");
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message);
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["missions"],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
const { setMissionFormValues, setOpen } = usePannelStore((state) => state);
|
const { setMissionFormValues, setOpen } = usePannelStore((state) => state);
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-base-content">
|
<div className="p-4 text-base-content">
|
||||||
@@ -76,7 +92,10 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="divider mt-0 mb-0" />
|
<div className="divider mt-0 mb-0" />
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<button className="btn btn-sm btn-info btn-outline flex-3">
|
<button
|
||||||
|
className="btn btn-sm btn-info btn-outline flex-3"
|
||||||
|
onClick={() => sendAlertMutation.mutate(mission.id)}
|
||||||
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<BellRing size={16} /> Alarmieren
|
<BellRing size={16} /> Alarmieren
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export const MissionForm = () => {
|
|||||||
form={form}
|
form={form}
|
||||||
options={stations?.map((s) => ({
|
options={stations?.map((s) => ({
|
||||||
label: s.bosCallsign,
|
label: s.bosCallsign,
|
||||||
value: s.id.toString(),
|
value: s.id,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { pilotSocket } from "pilot/socket";
|
"use client";
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
|
|
||||||
export const dispatchSocket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
export const dispatchSocket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Pannel } from "dispatch/_components/pannel/Pannel";
|
|
||||||
import { usePannelStore } from "_store/pannelStore";
|
|
||||||
import { cn } from "helpers/cn";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { Chat } from "../_components/left/Chat";
|
import { Chat } from "../_components/left/Chat";
|
||||||
import { Report } from "../_components/left/Report";
|
import { Report } from "../_components/left/Report";
|
||||||
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
|
|
||||||
const DispatchPage = () => {
|
const DispatchPage = () => {
|
||||||
const { isOpen } = usePannelStore();
|
const { activeMission } = usePilotConnectionStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex-1 flex transition-all duration-500 ease w-full">
|
<div className="relative flex-1 flex transition-all duration-500 ease w-full">
|
||||||
{/* <MapToastCard2 /> */}
|
{/* <MapToastCard2 /> */}
|
||||||
@@ -20,14 +18,7 @@ const DispatchPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div>{JSON.stringify(activeMission)}</div>
|
||||||
className={cn(
|
|
||||||
"absolute right-0 w-[500px] z-999 transition-transform",
|
|
||||||
isOpen ? "translate-x-0" : "translate-x-full",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Pannel />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
import { dispatchSocket } from "dispatch/socket";
|
|
||||||
|
|
||||||
export const pilotSocket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
export const pilotSocket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ export const editMissionAPI = async (
|
|||||||
return respone.data;
|
return respone.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendMissionAPI = async (id: number) => {
|
||||||
|
const respone = await serverApi.post<{
|
||||||
|
message: string;
|
||||||
|
}>(`/mission/${id}/send-alert`);
|
||||||
|
return respone.data;
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteMissionAPI = async (id: number) => {
|
export const deleteMissionAPI = async (id: number) => {
|
||||||
await serverApi.delete(`/mission/${id}`);
|
await serverApi.delete(`/mission/${id}`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { prisma } from "@repo/db";
|
|
||||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
|
||||||
import { PlaneIcon } from "lucide-react";
|
|
||||||
|
|
||||||
export const PilotStats = async () => {
|
|
||||||
return (
|
|
||||||
<div className="stats shadow">
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure text-primary">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
className="inline-block h-8 w-8 stroke-current"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="stat-title">Einsätze geflogen</div>
|
|
||||||
<div className="stat-value text-primary">127</div>
|
|
||||||
<div className="stat-desc">Du bist damit unter den top 5%!</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure text-secondary">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
className="inline-block h-8 w-8 stroke-current"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="stat-title">Pilot Login Zeit</div>
|
|
||||||
<div className="stat-value text-secondary">35h 12m</div>
|
|
||||||
<div className="stat-desc">Mehr als 58% aller anderen User!</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure text-info">
|
|
||||||
<PlaneIcon className="w-8 h-8" />
|
|
||||||
</div>
|
|
||||||
<div className="stat-value text-info">Christoph 31</div>
|
|
||||||
<div className="stat-title">
|
|
||||||
War bisher dein Rettungsmittel der Wahl
|
|
||||||
</div>
|
|
||||||
<div className="stat-desc text-secondary">
|
|
||||||
87 Stationen warten noch auf dich!
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DispoStats = async () => {
|
|
||||||
const session = await getServerSession();
|
|
||||||
if (!session) return null;
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: { id: session.user.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
|
||||||
where: {
|
|
||||||
userId: user?.id,
|
|
||||||
logoutTime: {
|
|
||||||
not: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
loginTime: true,
|
|
||||||
logoutTime: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const mostDispatchedStationIds = await prisma.mission.groupBy({
|
|
||||||
where: {
|
|
||||||
createdUserId: user?.id,
|
|
||||||
},
|
|
||||||
by: ["missionStationIds"],
|
|
||||||
orderBy: {
|
|
||||||
_count: {
|
|
||||||
missionStationIds: "desc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
_count: {
|
|
||||||
missionStationIds: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let mostDispatchedStation = null;
|
|
||||||
|
|
||||||
if (mostDispatchedStationIds[0]?.missionStationIds[0]) {
|
|
||||||
mostDispatchedStation = await prisma.station.findUnique({
|
|
||||||
where: {
|
|
||||||
id: parseInt(mostDispatchedStationIds[0]?.missionStationIds[0]),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalDispatchedMissions = await prisma.mission.count({
|
|
||||||
where: {
|
|
||||||
createdUserId: user?.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalDispoTime = dispoSessions.reduce((acc, session) => {
|
|
||||||
const logoffTime = new Date(session.logoutTime!).getTime();
|
|
||||||
const logonTime = new Date(session.loginTime).getTime();
|
|
||||||
return acc + (logoffTime - logonTime);
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
const hours = Math.floor(totalDispoTime / (1000 * 60 * 60));
|
|
||||||
const minutes = Math.floor((totalDispoTime % (1000 * 60 * 60)) / (1000 * 60));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="stats shadow">
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure text-primary">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
className="inline-block h-8 w-8 stroke-current"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="stat-title">Einsätze disponiert</div>
|
|
||||||
<div className="stat-value text-primary">{totalDispatchedMissions}</div>
|
|
||||||
<div className="stat-desc">Du bist damit unter den top 9%!</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure text-secondary">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
className="inline-block h-8 w-8 stroke-current"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="stat-title">Disponent Login Zeit</div>
|
|
||||||
<div className="stat-value text-secondary">
|
|
||||||
{hours}h {minutes}m
|
|
||||||
</div>
|
|
||||||
<div className="stat-desc">Mehr als 69% aller anderen User!</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{mostDispatchedStation && (
|
|
||||||
<div className="stat">
|
|
||||||
<div className="stat-figure text-info">
|
|
||||||
<PlaneIcon className="w-8 h-8" />
|
|
||||||
</div>
|
|
||||||
<div className="stat-value text-info">
|
|
||||||
{mostDispatchedStation?.bosCallsign}
|
|
||||||
</div>
|
|
||||||
<div className="stat-title">Wurde von dir am meisten Disponiert</div>
|
|
||||||
<div className="stat-desc text-secondary">
|
|
||||||
{mostDispatchedStationIds[0]?._count.missionStationIds} Einsätze
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,292 @@
|
|||||||
/* import { useState } from "react";
|
|
||||||
import { StatsToggle } from "./StatsToggle"; */
|
|
||||||
import { StatsToggle } from "(app)/_components/StatsToggle";
|
import { StatsToggle } from "(app)/_components/StatsToggle";
|
||||||
import { DispoStats, PilotStats } from "./PilotDispoStats";
|
|
||||||
|
import { prisma } from "@repo/db";
|
||||||
|
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||||
|
import { PlaneIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export const PilotStats = async () => {
|
||||||
|
const session = await getServerSession();
|
||||||
|
if (!session) return null;
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: session.user.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const mostFlownStationsIds = await prisma.missionOnStationUsers.groupBy({
|
||||||
|
where: {
|
||||||
|
userId: user?.id,
|
||||||
|
},
|
||||||
|
by: ["stationId"],
|
||||||
|
orderBy: {
|
||||||
|
_count: {
|
||||||
|
stationId: "desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
take: 1,
|
||||||
|
_count: {
|
||||||
|
stationId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let mostFlownStation = null;
|
||||||
|
|
||||||
|
if (mostFlownStationsIds[0]?.stationId) {
|
||||||
|
mostFlownStation = await prisma.station.findUnique({
|
||||||
|
where: {
|
||||||
|
id: mostFlownStationsIds[0]?.stationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispoSessions = await prisma.connectedAircraft.findMany({
|
||||||
|
where: {
|
||||||
|
userId: user?.id,
|
||||||
|
logoutTime: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
loginTime: true,
|
||||||
|
logoutTime: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalFlownMissions = await prisma.missionOnStationUsers.count({
|
||||||
|
where: {
|
||||||
|
userId: user?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the user's rank by missions flown
|
||||||
|
const missionsFlownRanks = await prisma.missionOnStationUsers.groupBy({
|
||||||
|
by: ["userId"],
|
||||||
|
_count: { userId: true },
|
||||||
|
orderBy: { _count: { userId: "desc" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ownRankMissionsFlown =
|
||||||
|
missionsFlownRanks.findIndex((rank) => rank.userId === user?.id) + 1;
|
||||||
|
|
||||||
|
const totalUserCount = await prisma.user.count({
|
||||||
|
where: {
|
||||||
|
NOT: {
|
||||||
|
id: user?.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalStationsCount = await prisma.station.count();
|
||||||
|
|
||||||
|
const unflownStationsCount = totalStationsCount - mostFlownStationsIds.length;
|
||||||
|
|
||||||
|
const totalPilotTime = dispoSessions.reduce((acc, session) => {
|
||||||
|
const logoffTime = new Date(session.logoutTime!).getTime();
|
||||||
|
const logonTime = new Date(session.loginTime).getTime();
|
||||||
|
return acc + (logoffTime - logonTime);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const hours = Math.floor(totalPilotTime / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((totalPilotTime % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="stats shadow">
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="inline-block h-8 w-8 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="stat-title">Einsätze geflogen</div>
|
||||||
|
<div className="stat-value text-primary">{totalFlownMissions}</div>
|
||||||
|
<div className="stat-desc">
|
||||||
|
Du bist damit unter den top{" "}
|
||||||
|
{((ownRankMissionsFlown * 100) / totalUserCount).toFixed(0)}%!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-secondary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="inline-block h-8 w-8 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="stat-title">Pilot Login Zeit</div>
|
||||||
|
<div className="stat-value text-secondary">
|
||||||
|
{hours}h {minutes}min
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mostFlownStation && (
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-info">
|
||||||
|
<PlaneIcon className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div className="stat-value text-info">
|
||||||
|
{mostFlownStation?.bosCallsign}
|
||||||
|
</div>
|
||||||
|
<div className="stat-title">
|
||||||
|
War bisher dein Rettungsmittel der Wahl
|
||||||
|
</div>
|
||||||
|
{unflownStationsCount > 0 && (
|
||||||
|
<div className="stat-desc text-secondary">
|
||||||
|
{unflownStationsCount}{" "}
|
||||||
|
{unflownStationsCount > 1 ? "Stationen" : "Station"} warten noch
|
||||||
|
auf dich!
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{unflownStationsCount === 0 && (
|
||||||
|
<div className="stat-desc text-secondary">
|
||||||
|
Du hast alle Stationen geflogen! Krass...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DispoStats = async () => {
|
||||||
|
const session = await getServerSession();
|
||||||
|
if (!session) return null;
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: session.user.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
||||||
|
where: {
|
||||||
|
userId: user?.id,
|
||||||
|
logoutTime: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
loginTime: true,
|
||||||
|
logoutTime: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mostDispatchedStationIds = await prisma.mission.groupBy({
|
||||||
|
where: {
|
||||||
|
createdUserId: user?.id,
|
||||||
|
},
|
||||||
|
by: ["missionStationIds"],
|
||||||
|
orderBy: {
|
||||||
|
_count: {
|
||||||
|
missionStationIds: "desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
take: 1,
|
||||||
|
_count: {
|
||||||
|
missionStationIds: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let mostDispatchedStation = null;
|
||||||
|
|
||||||
|
if (mostDispatchedStationIds[0]?.missionStationIds[0]) {
|
||||||
|
mostDispatchedStation = await prisma.station.findUnique({
|
||||||
|
where: {
|
||||||
|
id: mostDispatchedStationIds[0]?.missionStationIds[0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalDispatchedMissions = await prisma.mission.count({
|
||||||
|
where: {
|
||||||
|
createdUserId: user?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalDispoTime = dispoSessions.reduce((acc, session) => {
|
||||||
|
const logoffTime = new Date(session.logoutTime!).getTime();
|
||||||
|
const logonTime = new Date(session.loginTime).getTime();
|
||||||
|
return acc + (logoffTime - logonTime);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const hours = Math.floor(totalDispoTime / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((totalDispoTime % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="stats shadow">
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="inline-block h-8 w-8 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="stat-title">Einsätze disponiert</div>
|
||||||
|
<div className="stat-value text-primary">{totalDispatchedMissions}</div>
|
||||||
|
<div className="stat-desc">Du bist damit unter den top 9%!</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-secondary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="inline-block h-8 w-8 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="stat-title">Disponent Login Zeit</div>
|
||||||
|
<div className="stat-value text-secondary">
|
||||||
|
{hours}h {minutes}m
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mostDispatchedStation && (
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-figure text-info">
|
||||||
|
<PlaneIcon className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div className="stat-value text-info">
|
||||||
|
{mostDispatchedStation?.bosCallsign}
|
||||||
|
</div>
|
||||||
|
<div className="stat-title">Wurde von dir am meisten Disponiert</div>
|
||||||
|
<div className="stat-desc text-secondary">
|
||||||
|
{mostDispatchedStationIds[0]?._count.missionStationIds} Einsätze
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Stats = ({ stats }: { stats: "pilot" | "dispo" }) => {
|
export const Stats = ({ stats }: { stats: "pilot" | "dispo" }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { BADGES, PERMISSION, Report, User } from "@repo/db";
|
import {
|
||||||
|
BADGES,
|
||||||
|
ConnectedAircraft,
|
||||||
|
ConnectedDispatcher,
|
||||||
|
PERMISSION,
|
||||||
|
Report,
|
||||||
|
Station,
|
||||||
|
User,
|
||||||
|
} from "@repo/db";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { deleteDispoHistory, editUser, resetPassword } from "../../action";
|
import {
|
||||||
|
deleteDispoHistory,
|
||||||
|
deletePilotHistory,
|
||||||
|
editUser,
|
||||||
|
resetPassword,
|
||||||
|
} from "../../action";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
PersonIcon,
|
PersonIcon,
|
||||||
@@ -166,59 +179,60 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
}}
|
}}
|
||||||
prismaModel={"connectedDispatcher"}
|
prismaModel={"connectedDispatcher"}
|
||||||
columns={[
|
columns={
|
||||||
{
|
[
|
||||||
accessorKey: "loginTime",
|
{
|
||||||
header: "Login",
|
accessorKey: "loginTime",
|
||||||
cell: ({ row }) => {
|
header: "Login",
|
||||||
return new Date(row.getValue("loginTime")).toLocaleString(
|
cell: ({ row }) => {
|
||||||
"de-DE",
|
return new Date(row.getValue("loginTime")).toLocaleString(
|
||||||
);
|
"de-DE",
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
header: "Time Online",
|
||||||
header: "Time Online",
|
cell: ({ row }) => {
|
||||||
cell: ({ row }) => {
|
if (row.original.logoutTime == null) {
|
||||||
console.log(row.original);
|
return <span className="text-success">Online</span>;
|
||||||
const loginTime = new Date(row.original.loginTime).getTime();
|
}
|
||||||
const logoutTime = new Date(
|
const loginTime = new Date(row.original.loginTime).getTime();
|
||||||
(row.original as any).logoutTime,
|
const logoutTime = new Date(
|
||||||
).getTime();
|
row.original.logoutTime,
|
||||||
const timeOnline = logoutTime - loginTime;
|
).getTime();
|
||||||
|
|
||||||
const hours = Math.floor(timeOnline / 1000 / 60 / 60);
|
const timeOnline = logoutTime - loginTime;
|
||||||
const minutes = Math.floor((timeOnline / 1000 / 60) % 60);
|
|
||||||
|
|
||||||
if ((row.original as any).logoutTime == null) {
|
const hours = Math.floor(timeOnline / 1000 / 60 / 60);
|
||||||
return <span className="text-success">Online</span>;
|
const minutes = Math.floor((timeOnline / 1000 / 60) % 60);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={cn(hours > 2 && "text-error")}>
|
<span className={cn(hours > 2 && "text-error")}>
|
||||||
{hours}h {minutes}min
|
{hours}h {minutes}min
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
header: "Aktionen",
|
||||||
header: "Aktionen",
|
cell: ({ row }) => {
|
||||||
cell: ({ row }) => {
|
return (
|
||||||
return (
|
<div>
|
||||||
<div>
|
<button
|
||||||
<button
|
className="btn btn-sm btn-error"
|
||||||
className="btn btn-sm btn-error"
|
onClick={async () => {
|
||||||
onClick={async () => {
|
await deleteDispoHistory(row.original.id);
|
||||||
await deleteDispoHistory((row.original as any).id);
|
dispoTableRef.current?.refresh();
|
||||||
dispoTableRef.current?.refresh();
|
}}
|
||||||
}}
|
>
|
||||||
>
|
löschen
|
||||||
löschen
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
},
|
||||||
},
|
},
|
||||||
},
|
] as ColumnDef<ConnectedDispatcher>[]
|
||||||
]}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -232,73 +246,73 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({
|
|||||||
}}
|
}}
|
||||||
prismaModel={"connectedAircraft"}
|
prismaModel={"connectedAircraft"}
|
||||||
include={{ Station: true }}
|
include={{ Station: true }}
|
||||||
columns={[
|
columns={
|
||||||
{
|
[
|
||||||
accessorKey: "Station.bosCallsign",
|
{
|
||||||
header: "Station",
|
accessorKey: "Station.bosCallsign",
|
||||||
cell: ({ row }) => {
|
header: "Station",
|
||||||
return (
|
cell: ({ row }) => {
|
||||||
<Link
|
return (
|
||||||
className="link link-hover"
|
<Link
|
||||||
href={`/admin/station/${(row.original as any).id}`}
|
className="link link-hover"
|
||||||
>
|
href={`/admin/station/${row.original.id}`}
|
||||||
{(row.original as any).Station.bosCallsign}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "loginTime",
|
|
||||||
header: "Login",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return new Date(row.getValue("loginTime")).toLocaleString(
|
|
||||||
"de-DE",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: "Time Online",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
console.log(row.original);
|
|
||||||
const loginTime = new Date(row.original.loginTime).getTime();
|
|
||||||
const logoutTime = new Date(
|
|
||||||
(row.original as any).logoutTime,
|
|
||||||
).getTime();
|
|
||||||
const timeOnline = logoutTime - loginTime;
|
|
||||||
|
|
||||||
const hours = Math.floor(timeOnline / 1000 / 60 / 60);
|
|
||||||
const minutes = Math.floor((timeOnline / 1000 / 60) % 60);
|
|
||||||
|
|
||||||
if ((row.original as any).logoutTime == null) {
|
|
||||||
return <span className="text-success">Online</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 deleteDispoHistory((row.original as any).id);
|
|
||||||
dispoTableRef.current?.refresh();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
löschen
|
{row.original.Station.bosCallsign}
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
);
|
||||||
);
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
]}
|
accessorKey: "loginTime",
|
||||||
|
header: "Login",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return new Date(row.getValue("loginTime")).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 }>[]
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,3 +44,11 @@ export const deleteDispoHistory = async (id: number) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deletePilotHistory = async (id: number) => {
|
||||||
|
return await prisma.connectedAircraft.delete({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
Binary file not shown.
@@ -13,7 +13,7 @@ model Mission {
|
|||||||
missionKeywordAbbreviation String?
|
missionKeywordAbbreviation String?
|
||||||
missionPatientInfo String
|
missionPatientInfo String
|
||||||
missionAdditionalInfo String
|
missionAdditionalInfo String
|
||||||
missionStationIds String[] @default([])
|
missionStationIds Int[] @default([])
|
||||||
missionStationUserIds String[] @default([])
|
missionStationUserIds String[] @default([])
|
||||||
missionLog Json[] @default([])
|
missionLog Json[] @default([])
|
||||||
hpgMissionString String?
|
hpgMissionString String?
|
||||||
|
|||||||
Reference in New Issue
Block a user