Buchungen werden in Connect Modal angezeigt (Pilot), new Booking form improvement
This commit is contained in:
@@ -6,9 +6,10 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
|||||||
import { getStationsAPI } from "_querys/stations";
|
import { getStationsAPI } from "_querys/stations";
|
||||||
import { editConnectedAircraftAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
import { editConnectedAircraftAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { Prisma } from "@repo/db";
|
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 { Select } from "_components/Select";
|
||||||
import { Radio } from "lucide-react";
|
import { Calendar, Radio } from "lucide-react";
|
||||||
|
import { getBookingsAPI } from "_querys/bookings";
|
||||||
|
|
||||||
export const ConnectionBtn = () => {
|
export const ConnectionBtn = () => {
|
||||||
const modalRef = useRef<HTMLDialogElement>(null);
|
const modalRef = useRef<HTMLDialogElement>(null);
|
||||||
@@ -27,6 +28,19 @@ export const ConnectionBtn = () => {
|
|||||||
queryKey: ["stations"],
|
queryKey: ["stations"],
|
||||||
queryFn: () => getStationsAPI(),
|
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({
|
const aircraftMutation = useMutation({
|
||||||
mutationFn: ({
|
mutationFn: ({
|
||||||
change,
|
change,
|
||||||
@@ -62,6 +76,7 @@ export const ConnectionBtn = () => {
|
|||||||
const session = useSession();
|
const session = useSession();
|
||||||
const uid = session.data?.user?.id;
|
const uid = session.data?.user?.id;
|
||||||
if (!uid) return null;
|
if (!uid) return null;
|
||||||
|
console.log(bookings);
|
||||||
return (
|
return (
|
||||||
<div className="rounded-box bg-base-200 flex items-center justify-center gap-2 p-1">
|
<div className="rounded-box bg-base-200 flex items-center justify-center gap-2 p-1">
|
||||||
{connection.message.length > 0 && (
|
{connection.message.length > 0 && (
|
||||||
@@ -117,7 +132,9 @@ export const ConnectionBtn = () => {
|
|||||||
(option as { component: React.ReactNode }).component
|
(option as { component: React.ReactNode }).component
|
||||||
}
|
}
|
||||||
options={
|
options={
|
||||||
stations?.map((station) => ({
|
stations?.map((station) => {
|
||||||
|
const booking = bookings?.find((b) => b.stationId == station.id);
|
||||||
|
return {
|
||||||
value: station.id.toString(),
|
value: station.id.toString(),
|
||||||
label: station.bosCallsign,
|
label: station.bosCallsign,
|
||||||
component: (
|
component: (
|
||||||
@@ -126,11 +143,28 @@ export const ConnectionBtn = () => {
|
|||||||
{connectedAircrafts?.find((a) => a.stationId == station.id) && (
|
{connectedAircrafts?.find((a) => a.stationId == station.id) && (
|
||||||
<Radio className="text-warning" size={15} />
|
<Radio className="text-warning" size={15} />
|
||||||
)}
|
)}
|
||||||
|
{booking && (
|
||||||
|
<div
|
||||||
|
className="tooltip tooltip-right"
|
||||||
|
data-tip={`${new Date(booking.startTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} - ${new Date(booking.endTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} Uhr gebucht von ${booking.userId == session.data?.user?.id ? "dir" : booking.User.fullName}`}
|
||||||
|
>
|
||||||
|
<Calendar
|
||||||
|
className={
|
||||||
|
cn(
|
||||||
|
"text-warning",
|
||||||
|
booking?.userId === session.data?.user?.id,
|
||||||
|
) && "text-success"
|
||||||
|
}
|
||||||
|
size={15}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{station.bosCallsign}
|
{station.bosCallsign}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
})) ?? []
|
};
|
||||||
|
}) ?? []
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
36
apps/dispatch/app/_querys/bookings.ts
Normal file
36
apps/dispatch/app/_querys/bookings.ts
Normal file
@@ -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<Prisma.BookingCreateInput, "User">) => {
|
||||||
|
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;
|
||||||
|
};
|
||||||
42
apps/dispatch/app/api/bookings/route.ts
Normal file
42
apps/dispatch/app/api/bookings/route.ts
Normal file
@@ -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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { X, CalendarIcon, Clock } from "lucide-react";
|
import { X, CalendarIcon, Clock } from "lucide-react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
@@ -10,15 +10,6 @@ import { createBookingAPI } from "(app)/_querys/bookings";
|
|||||||
import { Button } from "@repo/shared-components";
|
import { Button } from "@repo/shared-components";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
interface Station {
|
|
||||||
id: number;
|
|
||||||
bosCallsign: string;
|
|
||||||
bosCallsignShort: string;
|
|
||||||
locationState: string;
|
|
||||||
operator: string;
|
|
||||||
aircraft: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NewBookingFormData {
|
interface NewBookingFormData {
|
||||||
type: "STATION" | "LST_01" | "LST_02" | "LST_03" | "LST_04";
|
type: "STATION" | "LST_01" | "LST_02" | "LST_03" | "LST_04";
|
||||||
stationId?: number;
|
stationId?: number;
|
||||||
@@ -197,7 +188,17 @@ export const NewBookingModal = ({
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
{...register("startTime", { required: "Startzeit ist erforderlich" })}
|
{...register("startTime", {
|
||||||
|
required: "Startzeit ist erforderlich",
|
||||||
|
onChange: (e) => {
|
||||||
|
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"
|
className="input input-bordered w-full"
|
||||||
/>
|
/>
|
||||||
{errors.startTime && (
|
{errors.startTime && (
|
||||||
|
|||||||
Reference in New Issue
Block a user