feat: Equipment-System, Buchungsbearbeitung, Kundenadresse, LexOffice-Fix

- Vintage Modell hinzugefuegt
- Equipment Multi-Select (Neue Buchung + Bearbeitung)
- Kundenadresse in Formularen
- Bearbeiten-Seite fuer Buchungen
- Abbau-Zeiten in Formularen und Uebersicht
- Vertrag PDF nur bei Privatkunden
- LexOffice Kontakt-Erstellung Fix (BUSINESS)
- Zurueck-Pfeil auf Touren-Seite
This commit is contained in:
Julia Wehden
2026-03-19 16:21:55 +01:00
parent 0b6e429329
commit a2c95c70e7
79 changed files with 7396 additions and 538 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, useEffect } from "react";
import { FiCalendar, FiMapPin } from "react-icons/fi";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -17,6 +17,21 @@ export default function NewBookingForm({
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [equipmentList, setEquipmentList] = useState<any[]>([]);
const [selectedEquipment, setSelectedEquipment] = useState<string[]>([]);
useEffect(() => {
fetch("/api/equipment")
.then((res) => res.json())
.then((data) => setEquipmentList(data.equipment || []))
.catch(() => {});
}, []);
const toggleEquipment = (id: string) => {
setSelectedEquipment((prev) =>
prev.includes(id) ? prev.filter((e) => e !== id) : [...prev, id]
);
};
const [formData, setFormData] = useState({
locationId: "",
@@ -51,7 +66,7 @@ export default function NewBookingForm({
const res = await fetch("/api/bookings/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
body: JSON.stringify({ ...formData, equipmentIds: selectedEquipment }),
});
const data = await res.json();
@@ -125,6 +140,7 @@ export default function NewBookingForm({
required
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
>
<option value="VINTAGE">Vintage</option>
<option value="VINTAGE_SMILE">Vintage Smile</option>
<option value="VINTAGE_PHOTOS">Vintage Photos</option>
<option value="NOSTALGIE">Nostalgie</option>
@@ -132,6 +148,34 @@ export default function NewBookingForm({
</select>
</div>
</div>
{equipmentList.length > 0 && (
<div className="mt-4">
<label className="block text-sm font-medium text-gray-300 mb-2">
Zusatzausstattung
</label>
<div className="grid grid-cols-2 gap-2">
{equipmentList.map((eq) => (
<label
key={eq.id}
className={`flex items-center gap-2 p-3 rounded-lg border cursor-pointer transition-colors ${
selectedEquipment.includes(eq.id)
? "bg-red-500/10 border-red-500/50 text-white"
: "bg-gray-700/50 border-gray-600 text-gray-300 hover:border-gray-500"
}`}
>
<input
type="checkbox"
checked={selectedEquipment.includes(eq.id)}
onChange={() => toggleEquipment(eq.id)}
className="accent-red-500"
/>
{eq.name}
</label>
))}
</div>
</div>
)}
</div>
<div>
@@ -229,6 +273,49 @@ export default function NewBookingForm({
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
/>
</div>
<div className="col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-2">
Adresse
</label>
<input
type="text"
value={formData.customerAddress}
onChange={(e) =>
setFormData({ ...formData, customerAddress: e.target.value })
}
placeholder="Straße und Hausnummer"
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent placeholder-gray-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
PLZ
</label>
<input
type="text"
value={formData.customerZip}
onChange={(e) =>
setFormData({ ...formData, customerZip: e.target.value })
}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Stadt
</label>
<input
type="text"
value={formData.customerCity}
onChange={(e) =>
setFormData({ ...formData, customerCity: e.target.value })
}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
/>
</div>
</div>
</div>
@@ -362,6 +449,34 @@ export default function NewBookingForm({
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Abbau ab
</label>
<input
type="datetime-local"
value={formData.dismantleTimeEarliest}
onChange={(e) =>
setFormData({ ...formData, dismantleTimeEarliest: e.target.value })
}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Abbau spätestens
</label>
<input
type="datetime-local"
value={formData.dismantleTimeLatest}
onChange={(e) =>
setFormData({ ...formData, dismantleTimeLatest: e.target.value })
}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
/>
</div>
<div className="col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-2">
Notizen (optional)