getNextHourTime zu shared library hinzugefügt

This commit is contained in:
PxlLoewe
2025-06-27 22:54:31 -07:00
parent dc92174798
commit ec22cdb987
14 changed files with 89 additions and 62 deletions

View File

@@ -12,6 +12,7 @@
"devDependencies": { "devDependencies": {
"@repo/db": "workspace:*", "@repo/db": "workspace:*",
"@repo/typescript-config": "workspace:*", "@repo/typescript-config": "workspace:*",
"@repo/shared-components": "workspace:*",
"@types/cookie-parser": "^1.4.8", "@types/cookie-parser": "^1.4.8",
"@types/cors": "^2.8.18", "@types/cors": "^2.8.18",
"@types/express": "^5.0.2", "@types/express": "^5.0.2",

View File

@@ -1,5 +1,6 @@
import { getPublicUser, prisma, User } from "@repo/db"; import { getPublicUser, prisma, User } from "@repo/db";
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord"; import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
import { getNextDateWithTime } from "@repo/shared-components";
import { DISCORD_ROLES } from "@repo/db"; import { DISCORD_ROLES } from "@repo/db";
import { Server, Socket } from "socket.io"; import { Server, Socket } from "socket.io";
@@ -45,30 +46,13 @@ export const handleConnectDispatch =
} }
let parsedLogoffDate = null; let parsedLogoffDate = null;
if (logoffTime.length > 0) { const [logoffHours, logoffMinutes] = logoffTime.split(":").map(Number);
const now = new Date();
const [hours, minutes] = logoffTime.split(":").map(Number);
if (!hours || !minutes) {
throw new Error("Invalid logoffTime format");
}
parsedLogoffDate = new Date(now);
parsedLogoffDate.setHours(hours, minutes, 0, 0);
// If the calculated time is earlier than now, add one day to make it tomorrow
if (parsedLogoffDate <= now) {
parsedLogoffDate.setDate(parsedLogoffDate.getDate() + 1);
}
// If the calculated time is in the past, add one day to make it in the future
if (parsedLogoffDate <= now) {
parsedLogoffDate.setDate(parsedLogoffDate.getDate() + 1);
}
}
const connectedDispatcherEntry = await prisma.connectedDispatcher.create({ const connectedDispatcherEntry = await prisma.connectedDispatcher.create({
data: { data: {
publicUser: getPublicUser(user) as any, publicUser: getPublicUser(user) as any,
esimatedLogoutTime: parsedLogoffDate?.toISOString() || null, esimatedLogoutTime:
logoffHours && logoffMinutes ? getNextDateWithTime(logoffHours, logoffMinutes) : null,
lastHeartbeat: new Date().toISOString(), lastHeartbeat: new Date().toISOString(),
userId: user.id, userId: user.id,
zone: selectedZone, zone: selectedZone,

View File

@@ -1,5 +1,6 @@
import { getPublicUser, prisma, User } from "@repo/db"; import { getPublicUser, prisma, User } from "@repo/db";
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord"; import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
import { getNextDateWithTime } from "@repo/shared-components";
import { DISCORD_ROLES } from "@repo/db"; import { DISCORD_ROLES } from "@repo/db";
import { Server, Socket } from "socket.io"; import { Server, Socket } from "socket.io";
@@ -55,22 +56,6 @@ export const handleConnectPilot =
}); });
} }
let parsedLogoffDate = null;
if (logoffTime.length > 0) {
const now = new Date();
const [hours, minutes] = logoffTime.split(":").map(Number);
if (!hours || !minutes) {
throw new Error("Invalid logoffTime format");
}
parsedLogoffDate = new Date(now);
parsedLogoffDate.setHours(hours, minutes, 0, 0);
// If the calculated time is earlier than now, add one day to make it tomorrow
if (parsedLogoffDate <= now) {
parsedLogoffDate.setDate(parsedLogoffDate.getDate() + 1);
}
}
// Set "now" to 2 hours in the future // Set "now" to 2 hours in the future
const nowPlus2h = new Date(); const nowPlus2h = new Date();
nowPlus2h.setHours(nowPlus2h.getHours() + 2); nowPlus2h.setHours(nowPlus2h.getHours() + 2);
@@ -87,11 +72,13 @@ export const handleConnectPilot =
} }
const randomPos = debug ? getRandomGermanPosition() : undefined; const randomPos = debug ? getRandomGermanPosition() : undefined;
const [logoffHours, logoffMinutes] = logoffTime.split(":").map(Number);
const connectedAircraftEntry = await prisma.connectedAircraft.create({ const connectedAircraftEntry = await prisma.connectedAircraft.create({
data: { data: {
publicUser: getPublicUser(user) as any, publicUser: getPublicUser(user) as any,
esimatedLogoutTime: parsedLogoffDate?.toISOString() || null, esimatedLogoutTime:
logoffHours && logoffMinutes ? getNextDateWithTime(logoffHours, logoffMinutes) : null,
userId: userId, userId: userId,
stationId: parseInt(stationId), stationId: parseInt(stationId),
lastHeartbeat: debug ? nowPlus2h.toISOString() : undefined, lastHeartbeat: debug ? nowPlus2h.toISOString() : undefined,

View File

@@ -2,9 +2,10 @@
"extends": "@repo/typescript-config/base.json", "extends": "@repo/typescript-config/base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"allowImportingTsExtensions": false, "module": "ESNext",
"baseUrl": ".", "moduleResolution": "bundler",
"jsx": "react" "noEmit": true,
"baseUrl": "."
}, },
"include": ["**/*.ts", "./index.ts", "**/*.d.ts"], "include": ["**/*.ts", "./index.ts", "**/*.d.ts"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]

View File

@@ -6,6 +6,7 @@ import { toast } from "react-hot-toast";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { Prisma } from "@repo/db"; import { Prisma } from "@repo/db";
import { changeDispatcherAPI } from "_querys/dispatcher"; import { changeDispatcherAPI } from "_querys/dispatcher";
import { getNextDateWithTime } from "@repo/shared-components";
export const ConnectionBtn = () => { export const ConnectionBtn = () => {
const modalRef = useRef<HTMLDialogElement>(null); const modalRef = useRef<HTMLDialogElement>(null);
@@ -24,17 +25,19 @@ export const ConnectionBtn = () => {
if (!uid) return null; if (!uid) return null;
// useEffect für die Logoff-Zeit // useEffect für die Logoff-Zeit
const [logoffHours, logoffMinutes] = form.logoffTime?.split(":").map(Number) || [];
useEffect(() => { useEffect(() => {
if (!logoffHours || !logoffMinutes) return;
if (logoffDebounce) clearTimeout(logoffDebounce); if (logoffDebounce) clearTimeout(logoffDebounce);
const timeout = setTimeout(async () => { const timeout = setTimeout(async () => {
if (!form.logoffTime || !connection.connectedDispatcher) return; if (!logoffHours || !logoffMinutes || !connection.connectedDispatcher) return;
await changeDispatcherMutation.mutateAsync({ await changeDispatcherMutation.mutateAsync({
id: connection.connectedDispatcher?.id, id: connection.connectedDispatcher?.id,
data: { data: {
esimatedLogoutTime: new Date( esimatedLogoutTime:
new Date().toDateString() + " " + form.logoffTime, logoffHours && logoffMinutes ? getNextDateWithTime(logoffHours, logoffMinutes) : null,
).toISOString(),
}, },
}); });
toast.success("Änderung gespeichert!"); toast.success("Änderung gespeichert!");

View File

@@ -8,6 +8,7 @@ import toast from "react-hot-toast";
import { editConnectedAircraftAPI } from "_querys/aircrafts"; import { editConnectedAircraftAPI } from "_querys/aircrafts";
import { Prisma } from "@repo/db"; import { Prisma } from "@repo/db";
import { debug } from "console"; import { debug } from "console";
import { getNextDateWithTime } from "@repo/shared-components";
export const ConnectionBtn = () => { export const ConnectionBtn = () => {
const modalRef = useRef<HTMLDialogElement>(null); const modalRef = useRef<HTMLDialogElement>(null);
@@ -53,10 +54,10 @@ export const ConnectionBtn = () => {
}; };
}, [connection.disconnect]); }, [connection.disconnect]);
const logoffTime = form.logoffTime; const [logoffHours, logoffMinutes] = form.logoffTime?.split(":").map(Number) || [];
useEffect(() => { useEffect(() => {
if (!logoffTime || !connection.connectedAircraft) return; if (!logoffHours || !logoffMinutes || !connection.connectedAircraft) return;
if (logoffDebounce) clearTimeout(logoffDebounce); if (logoffDebounce) clearTimeout(logoffDebounce);
@@ -65,9 +66,8 @@ export const ConnectionBtn = () => {
await aircraftMutation.mutateAsync({ await aircraftMutation.mutateAsync({
sessionId: connection.connectedAircraft.id, sessionId: connection.connectedAircraft.id,
change: { change: {
esimatedLogoutTime: logoffTime esimatedLogoutTime:
? new Date(new Date().toDateString() + " " + logoffTime).toISOString() logoffHours && logoffMinutes ? getNextDateWithTime(logoffHours, logoffMinutes) : null,
: null,
}, },
}); });
modalRef.current?.close(); modalRef.current?.close();
@@ -80,7 +80,7 @@ export const ConnectionBtn = () => {
return () => { return () => {
if (logoffDebounce) clearTimeout(logoffDebounce); if (logoffDebounce) clearTimeout(logoffDebounce);
}; };
}, [logoffTime, connection.connectedAircraft]); }, [logoffHours, logoffMinutes, connection.connectedAircraft]);
const session = useSession(); const session = useSession();
const uid = session.data?.user?.id; const uid = session.data?.user?.id;

View File

@@ -1,7 +1,9 @@
"use client"; "use client";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors"; import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
import { Badge, cn } from "@repo/shared-components";
import { import {
BADGES,
ConnectedAircraft, ConnectedAircraft,
getPublicUser, getPublicUser,
Mission, Mission,
@@ -16,7 +18,6 @@ import { toast } from "react-hot-toast";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { editConnectedAircraftAPI } from "_querys/aircrafts"; import { editConnectedAircraftAPI } from "_querys/aircrafts";
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore"; import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
import { cn } from "@repo/shared-components";
import { PersonIcon } from "@radix-ui/react-icons"; import { PersonIcon } from "@radix-ui/react-icons";
import { import {
Ban, Ban,
@@ -42,6 +43,8 @@ import { useSession } from "next-auth/react";
import { sendSdsMessageAPI } from "_querys/missions"; import { sendSdsMessageAPI } from "_querys/missions";
import { getLivekitRooms } from "_querys/livekit"; import { getLivekitRooms } from "_querys/livekit";
import { findLeitstelleForPosition } from "_helpers/findLeitstelleinPoint"; import { findLeitstelleForPosition } from "_helpers/findLeitstelleinPoint";
import { formatDistance } from "date-fns";
import { de } from "date-fns/locale";
const FMSStatusHistory = ({ const FMSStatusHistory = ({
aircraft, aircraft,
@@ -63,9 +66,22 @@ const FMSStatusHistory = ({
<ul className="text-base-content font-semibold"> <ul className="text-base-content font-semibold">
<li className="flex items-center gap-2 mb-1"> <li className="flex items-center gap-2 mb-1">
<p className="flex items-center gap-2 flex-1"> <p className="flex items-center gap-2 flex-1">
<PersonIcon className="w-5 h-5" /> {aircraftUser.fullName} ({aircraftUser.publicId}) <PersonIcon className="w-5 h-5" /> {aircraftUser.fullName} ({aircraftUser.publicId}){" "}
{(() => {
const badges = aircraftUser.badges
.filter((b) => b.startsWith("P") && b.length == 2)
.sort((a, b) => a.localeCompare(b));
const lastBadge = badges[badges.length - 1];
return lastBadge ? <Badge badge={lastBadge as BADGES} className="h-8 w-12" /> : null;
})()}
</p> </p>
<p className="text-sm font-thin text-gray-500"> <p
className={cn(
"text-sm font-thin text-gray-500",
aircraft.esimatedLogoutTime && "tooltip tooltip-left",
)}
data-tip={`in ${aircraft.esimatedLogoutTime && formatDistance(new Date(aircraft.esimatedLogoutTime), new Date(), { locale: de })}`}
>
{aircraft.esimatedLogoutTime {aircraft.esimatedLogoutTime
? `Geplante Abmeldung: ${new Date(aircraft.esimatedLogoutTime).toLocaleTimeString( ? `Geplante Abmeldung: ${new Date(aircraft.esimatedLogoutTime).toLocaleTimeString(
[], [],

View File

@@ -16,8 +16,6 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
Station: true, Station: true,
}, },
}); });
console.log("Filter applied:", filter, connectedAircraft);
return NextResponse.json(connectedAircraft, { return NextResponse.json(connectedAircraft, {
status: 200, status: 200,
}); });

View File

@@ -3,6 +3,8 @@ import { useQuery } from "@tanstack/react-query";
import { getConnectedAircraftsAPI } from "_querys/aircrafts"; import { getConnectedAircraftsAPI } from "_querys/aircrafts";
import { getConnectedDispatcherAPI } from "_querys/dispatcher"; import { getConnectedDispatcherAPI } from "_querys/dispatcher";
import { Plane, Workflow } from "lucide-react"; import { Plane, Workflow } from "lucide-react";
import { formatDistance } from "date-fns";
import { de } from "date-fns/locale";
import { Badge } from "@repo/shared-components"; import { Badge } from "@repo/shared-components";
export const ConnectedDispatcher = () => { export const ConnectedDispatcher = () => {
@@ -59,7 +61,10 @@ export const ConnectedDispatcher = () => {
</p> </p>
</div> </div>
{d.esimatedLogoutTime && ( {d.esimatedLogoutTime && (
<div className="tooltip tooltip-right" data-tip="Vorrausichtliche Abmeldung"> <div
className="tooltip tooltip-right"
data-tip={`vorraussichtliche Abmeldung in ${formatDistance(new Date(), new Date(d.esimatedLogoutTime), { locale: de })}`}
>
<p className="text-gray-500 font-thin "> <p className="text-gray-500 font-thin ">
{new Date(d.esimatedLogoutTime).toLocaleTimeString([], { {new Date(d.esimatedLogoutTime).toLocaleTimeString([], {
hour: "2-digit", hour: "2-digit",
@@ -74,11 +79,15 @@ export const ConnectedDispatcher = () => {
<div className="text-xs uppercase font-semibold opacity-60">{d.zone}</div> <div className="text-xs uppercase font-semibold opacity-60">{d.zone}</div>
</div> </div>
<div> <div>
{(d.publicUser as unknown as PublicUser).badges {(() => {
.filter((b) => b.startsWith("D")) const badges = (d.publicUser as unknown as PublicUser).badges
.map((b) => ( .filter((b) => b.startsWith("D") && b.length == 2)
<Badge badge={b as BADGES} className="h-8 w-12" /> .sort((a, b) => a.localeCompare(b));
))} const lastBadge = badges[badges.length - 1];
return lastBadge ? (
<Badge badge={lastBadge as BADGES} className="h-8 w-12" />
) : null;
})()}
</div> </div>
</li> </li>
); );

View File

@@ -19,8 +19,8 @@
"@next-auth/prisma-adapter": "^1.0.7", "@next-auth/prisma-adapter": "^1.0.7",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@repo/db": "workspace:*", "@repo/db": "workspace:*",
"@repo/shared-components": "workspace:*",
"@repo/eslint-config": "workspace:*", "@repo/eslint-config": "workspace:*",
"@repo/shared-components": "workspace:*",
"@repo/typescript-config": "workspace:*", "@repo/typescript-config": "workspace:*",
"@tailwindcss/postcss": "^4.1.8", "@tailwindcss/postcss": "^4.1.8",
"@tanstack/react-query": "^5.79.0", "@tanstack/react-query": "^5.79.0",
@@ -33,6 +33,7 @@
"axios": "^1.9.0", "axios": "^1.9.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"daisyui": "^5.0.43", "daisyui": "^5.0.43",
"date-fns": "^4.1.0",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"i": "^0.3.7", "i": "^0.3.7",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",

View File

@@ -0,0 +1,16 @@
import { set, isBefore, addDays } from "date-fns";
export function getNextDateWithTime(targetHour: number, targetMinute: number): Date {
const now = new Date();
let targetDate = set(now, {
hours: targetHour,
minutes: targetMinute,
seconds: 0,
milliseconds: 0,
});
if (!isBefore(now, targetDate)) {
targetDate = addDays(targetDate, 1);
}
return targetDate;
}

View File

@@ -1,2 +1,3 @@
export * from "./cn"; export * from "./cn";
export * from "./event"; export * from "./event";
export * from "./dates";

View File

@@ -11,6 +11,7 @@
"@repo/typescript-config": "workspace:*", "@repo/typescript-config": "workspace:*",
"@types/node": "^22.15.29", "@types/node": "^22.15.29",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0",
"tailwind-merge": "^3.3.0" "tailwind-merge": "^3.3.0"
}, },
"devDependencies": { "devDependencies": {

9
pnpm-lock.yaml generated
View File

@@ -141,6 +141,9 @@ importers:
daisyui: daisyui:
specifier: ^5.0.43 specifier: ^5.0.43
version: 5.0.43 version: 5.0.43
date-fns:
specifier: ^4.1.0
version: 4.1.0
geojson: geojson:
specifier: ^0.5.0 specifier: ^0.5.0
version: 0.5.0 version: 0.5.0
@@ -283,6 +286,9 @@ importers:
'@repo/db': '@repo/db':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/database version: link:../../packages/database
'@repo/shared-components':
specifier: workspace:*
version: link:../../packages/shared-components
'@repo/typescript-config': '@repo/typescript-config':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/typescript-config version: link:../../packages/typescript-config
@@ -589,6 +595,9 @@ importers:
clsx: clsx:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
date-fns:
specifier: ^4.1.0
version: 4.1.0
tailwind-merge: tailwind-merge:
specifier: ^3.3.0 specifier: ^3.3.0
version: 3.3.0 version: 3.3.0