Release v2.0.4 #140

Merged
PxlLoewe merged 30 commits from staging into release 2025-12-08 18:48:53 +00:00
8 changed files with 50 additions and 58 deletions
Showing only changes of commit cf199150fe - Show all commits

View File

@@ -18,9 +18,10 @@ export const getBookingsAPI = async (filter: Prisma.BookingWhereInput) => {
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);
if (response.status !== 201) {
console.error("Error creating booking:", response);
throw new Error("Failed to create booking");
}
return response.data;

View 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;
};

View File

@@ -86,18 +86,15 @@ export const BookingTimelineModal = ({
// Check if user can delete a booking
const canDeleteBooking = (bookingUserPublicId: string) => {
if (!currentUser) return false;
if (currentUser.permissions.includes("ADMIN_BOOKING")) return true;
// User can delete their own bookings if they meet basic requirements
if (
bookingUserPublicId === currentUser.publicId &&
currentUser.emailVerified &&
!currentUser.isBanned
) {
if (bookingUserPublicId === currentUser.publicId) {
return true;
}
// Admins can delete any booking
return currentUser.permissions.includes("ADMIN_KICK");
return false;
};
const navigate = (direction: "prev" | "next") => {
@@ -240,6 +237,8 @@ export const BookingTimelineModal = ({
const groupedBookings = groupBookingsByResource();
console.log("Grouped Bookings:", groupedBookings);
return (
<div className="modal modal-open">
<div className="modal-box flex max-h-[83vh] w-11/12 max-w-7xl flex-col">

View File

@@ -4,6 +4,9 @@ import { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { X, CalendarIcon, Clock } from "lucide-react";
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 {
id: number;
@@ -36,10 +39,24 @@ export const NewBookingModal = ({
onBookingCreated,
userPermissions,
}: NewBookingModalProps) => {
const [stations, setStations] = useState<Station[]>([]);
const [loading, setLoading] = 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 {
register,
handleSubmit,
@@ -52,30 +69,6 @@ export const NewBookingModal = ({
const selectedType = watch("type");
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
useEffect(() => {
if (isOpen) {
@@ -106,24 +99,9 @@ export const NewBookingModal = ({
return;
}
const response = await fetch("/api/booking", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
const response = await createBooking(data);
const result = await response.json();
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;
}
console.log("Booking created:", response);
toast.success("Buchung erfolgreich erstellt!");
onBookingCreated();
@@ -167,10 +145,7 @@ export const NewBookingModal = ({
<option value="STATION">Station</option>
{hasDISPOPermission && (
<>
<option value="LST_01">LST-01</option>
<option value="LST_02">LST-02</option>
<option value="LST_03">LST-03</option>
<option value="LST_04">LST-04</option>
<option value="LST_01">Leitstelle</option>
</>
)}
</select>
@@ -187,7 +162,7 @@ export const NewBookingModal = ({
<label className="label">
<span className="label-text font-semibold">Station *</span>
</label>
{loading ? (
{isLoadingStations ? (
<div className="skeleton h-12 w-full"></div>
) : (
<select
@@ -198,7 +173,7 @@ export const NewBookingModal = ({
className="select select-bordered w-full"
>
<option value="">Station auswählen...</option>
{stations.map((station) => (
{stations?.map((station) => (
<option key={station.id} value={station.id}>
{station.bosCallsignShort} - {station.locationState} ({station.aircraft})
</option>

View File

@@ -33,7 +33,7 @@ export const GET = async (req: NextRequest) => {
return NextResponse.json(
bookings.map((b) => ({
...b,
user: b.User && getPublicUser(b.User),
User: b.User ? getPublicUser(b.User) : null,
})),
);
} catch (error) {

View File

@@ -24,7 +24,7 @@ export const GET = async () => {
},
});
return NextResponse.json({ stations });
return NextResponse.json(stations);
} catch (error) {
console.error("Error fetching stations:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });

View File

@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "PERMISSION" ADD VALUE 'ADMIN_BOOKING';

View File

@@ -19,6 +19,7 @@ enum PERMISSION {
ADMIN_KICK
ADMIN_HELIPORT
ADMIN_CHANGELOG
ADMIN_BOOKING
AUDIO
PILOT
DISPO