futher booking stuff
This commit is contained in:
@@ -18,9 +18,10 @@ export const getBookingsAPI = async (filter: Prisma.BookingWhereInput) => {
|
|||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createBookingAPI = async (booking: Prisma.BookingCreateInput) => {
|
export const createBookingAPI = async (booking: Omit<Prisma.BookingCreateInput, "User">) => {
|
||||||
const response = await axios.post("/api/bookings", booking);
|
const response = await axios.post("/api/bookings", booking);
|
||||||
if (response.status !== 201) {
|
if (response.status !== 201) {
|
||||||
|
console.error("Error creating booking:", response);
|
||||||
throw new Error("Failed to create booking");
|
throw new Error("Failed to create booking");
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|||||||
14
apps/hub/app/(app)/_querys/stations.ts
Normal file
14
apps/hub/app/(app)/_querys/stations.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Prisma, Station } from "@repo/db";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export const getStationsAPI = async (filter: Prisma.StationWhereInput) => {
|
||||||
|
const res = await axios.get<Station[]>("/api/stations", {
|
||||||
|
params: {
|
||||||
|
filter: JSON.stringify(filter),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error("Failed to fetch stations");
|
||||||
|
}
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
@@ -86,18 +86,15 @@ export const BookingTimelineModal = ({
|
|||||||
// Check if user can delete a booking
|
// Check if user can delete a booking
|
||||||
const canDeleteBooking = (bookingUserPublicId: string) => {
|
const canDeleteBooking = (bookingUserPublicId: string) => {
|
||||||
if (!currentUser) return false;
|
if (!currentUser) return false;
|
||||||
|
if (currentUser.permissions.includes("ADMIN_BOOKING")) return true;
|
||||||
|
|
||||||
// User can delete their own bookings if they meet basic requirements
|
// User can delete their own bookings if they meet basic requirements
|
||||||
if (
|
if (bookingUserPublicId === currentUser.publicId) {
|
||||||
bookingUserPublicId === currentUser.publicId &&
|
|
||||||
currentUser.emailVerified &&
|
|
||||||
!currentUser.isBanned
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admins can delete any booking
|
// Admins can delete any booking
|
||||||
return currentUser.permissions.includes("ADMIN_KICK");
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigate = (direction: "prev" | "next") => {
|
const navigate = (direction: "prev" | "next") => {
|
||||||
@@ -240,6 +237,8 @@ export const BookingTimelineModal = ({
|
|||||||
|
|
||||||
const groupedBookings = groupBookingsByResource();
|
const groupedBookings = groupBookingsByResource();
|
||||||
|
|
||||||
|
console.log("Grouped Bookings:", groupedBookings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal modal-open">
|
<div className="modal modal-open">
|
||||||
<div className="modal-box flex max-h-[83vh] w-11/12 max-w-7xl flex-col">
|
<div className="modal-box flex max-h-[83vh] w-11/12 max-w-7xl flex-col">
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { useState, 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";
|
||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { getStationsAPI } from "(app)/_querys/stations";
|
||||||
|
import { createBookingAPI } from "(app)/_querys/bookings";
|
||||||
|
|
||||||
interface Station {
|
interface Station {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -36,10 +39,24 @@ export const NewBookingModal = ({
|
|||||||
onBookingCreated,
|
onBookingCreated,
|
||||||
userPermissions,
|
userPermissions,
|
||||||
}: NewBookingModalProps) => {
|
}: NewBookingModalProps) => {
|
||||||
const [stations, setStations] = useState<Station[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { data: stations, isLoading: isLoadingStations } = useQuery({
|
||||||
|
queryKey: ["stations"],
|
||||||
|
queryFn: () => getStationsAPI({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: createBooking, isPending: isCreateBookingLoading } = useMutation({
|
||||||
|
mutationKey: ["createBooking"],
|
||||||
|
mutationFn: createBookingAPI,
|
||||||
|
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["bookings"] });
|
||||||
|
onBookingCreated();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -52,30 +69,6 @@ export const NewBookingModal = ({
|
|||||||
const selectedType = watch("type");
|
const selectedType = watch("type");
|
||||||
const hasDISPOPermission = userPermissions.includes("DISPO");
|
const hasDISPOPermission = userPermissions.includes("DISPO");
|
||||||
|
|
||||||
// Fetch stations for selection
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchStations = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/station");
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Failed to fetch stations");
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
setStations(data.stations || []);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching stations:", error);
|
|
||||||
toast.error("Fehler beim Laden der Stationen");
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
fetchStations();
|
|
||||||
}
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
// Reset form when modal opens
|
// Reset form when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
@@ -106,24 +99,9 @@ export const NewBookingModal = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch("/api/booking", {
|
const response = await createBooking(data);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
console.log("Booking created:", response);
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 409) {
|
|
||||||
toast.error(result.error || "Konflikt: Zeitraum bereits gebucht");
|
|
||||||
} else {
|
|
||||||
toast.error(result.error || "Fehler beim Erstellen der Buchung");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success("Buchung erfolgreich erstellt!");
|
toast.success("Buchung erfolgreich erstellt!");
|
||||||
onBookingCreated();
|
onBookingCreated();
|
||||||
@@ -167,10 +145,7 @@ export const NewBookingModal = ({
|
|||||||
<option value="STATION">Station</option>
|
<option value="STATION">Station</option>
|
||||||
{hasDISPOPermission && (
|
{hasDISPOPermission && (
|
||||||
<>
|
<>
|
||||||
<option value="LST_01">LST-01</option>
|
<option value="LST_01">Leitstelle</option>
|
||||||
<option value="LST_02">LST-02</option>
|
|
||||||
<option value="LST_03">LST-03</option>
|
|
||||||
<option value="LST_04">LST-04</option>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
@@ -187,7 +162,7 @@ export const NewBookingModal = ({
|
|||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text font-semibold">Station *</span>
|
<span className="label-text font-semibold">Station *</span>
|
||||||
</label>
|
</label>
|
||||||
{loading ? (
|
{isLoadingStations ? (
|
||||||
<div className="skeleton h-12 w-full"></div>
|
<div className="skeleton h-12 w-full"></div>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<select
|
||||||
@@ -198,7 +173,7 @@ export const NewBookingModal = ({
|
|||||||
className="select select-bordered w-full"
|
className="select select-bordered w-full"
|
||||||
>
|
>
|
||||||
<option value="">Station auswählen...</option>
|
<option value="">Station auswählen...</option>
|
||||||
{stations.map((station) => (
|
{stations?.map((station) => (
|
||||||
<option key={station.id} value={station.id}>
|
<option key={station.id} value={station.id}>
|
||||||
{station.bosCallsignShort} - {station.locationState} ({station.aircraft})
|
{station.bosCallsignShort} - {station.locationState} ({station.aircraft})
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const GET = async (req: NextRequest) => {
|
|||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
bookings.map((b) => ({
|
bookings.map((b) => ({
|
||||||
...b,
|
...b,
|
||||||
user: b.User && getPublicUser(b.User),
|
User: b.User ? getPublicUser(b.User) : null,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const GET = async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({ stations });
|
return NextResponse.json(stations);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching stations:", error);
|
console.error("Error fetching stations:", error);
|
||||||
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "PERMISSION" ADD VALUE 'ADMIN_BOOKING';
|
||||||
@@ -19,6 +19,7 @@ enum PERMISSION {
|
|||||||
ADMIN_KICK
|
ADMIN_KICK
|
||||||
ADMIN_HELIPORT
|
ADMIN_HELIPORT
|
||||||
ADMIN_CHANGELOG
|
ADMIN_CHANGELOG
|
||||||
|
ADMIN_BOOKING
|
||||||
AUDIO
|
AUDIO
|
||||||
PILOT
|
PILOT
|
||||||
DISPO
|
DISPO
|
||||||
|
|||||||
Reference in New Issue
Block a user