From dc4a3ab4d8afbbeaf947ac65e206756fa079fe5a Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Sun, 28 Sep 2025 12:19:14 +0200 Subject: [PATCH] Buchungen werden in Connect Modal angezeigt (Pilot), new Booking form improvement --- .../navbar/_components/Connection.tsx | 66 ++++++++++++++----- apps/dispatch/app/_querys/bookings.ts | 36 ++++++++++ apps/dispatch/app/api/bookings/route.ts | 42 ++++++++++++ apps/hub/app/_components/NewBookingModal.tsx | 23 +++---- 4 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 apps/dispatch/app/_querys/bookings.ts create mode 100644 apps/dispatch/app/api/bookings/route.ts diff --git a/apps/dispatch/app/(app)/pilot/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/(app)/pilot/_components/navbar/_components/Connection.tsx index fd193ee8..2915884f 100644 --- a/apps/dispatch/app/(app)/pilot/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/(app)/pilot/_components/navbar/_components/Connection.tsx @@ -6,9 +6,10 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { getStationsAPI } from "_querys/stations"; import { editConnectedAircraftAPI, getConnectedAircraftsAPI } from "_querys/aircrafts"; import { Prisma } from "@repo/db"; -import { Button, getNextDateWithTime } from "@repo/shared-components"; +import { Button, cn, getNextDateWithTime } from "@repo/shared-components"; import { Select } from "_components/Select"; -import { Radio } from "lucide-react"; +import { Calendar, Radio } from "lucide-react"; +import { getBookingsAPI } from "_querys/bookings"; export const ConnectionBtn = () => { const modalRef = useRef(null); @@ -27,6 +28,19 @@ export const ConnectionBtn = () => { queryKey: ["stations"], queryFn: () => getStationsAPI(), }); + const { data: bookings } = useQuery({ + queryKey: ["bookings"], + queryFn: () => + getBookingsAPI({ + startTime: { + lte: new Date(Date.now() + 8 * 60 * 60 * 1000), + }, + endTime: { + gte: new Date(), + }, + }), + }); + const aircraftMutation = useMutation({ mutationFn: ({ change, @@ -62,6 +76,7 @@ export const ConnectionBtn = () => { const session = useSession(); const uid = session.data?.user?.id; if (!uid) return null; + console.log(bookings); return (
{connection.message.length > 0 && ( @@ -117,20 +132,39 @@ export const ConnectionBtn = () => { (option as { component: React.ReactNode }).component } options={ - stations?.map((station) => ({ - value: station.id.toString(), - label: station.bosCallsign, - component: ( -
- - {connectedAircrafts?.find((a) => a.stationId == station.id) && ( - - )} - {station.bosCallsign} - -
- ), - })) ?? [] + stations?.map((station) => { + const booking = bookings?.find((b) => b.stationId == station.id); + return { + value: station.id.toString(), + label: station.bosCallsign, + component: ( +
+ + {connectedAircrafts?.find((a) => a.stationId == station.id) && ( + + )} + {booking && ( +
+ +
+ )} + {station.bosCallsign} +
+
+ ), + }; + }) ?? [] } />
diff --git a/apps/dispatch/app/_querys/bookings.ts b/apps/dispatch/app/_querys/bookings.ts new file mode 100644 index 00000000..b13805d2 --- /dev/null +++ b/apps/dispatch/app/_querys/bookings.ts @@ -0,0 +1,36 @@ +import { Booking, Prisma, PublicUser, Station } from "@repo/db"; +import axios from "axios"; + +export const getBookingsAPI = async (filter: Prisma.BookingWhereInput) => { + const res = await axios.get< + (Booking & { + Station: Station; + User: PublicUser; + })[] + >("/api/bookings", { + params: { + filter: JSON.stringify(filter), + }, + }); + if (res.status !== 200) { + throw new Error("Failed to fetch stations"); + } + return res.data; +}; + +export const createBookingAPI = async (booking: Omit) => { + const response = await axios.post("/api/bookings", booking); + if (response.status !== 201) { + console.error("Error creating booking:", response); + throw new Error("Failed to create booking"); + } + return response.data; +}; + +export const deleteBookingAPI = async (bookingId: string) => { + const response = await axios.delete(`/api/bookings/${bookingId}`); + if (!response.status.toString().startsWith("2")) { + throw new Error("Failed to delete booking"); + } + return bookingId; +}; diff --git a/apps/dispatch/app/api/bookings/route.ts b/apps/dispatch/app/api/bookings/route.ts new file mode 100644 index 00000000..592eae6c --- /dev/null +++ b/apps/dispatch/app/api/bookings/route.ts @@ -0,0 +1,42 @@ +import { getPublicUser, prisma } from "@repo/db"; +import { getServerSession } from "api/auth/[...nextauth]/auth"; +import { NextRequest, NextResponse } from "next/server"; + +export const GET = async (req: NextRequest) => { + try { + const session = await getServerSession(); + if (!session?.user) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); + } + + const { searchParams } = req.nextUrl; + const filter = JSON.parse(searchParams.get("filter") || "{}"); + + const bookings = await prisma.booking.findMany({ + where: filter, + include: { + User: true, + Station: { + select: { + id: true, + bosCallsign: true, + bosCallsignShort: true, + }, + }, + }, + orderBy: { + startTime: "asc", + }, + }); + + return NextResponse.json( + bookings.map((b) => ({ + ...b, + User: b.User ? getPublicUser(b.User) : null, + })), + ); + } catch (error) { + console.error("Error fetching bookings:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +}; diff --git a/apps/hub/app/_components/NewBookingModal.tsx b/apps/hub/app/_components/NewBookingModal.tsx index 7b9e7197..fa0e0593 100644 --- a/apps/hub/app/_components/NewBookingModal.tsx +++ b/apps/hub/app/_components/NewBookingModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { X, CalendarIcon, Clock } from "lucide-react"; import toast from "react-hot-toast"; @@ -10,15 +10,6 @@ import { createBookingAPI } from "(app)/_querys/bookings"; import { Button } from "@repo/shared-components"; import { useRouter } from "next/navigation"; -interface Station { - id: number; - bosCallsign: string; - bosCallsignShort: string; - locationState: string; - operator: string; - aircraft: string; -} - interface NewBookingFormData { type: "STATION" | "LST_01" | "LST_02" | "LST_03" | "LST_04"; stationId?: number; @@ -197,7 +188,17 @@ export const NewBookingModal = ({ { + if (new Date(e.target.value) >= new Date(watch("endTime"))) { + const newEndTime = new Date( + new Date(e.target.value).getTime() + 60 * 60 * 3000, + ); + setValue("endTime", newEndTime.toISOString().slice(0, 16)); + } + }, + })} className="input input-bordered w-full" /> {errors.startTime && (