Buchungskalender eingefügt
This commit is contained in:
@@ -4,7 +4,7 @@ Diese Anwendung stellt eine mehrseitige deutsche Vermietungsseite für eine Foto
|
||||
|
||||
- eine komplett neu aufgebaute öffentliche Website mit den Seiten `Leistungen`, `Preise`, `Verfügbarkeit`, `Buchen`, `Ablauf`, `FAQ`, `Kontakt`, `Impressum`, `Datenschutz` und `Mietbedingungen`
|
||||
- eine Buchungsanfrage mit Nachtlogik: `Montag bis Dienstag = 1 Miettag`
|
||||
- Live-Preisberechnung mit `99,99 €` pro Miettag
|
||||
- Live-Preisberechnung je nach Abholung oder Liefergebiet
|
||||
- einen Verwaltungsbereich für Anfragen, Buchungen, Kunden, Kalender, Rechnungen und Einstellungen
|
||||
- MySQL-Unterstützung mit Tabellenpräfix `fb_` sowie JSON-Fallback
|
||||
|
||||
@@ -59,7 +59,10 @@ Die Datei `mysql.local.php` ist bereits in `.gitignore` ausgeschlossen. Standard
|
||||
|
||||
- Ein Miettag entspricht immer einer Übernachtung.
|
||||
- Beispiel: `Montag bis Dienstag = 1 Miettag`
|
||||
- Der Standardpreis beträgt `99,99 €` pro Miettag.
|
||||
- Selbstabholung kostet `99,99 €` pro Miettag.
|
||||
- Lieferung nach Hannover kostet `199,99 €` pro Miettag.
|
||||
- Lieferung in die Region Hannover kostet `249,99 €` pro Miettag.
|
||||
- Lieferung nach Hameln, Braunschweig, Hildesheim oder Celle kostet `299,99 €` pro Miettag.
|
||||
- Zahlungsarten: `Rechnung / Überweisung` und `PayPal`
|
||||
- Öffentliche Eingaben sind zunächst Buchungsanfragen und werden erst nach Bestätigung verbindlich.
|
||||
|
||||
|
||||
+70
-2
@@ -1,4 +1,7 @@
|
||||
const forms = document.querySelectorAll('.booking-form');
|
||||
const navToggle = document.querySelector('[data-nav-toggle]');
|
||||
const navMenu = document.querySelector('[data-nav-menu]');
|
||||
const navLinks = document.querySelectorAll('.site-nav-public a');
|
||||
|
||||
const formatCurrency = (cents) =>
|
||||
new Intl.NumberFormat('de-DE', {
|
||||
@@ -26,17 +29,54 @@ forms.forEach((form) => {
|
||||
const startInput = form.querySelector('[data-booking-start]');
|
||||
const endInput = form.querySelector('[data-booking-end]');
|
||||
const daysOutput = form.querySelector('[data-summary-days]');
|
||||
const rateOutput = form.querySelector('[data-summary-rate]');
|
||||
const totalOutput = form.querySelector('[data-summary-total]');
|
||||
const rateInput = form.querySelector('input[name="price_per_day_cents"]');
|
||||
const deliveryModeInput = form.querySelector('[data-delivery-mode]');
|
||||
const deliveryZoneInput = form.querySelector('[data-delivery-zone]');
|
||||
const defaultRate = Number(form.dataset.dayRate || rateInput?.value || 9999);
|
||||
const priceRates = (() => {
|
||||
try {
|
||||
return JSON.parse(form.dataset.priceRates || '{}');
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
})();
|
||||
|
||||
const resolveRate = () => {
|
||||
if (deliveryModeInput?.value === 'self_pickup') {
|
||||
return Number(priceRates.self_pickup || defaultRate);
|
||||
}
|
||||
|
||||
return Number(priceRates[deliveryZoneInput?.value || ''] || defaultRate);
|
||||
};
|
||||
|
||||
const syncRate = () => {
|
||||
if (deliveryZoneInput) {
|
||||
if (deliveryModeInput?.value === 'self_pickup') {
|
||||
deliveryZoneInput.value = 'self_pickup';
|
||||
} else if (deliveryZoneInput.value === 'self_pickup') {
|
||||
deliveryZoneInput.value = 'hannover';
|
||||
}
|
||||
}
|
||||
|
||||
const rate = resolveRate();
|
||||
|
||||
if (rateInput) {
|
||||
rateInput.value = String(rate);
|
||||
}
|
||||
|
||||
return rate;
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const rentalDays = calculateRentalDays(startInput?.value, endInput?.value);
|
||||
const rate = Number(rateInput?.value || defaultRate);
|
||||
const rate = syncRate();
|
||||
|
||||
if (!rentalDays || rate < 0) {
|
||||
if (daysOutput) daysOutput.textContent = 'Noch nicht gewählt';
|
||||
if (totalOutput) totalOutput.textContent = formatCurrency(defaultRate);
|
||||
if (rateOutput) rateOutput.textContent = formatCurrency(rate);
|
||||
if (totalOutput) totalOutput.textContent = formatCurrency(rate);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,6 +84,10 @@ forms.forEach((form) => {
|
||||
daysOutput.textContent = `${rentalDays} ${rentalDays === 1 ? 'Miettag' : 'Miettage'}`;
|
||||
}
|
||||
|
||||
if (rateOutput) {
|
||||
rateOutput.textContent = formatCurrency(rate);
|
||||
}
|
||||
|
||||
if (totalOutput) {
|
||||
totalOutput.textContent = formatCurrency(rentalDays * rate);
|
||||
}
|
||||
@@ -52,5 +96,29 @@ forms.forEach((form) => {
|
||||
startInput?.addEventListener('input', render);
|
||||
endInput?.addEventListener('input', render);
|
||||
rateInput?.addEventListener('input', render);
|
||||
deliveryModeInput?.addEventListener('change', render);
|
||||
deliveryZoneInput?.addEventListener('change', render);
|
||||
render();
|
||||
});
|
||||
|
||||
if (navToggle && navMenu) {
|
||||
const closeMenu = () => {
|
||||
navToggle.setAttribute('aria-expanded', 'false');
|
||||
navMenu.classList.remove('is-open');
|
||||
};
|
||||
|
||||
navToggle.addEventListener('click', () => {
|
||||
const isOpen = navMenu.classList.toggle('is-open');
|
||||
navToggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
});
|
||||
|
||||
navLinks.forEach((link) => {
|
||||
link.addEventListener('click', closeMenu);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+347
-4
@@ -175,6 +175,125 @@ textarea {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.public-header-shell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.public-header-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.site-nav-public {
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.site-nav-public a {
|
||||
position: relative;
|
||||
padding: 0.35rem 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--text-soft);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.site-nav-public a::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -0.35rem;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--accent) 0%, #d4a081 100%);
|
||||
opacity: 0;
|
||||
transform: scaleX(0.55);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.site-nav-public a:hover,
|
||||
.site-nav-public a:focus-visible,
|
||||
.site-nav-public a.is-active {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.site-nav-public a:hover::after,
|
||||
.site-nav-public a:focus-visible::after,
|
||||
.site-nav-public a.is-active::after {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.header-actions-public {
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.menu-meta {
|
||||
display: none;
|
||||
gap: 0.35rem;
|
||||
color: var(--text-soft);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.menu-meta a {
|
||||
color: var(--accent-strong);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.contact-chip {
|
||||
display: inline-grid;
|
||||
gap: 0.1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255, 255, 255, 0.74);
|
||||
text-decoration: none;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.contact-chip span {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.contact-chip strong {
|
||||
font-size: 0.95rem;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.85rem 1rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255, 255, 255, 0.76);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-toggle-box {
|
||||
display: inline-grid;
|
||||
gap: 0.24rem;
|
||||
}
|
||||
|
||||
.nav-toggle-box span {
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -618,6 +737,136 @@ p {
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
.calendar-legend,
|
||||
.public-calendar-grid,
|
||||
.calendar-weekdays,
|
||||
.calendar-days,
|
||||
.calendar-entry-list {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.calendar-legend {
|
||||
grid-auto-flow: column;
|
||||
justify-content: start;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.public-calendar-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.calendar-month-card {
|
||||
padding: 1.35rem;
|
||||
border-radius: var(--radius-xl);
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
.calendar-month-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.calendar-month-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
.calendar-month-header span,
|
||||
.calendar-weekdays span,
|
||||
.calendar-day-state {
|
||||
color: var(--text-soft);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.calendar-weekdays,
|
||||
.calendar-days {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.calendar-weekdays {
|
||||
margin-bottom: 0.45rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
min-height: 4.6rem;
|
||||
padding: 0.55rem 0.45rem;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(247, 243, 236, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.calendar-day-empty {
|
||||
border-style: dashed;
|
||||
background: rgba(255, 255, 255, 0.34);
|
||||
}
|
||||
|
||||
.calendar-day.is-today {
|
||||
border-color: rgba(183, 106, 71, 0.42);
|
||||
box-shadow: inset 0 0 0 1px rgba(183, 106, 71, 0.18);
|
||||
}
|
||||
|
||||
.calendar-day.is-booked {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.calendar-day-requested {
|
||||
background: rgba(183, 106, 71, 0.14);
|
||||
border-color: rgba(183, 106, 71, 0.24);
|
||||
}
|
||||
|
||||
.calendar-day-reserved {
|
||||
background: rgba(102, 87, 74, 0.14);
|
||||
border-color: rgba(102, 87, 74, 0.22);
|
||||
}
|
||||
|
||||
.calendar-day-confirmed {
|
||||
background: rgba(77, 105, 91, 0.14);
|
||||
border-color: rgba(77, 105, 91, 0.22);
|
||||
}
|
||||
|
||||
.calendar-day-number {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.calendar-entry-list {
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.calendar-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.9rem;
|
||||
padding: 0.85rem 0.95rem;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255, 255, 255, 0.58);
|
||||
}
|
||||
|
||||
.calendar-entry strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.calendar-entry span {
|
||||
color: var(--text-soft);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.availability-card,
|
||||
.stack-item {
|
||||
display: flex;
|
||||
@@ -1064,21 +1313,90 @@ tbody tr:last-child td {
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.header-inner {
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.site-header-admin .header-inner {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.site-nav,
|
||||
.header-actions {
|
||||
.site-header-admin .site-nav,
|
||||
.site-header-admin .header-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.public-header-shell {
|
||||
position: relative;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.public-header-controls {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.85rem);
|
||||
right: 0;
|
||||
width: min(24rem, calc(100vw - 2rem));
|
||||
padding: 1rem;
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255, 251, 246, 0.98);
|
||||
box-shadow: var(--shadow-soft);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.public-header-controls.is-open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.site-nav-public,
|
||||
.menu-meta,
|
||||
.header-actions-public {
|
||||
width: 100%;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.site-nav-public {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.menu-meta {
|
||||
display: grid;
|
||||
padding-top: 0.15rem;
|
||||
}
|
||||
|
||||
.header-actions-public {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.site-nav-public a {
|
||||
padding: 0.95rem 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.site-nav-public a::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.site-nav-public a:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.faq-grid,
|
||||
.legal-section,
|
||||
.trust-grid,
|
||||
.form-grid,
|
||||
.form-grid-two,
|
||||
.calendar-grid {
|
||||
.calendar-grid,
|
||||
.public-calendar-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -1123,9 +1441,14 @@ tbody tr:last-child td {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-primary,
|
||||
.button-secondary,
|
||||
.ghost-button {
|
||||
.ghost-button,
|
||||
.contact-chip {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1144,10 +1467,30 @@ tbody tr:last-child td {
|
||||
|
||||
.availability-card,
|
||||
.stack-item,
|
||||
.calendar-entry,
|
||||
.form-section-header,
|
||||
.hero-panel-top,
|
||||
.hero-panel-bottom {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.calendar-weekdays,
|
||||
.calendar-days {
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
min-height: 4rem;
|
||||
padding: 0.45rem 0.35rem;
|
||||
}
|
||||
|
||||
.calendar-legend {
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
|
||||
.public-header-shell {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
+24
-2
@@ -13,7 +13,7 @@ return [
|
||||
'email' => 'hallo@fotobox-moments.local',
|
||||
'phone' => '+49 170 1234567',
|
||||
'website' => 'https://fotobox-moments.local',
|
||||
'service_area' => 'Musterstadt und Umgebung',
|
||||
'service_area' => 'Hannover, Region Hannover, Hameln, Braunschweig, Hildesheim und Celle',
|
||||
'response_time' => 'Antwort meist innerhalb von 24 Stunden',
|
||||
'pickup_window' => 'Abholung ab 17:00 Uhr',
|
||||
'return_window' => 'Rückgabe bis 13:00 Uhr',
|
||||
@@ -42,7 +42,29 @@ return [
|
||||
'pricing' => [
|
||||
'default_day_rate_cents' => 9999,
|
||||
'currency' => 'EUR',
|
||||
'label' => '99,99 € pro Miettag',
|
||||
'label' => 'Abholung ab 99,99 € pro Miettag',
|
||||
'delivery_rates' => [
|
||||
'self_pickup' => [
|
||||
'label' => 'Selbstabholung',
|
||||
'description' => 'Abholung in Hannover',
|
||||
'price_cents' => 9999,
|
||||
],
|
||||
'hannover' => [
|
||||
'label' => 'Lieferung nach Hannover',
|
||||
'description' => 'Lieferung und Aufbau im Stadtgebiet Hannover',
|
||||
'price_cents' => 19999,
|
||||
],
|
||||
'region_hannover' => [
|
||||
'label' => 'Lieferung in die Region Hannover',
|
||||
'description' => 'Lieferung und Aufbau in die Region Hannover',
|
||||
'price_cents' => 24999,
|
||||
],
|
||||
'extended_region' => [
|
||||
'label' => 'Lieferung nach Hameln, Braunschweig, Hildesheim oder Celle',
|
||||
'description' => 'Lieferung und Aufbau in Hameln, Braunschweig, Hildesheim oder Celle',
|
||||
'price_cents' => 29999,
|
||||
],
|
||||
],
|
||||
],
|
||||
'admin' => [
|
||||
'username' => 'admin',
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
1. Startseite unter `/` öffnen und prüfen, ob Hero, Leistungsblöcke, Verfügbarkeitsvorschau und der Link zur Buchungsanfrage sichtbar sind.
|
||||
2. Die Seite `/buchen` öffnen und kontrollieren, ob die Zusammenfassung `Montag bis Dienstag = 1 Miettag` korrekt berechnet.
|
||||
3. Im Buchungsformular einen Zeitraum wählen und prüfen, ob der Gesamtpreis mit `99,99 €` pro Miettag berechnet wird.
|
||||
3. Im Buchungsformular die Lieferart wechseln und prüfen, ob die Preisberechnung sauber umschaltet: `99,99 €` für Selbstabholung, `199,99 €` für Hannover, `249,99 €` für Region Hannover und `299,99 €` für Hameln, Braunschweig, Hildesheim oder Celle.
|
||||
4. Eine Anfrage absenden und sicherstellen, dass sie in `storage/bookings.json` erscheint oder bei aktiver MySQL-Verbindung in `fb_bookings`.
|
||||
5. `/admin/login` öffnen, mit `admin` und dem konfigurierten Passwort anmelden und das Dashboard prüfen.
|
||||
6. Im Admin-Bereich eine manuelle Buchung anlegen und kontrollieren, ob Terminüberschneidungen erkannt werden.
|
||||
|
||||
@@ -200,6 +200,79 @@ final class BookingService
|
||||
return array_slice($bookings, 0, 4);
|
||||
}
|
||||
|
||||
public function getPublicAvailabilityBookings(): array
|
||||
{
|
||||
$today = gmdate('Y-m-d');
|
||||
|
||||
return array_values(array_filter(
|
||||
$this->getBookingsByStatuses(['requested', 'reserved', 'confirmed']),
|
||||
static fn(array $booking): bool => (string) $booking['end_date'] >= $today
|
||||
));
|
||||
}
|
||||
|
||||
public function getPublicCalendarMonths(int $monthCount = 4): array
|
||||
{
|
||||
$months = [];
|
||||
$monthCount = max(1, $monthCount);
|
||||
$today = gmdate('Y-m-d');
|
||||
$bookings = $this->getPublicAvailabilityBookings();
|
||||
$monthCursor = (new DateTimeImmutable('first day of this month'))->setTime(0, 0);
|
||||
|
||||
for ($index = 0; $index < $monthCount; $index++) {
|
||||
$monthStart = $monthCursor->modify('+' . $index . ' month');
|
||||
$monthEnd = $monthStart->modify('first day of next month');
|
||||
$monthKey = $monthStart->format('Y-m');
|
||||
$entries = array_values(array_filter(
|
||||
$bookings,
|
||||
fn(array $booking): bool => $this->bookingIntersectsRange(
|
||||
$booking,
|
||||
$monthStart->format('Y-m-d'),
|
||||
$monthEnd->format('Y-m-d')
|
||||
)
|
||||
));
|
||||
|
||||
$days = [];
|
||||
for ($blank = 1; $blank < (int) $monthStart->format('N'); $blank++) {
|
||||
$days[] = ['is_padding' => true];
|
||||
}
|
||||
|
||||
$dayCursor = $monthStart;
|
||||
while ($dayCursor < $monthEnd) {
|
||||
$date = $dayCursor->format('Y-m-d');
|
||||
$dayBooking = $this->findBookingForDate($entries, $date);
|
||||
|
||||
$days[] = [
|
||||
'is_padding' => false,
|
||||
'date' => $date,
|
||||
'day' => (int) $dayCursor->format('j'),
|
||||
'is_today' => $date === $today,
|
||||
'is_booked' => $dayBooking !== null,
|
||||
'status' => $dayBooking['status'] ?? '',
|
||||
'status_label' => $dayBooking['status_label'] ?? '',
|
||||
];
|
||||
|
||||
$dayCursor = $dayCursor->modify('+1 day');
|
||||
}
|
||||
|
||||
while (count($days) % 7 !== 0) {
|
||||
$days[] = ['is_padding' => true];
|
||||
}
|
||||
|
||||
$months[] = [
|
||||
'label' => $this->formatMonthLabel($monthKey),
|
||||
'month_key' => $monthKey,
|
||||
'entry_count' => count($entries),
|
||||
'days' => $days,
|
||||
'entries' => array_map(
|
||||
fn(array $booking): array => $this->buildPublicCalendarEntry($booking),
|
||||
$entries
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $months;
|
||||
}
|
||||
|
||||
public function getDashboardStats(): array
|
||||
{
|
||||
$bookings = $this->getBookings();
|
||||
@@ -303,6 +376,7 @@ final class BookingService
|
||||
'payment_status' => 'unpaid',
|
||||
'payment_method' => 'invoice_transfer',
|
||||
'delivery_mode' => 'self_pickup',
|
||||
'delivery_zone' => 'self_pickup',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -336,6 +410,20 @@ final class BookingService
|
||||
];
|
||||
}
|
||||
|
||||
public function getDeliveryModeOptions(): array
|
||||
{
|
||||
return [
|
||||
'self_pickup' => 'Selbstabholung',
|
||||
'delivery_setup' => 'Lieferung und Aufbau',
|
||||
'on_site_support' => 'Lieferung, Aufbau und Vor-Ort-Betreuung',
|
||||
];
|
||||
}
|
||||
|
||||
public function getDeliveryZoneOptions(): array
|
||||
{
|
||||
return $this->config['pricing']['delivery_rates'] ?? [];
|
||||
}
|
||||
|
||||
private function normalizeBookingInput(array $input, bool $adminMode): array
|
||||
{
|
||||
$customerName = trim((string) ($input['customer_name'] ?? ''));
|
||||
@@ -351,11 +439,11 @@ final class BookingService
|
||||
$endDate = trim((string) ($input['end_date'] ?? ''));
|
||||
$paymentMethod = trim((string) ($input['payment_method'] ?? 'invoice_transfer'));
|
||||
$deliveryMode = trim((string) ($input['delivery_mode'] ?? 'self_pickup'));
|
||||
$deliveryZone = trim((string) ($input['delivery_zone'] ?? 'self_pickup'));
|
||||
$notesCustomer = trim((string) ($input['notes_customer'] ?? ''));
|
||||
$internalNotes = trim((string) ($input['internal_notes'] ?? ''));
|
||||
$status = trim((string) ($input['status'] ?? ($adminMode ? 'confirmed' : 'requested')));
|
||||
$paymentStatus = trim((string) ($input['payment_status'] ?? 'unpaid'));
|
||||
$pricePerDay = (int) ($input['price_per_day_cents'] ?? $this->config['pricing']['default_day_rate_cents']);
|
||||
$privacyAccepted = (string) ($input['privacy_accepted'] ?? '') === '1';
|
||||
$termsAccepted = (string) ($input['terms_accepted'] ?? '') === '1';
|
||||
|
||||
@@ -408,11 +496,8 @@ final class BookingService
|
||||
'paypal' => 'PayPal',
|
||||
];
|
||||
|
||||
$deliveryLabels = [
|
||||
'self_pickup' => 'Selbstabholung',
|
||||
'delivery_setup' => 'Lieferung und Aufbau',
|
||||
'on_site_support' => 'Lieferung, Aufbau und Vor-Ort-Betreuung',
|
||||
];
|
||||
$deliveryLabels = $this->getDeliveryModeOptions();
|
||||
$deliveryZones = $this->getDeliveryZoneOptions();
|
||||
|
||||
if (!array_key_exists($paymentMethod, $paymentLabels)) {
|
||||
throw new RuntimeException('Die gewählte Zahlungsart ist ungültig.');
|
||||
@@ -422,6 +507,18 @@ final class BookingService
|
||||
throw new RuntimeException('Die gewählte Lieferart ist ungültig.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($deliveryZone, $deliveryZones)) {
|
||||
throw new RuntimeException('Bitte wähle ein gültiges Liefergebiet aus.');
|
||||
}
|
||||
|
||||
if ($deliveryMode === 'self_pickup') {
|
||||
$deliveryZone = 'self_pickup';
|
||||
} elseif ($deliveryZone === 'self_pickup') {
|
||||
throw new RuntimeException('Bitte wähle für die Lieferung ein passendes Liefergebiet aus.');
|
||||
}
|
||||
|
||||
$pricePerDay = $this->resolveRateForSelection($deliveryMode, $deliveryZone);
|
||||
|
||||
if ($pricePerDay < 0) {
|
||||
throw new RuntimeException('Der Tagespreis ist ungültig.');
|
||||
}
|
||||
@@ -443,6 +540,8 @@ final class BookingService
|
||||
'payment_method_label' => $paymentLabels[$paymentMethod],
|
||||
'delivery_mode' => $deliveryMode,
|
||||
'delivery_mode_label' => $deliveryLabels[$deliveryMode],
|
||||
'delivery_zone' => $deliveryZone,
|
||||
'delivery_zone_label' => $deliveryZones[$deliveryZone]['label'],
|
||||
'notes_customer' => $notesCustomer,
|
||||
'internal_notes' => $internalNotes,
|
||||
'status' => $status,
|
||||
@@ -472,6 +571,8 @@ final class BookingService
|
||||
'payment_method_label' => $payload['payment_method_label'],
|
||||
'delivery_mode' => $payload['delivery_mode'],
|
||||
'delivery_mode_label' => $payload['delivery_mode_label'],
|
||||
'delivery_zone' => $payload['delivery_zone'],
|
||||
'delivery_zone_label' => $payload['delivery_zone_label'],
|
||||
'start_date' => $payload['start_date'],
|
||||
'end_date' => $payload['end_date'],
|
||||
'total_days' => $payload['total_days'],
|
||||
@@ -562,6 +663,46 @@ final class BookingService
|
||||
return $prefix . '_' . strtolower(bin2hex(random_bytes(6)));
|
||||
}
|
||||
|
||||
private function resolveRateForSelection(string $deliveryMode, string $deliveryZone): int
|
||||
{
|
||||
$zones = $this->getDeliveryZoneOptions();
|
||||
|
||||
if ($deliveryMode === 'self_pickup') {
|
||||
return (int) ($zones['self_pickup']['price_cents'] ?? $this->config['pricing']['default_day_rate_cents']);
|
||||
}
|
||||
|
||||
return (int) ($zones[$deliveryZone]['price_cents'] ?? $this->config['pricing']['default_day_rate_cents']);
|
||||
}
|
||||
|
||||
private function bookingIntersectsRange(array $booking, string $rangeStart, string $rangeEnd): bool
|
||||
{
|
||||
return $booking['start_date'] < $rangeEnd && $booking['end_date'] > $rangeStart;
|
||||
}
|
||||
|
||||
private function findBookingForDate(array $bookings, string $date): ?array
|
||||
{
|
||||
foreach ($bookings as $booking) {
|
||||
if ($booking['start_date'] <= $date && $booking['end_date'] > $date) {
|
||||
return $booking;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function buildPublicCalendarEntry(array $booking): array
|
||||
{
|
||||
$deliveryLabel = (string) ($booking['delivery_zone_label'] ?: $booking['delivery_mode_label']);
|
||||
|
||||
return [
|
||||
'date_label' => formatDate($booking['start_date']) . ' bis ' . formatDate($booking['end_date']),
|
||||
'status' => $booking['status'],
|
||||
'status_label' => $booking['status_label'],
|
||||
'delivery_label' => $deliveryLabel,
|
||||
'day_count_label' => $booking['total_days'] . ' Miettag' . ($booking['total_days'] === 1 ? '' : 'e'),
|
||||
];
|
||||
}
|
||||
|
||||
private function formatMonthLabel(string $monthKey): string
|
||||
{
|
||||
[$year, $month] = explode('-', $monthKey) + [null, null];
|
||||
|
||||
@@ -64,6 +64,7 @@ function basePath(): string
|
||||
'/admin/login',
|
||||
'/admin/logout',
|
||||
'/admin',
|
||||
'/fotobox',
|
||||
'/leistungen',
|
||||
'/preise',
|
||||
'/verfuegbarkeit',
|
||||
@@ -185,7 +186,7 @@ function h(string $value): string
|
||||
|
||||
function formatCurrency(int $cents): string
|
||||
{
|
||||
return number_format($cents / 100, 2, ',', '.') . ' EUR';
|
||||
return number_format($cents / 100, 2, ',', '.') . ' €';
|
||||
}
|
||||
|
||||
function selected(string $current, string $expected): string
|
||||
|
||||
+68
-42
@@ -343,10 +343,16 @@ function publicRoutes(): array
|
||||
'metaDescription' => 'DSLR-Kamera, Studioblitz, Softbox, WLAN-Download und professioneller Lieferumfang für Ihre Fotobox-Miete.',
|
||||
'pageKey' => 'leistungen',
|
||||
],
|
||||
'/fotobox' => [
|
||||
'view' => 'pages/leistungen',
|
||||
'pageTitle' => 'Fotobox und Ausstattung',
|
||||
'metaDescription' => 'DSLR-Kamera, Studioblitz, Softbox, WLAN-Download und professioneller Lieferumfang für Ihre Fotobox-Miete.',
|
||||
'pageKey' => 'fotobox',
|
||||
],
|
||||
'/preise' => [
|
||||
'view' => 'pages/preise',
|
||||
'pageTitle' => 'Preise und Mietlogik',
|
||||
'metaDescription' => '99,99 € pro Miettag. Ein Miettag entspricht einer Übernachtung. Klare Preise, transparente Leistungen und feste Zahlungsarten.',
|
||||
'metaDescription' => 'Selbstabholung ab 99,99 € pro Miettag, Lieferung je nach Zielort. Ein Miettag entspricht einer Übernachtung.',
|
||||
'pageKey' => 'preise',
|
||||
],
|
||||
'/verfuegbarkeit' => [
|
||||
@@ -404,6 +410,7 @@ function renderPublicPage(array $route, BookingService $bookingService, array $c
|
||||
{
|
||||
$company = $config['company'];
|
||||
$bookings = $bookingService->getHighlightedBookings();
|
||||
$availabilityBookings = $bookingService->getPublicAvailabilityBookings();
|
||||
$currentView = (string) $route['view'];
|
||||
|
||||
render($currentView, [
|
||||
@@ -413,10 +420,14 @@ function renderPublicPage(array $route, BookingService $bookingService, array $c
|
||||
'config' => $config,
|
||||
'company' => $company,
|
||||
'dayRate' => $config['pricing']['default_day_rate_cents'],
|
||||
'deliveryModeOptions' => $bookingService->getDeliveryModeOptions(),
|
||||
'deliveryZoneOptions' => $bookingService->getDeliveryZoneOptions(),
|
||||
'flashSuccess' => flash('success'),
|
||||
'flashError' => flash('error'),
|
||||
'old' => flash('old') ?? [],
|
||||
'bookings' => $bookings,
|
||||
'availabilityBookings' => $availabilityBookings,
|
||||
'availabilityCalendarMonths' => $bookingService->getPublicCalendarMonths(4),
|
||||
'trustFacts' => publicTrustFacts($config),
|
||||
'featureCards' => publicFeatureCards(),
|
||||
'processSteps' => publicProcessSteps($config),
|
||||
@@ -547,6 +558,8 @@ function renderAdminCreate(BookingService $bookingService): void
|
||||
'flashError' => flash('error'),
|
||||
'old' => flash('admin_old') ?? [],
|
||||
'defaults' => $bookingService->getAdminDefaults(),
|
||||
'deliveryModeOptions' => $bookingService->getDeliveryModeOptions(),
|
||||
'deliveryZoneOptions' => $bookingService->getDeliveryZoneOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -574,13 +587,14 @@ function renderAdminOrder(BookingService $bookingService): void
|
||||
|
||||
function publicTrustFacts(array $config): array
|
||||
{
|
||||
$company = $config['company'];
|
||||
$rates = $config['pricing']['delivery_rates'];
|
||||
|
||||
return [
|
||||
['label' => 'Preis', 'value' => $config['pricing']['label']],
|
||||
['label' => 'Mietlogik', 'value' => '1 Miettag = 1 Übernachtung'],
|
||||
['label' => 'Abholung', 'value' => formatCurrency((int) $rates['self_pickup']['price_cents']) . ' pro Miettag'],
|
||||
['label' => 'Lieferung Hannover', 'value' => formatCurrency((int) $rates['hannover']['price_cents']) . ' pro Miettag'],
|
||||
['label' => 'Mietdauer', 'value' => '1 Miettag = 1 Übernachtung'],
|
||||
['label' => 'Zahlung', 'value' => 'Rechnung, Überweisung oder PayPal'],
|
||||
['label' => 'Servicefenster', 'value' => $company['pickup_window'] . ' / ' . $company['return_window']],
|
||||
['label' => 'Bilder', 'value' => 'Digitale Bilder inklusive'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -588,20 +602,20 @@ function publicFeatureCards(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'title' => 'Professionelle Bildqualität',
|
||||
'text' => 'DSLR-Kamera, Studioblitz und Softbox sorgen für klare, helle Fotos bei wechselnden Lichtverhältnissen.',
|
||||
'title' => 'Spiegelreflexkamera & Studioblitz',
|
||||
'text' => 'DSLR-Kamera, Bildschirm und große Softbox sorgen für helle, hochwertige Fotos bei jedem Anlass.',
|
||||
],
|
||||
[
|
||||
'title' => 'Direkter Download aufs Handy',
|
||||
'text' => 'Ihre Gäste können Bilder vor Ort per WLAN laden. Nach dem Event erhalten Sie zusätzlich die komplette Galerie digital.',
|
||||
'title' => 'WLAN-Download direkt vor Ort',
|
||||
'text' => 'Ihre Gäste können Fotos direkt auf das Handy laden und teilen. Nach dem Event erhalten Sie zusätzlich alle Bilder digital.',
|
||||
],
|
||||
[
|
||||
'title' => 'Lieferung oder Selbstabholung',
|
||||
'text' => 'Sie entscheiden zwischen Selbstabholung, Lieferung mit Aufbau oder einem Rundum-Service mit Vor-Ort-Betreuung.',
|
||||
'title' => 'Schnell aufgebaut, leicht bedient',
|
||||
'text' => 'Die Fotobox ist in wenigen Minuten einsatzbereit und wird bei der Übergabe kurz erklärt.',
|
||||
],
|
||||
[
|
||||
'title' => 'Saubere Verwaltung im Hintergrund',
|
||||
'text' => 'Anfragen, Kundendaten, Rechnungen und Zahlungsstatus werden in einem Verwaltungsbereich zentral gepflegt.',
|
||||
'title' => 'Lieferung, Abholung oder Betreuung',
|
||||
'text' => 'Sie entscheiden zwischen Selbstabholung, Lieferung mit Aufbau oder Vor-Ort-Unterstützung für Ihr Event.',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -612,42 +626,43 @@ function publicProcessSteps(array $config): array
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => 'Zeitraum wählen',
|
||||
'title' => 'Wunschtermin anfragen',
|
||||
'text' => 'Sie wählen Abholtag und Rückgabetag. Montag bis Dienstag zählt als 1 Miettag.',
|
||||
],
|
||||
[
|
||||
'title' => 'Leistung festlegen',
|
||||
'text' => 'Selbstabholung, Lieferung oder Betreuung vor Ort werden passend zu Ihrem Event gewählt.',
|
||||
'title' => 'Verfügbarkeit bestätigen lassen',
|
||||
'text' => 'Wir prüfen den Termin und melden uns in der Regel innerhalb von 24 Stunden zurück.',
|
||||
],
|
||||
[
|
||||
'title' => 'Anfrage absenden',
|
||||
'text' => 'Wir prüfen Verfügbarkeit, erfassen Ihre Daten und bestätigen den Auftrag persönlich.',
|
||||
'title' => 'Fotobox abholen oder liefern lassen',
|
||||
'text' => 'Sie wählen Selbstabholung oder Lieferung mit Aufbau passend zu Ihrem Event.',
|
||||
],
|
||||
[
|
||||
'title' => 'Feiern und Bilder erhalten',
|
||||
'text' => 'Die Fotobox steht rechtzeitig bereit. Die Rückgabe erfolgt bis ' . $company['return_window'] . '.',
|
||||
'text' => 'Die Fotobox steht rechtzeitig bereit und alle Bilder erhalten Sie anschließend digital.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function publicPricingExamples(array $config): array
|
||||
{
|
||||
$label = $config['pricing']['label'];
|
||||
$rates = $config['pricing']['delivery_rates'];
|
||||
|
||||
return [
|
||||
['title' => 'Montag bis Dienstag', 'text' => '1 Miettag · ' . $label],
|
||||
['title' => 'Freitag bis Sonntag', 'text' => '2 Miettage · 199,98 €'],
|
||||
['title' => 'Buchungsanfrage', 'text' => 'Noch kein Sofortvertrag. Verbindlich erst nach Bestätigung.'],
|
||||
['title' => 'Abholung', 'text' => formatCurrency((int) $rates['self_pickup']['price_cents']) . ' pro Miettag'],
|
||||
['title' => 'Lieferung nach Hannover', 'text' => formatCurrency((int) $rates['hannover']['price_cents']) . ' pro Miettag'],
|
||||
['title' => 'Region Hannover', 'text' => formatCurrency((int) $rates['region_hannover']['price_cents']) . ' pro Miettag'],
|
||||
['title' => 'Hameln, Braunschweig, Hildesheim oder Celle', 'text' => formatCurrency((int) $rates['extended_region']['price_cents']) . ' pro Miettag'],
|
||||
];
|
||||
}
|
||||
|
||||
function publicOccasionCards(): array
|
||||
{
|
||||
return [
|
||||
['title' => 'Hochzeiten', 'text' => 'Für Erinnerungen mit ruhiger Technik und hochwertigem Licht.'],
|
||||
['title' => 'Geburtstage', 'text' => 'Einfach zu bedienen und schnell einsatzbereit.'],
|
||||
['title' => 'Firmenfeiern', 'text' => 'Mit Rechnung, klarer Planung und sauberer Abwicklung.'],
|
||||
['title' => 'Jubiläen und Vereinsfeste', 'text' => 'Für Veranstaltungen mit vielen Gästen und wenig Zeitverlust.'],
|
||||
['title' => 'Hochzeiten', 'text' => 'Für emotionale Erinnerungen und eine entspannte Feier mit Ihren Gästen.'],
|
||||
['title' => 'Geburtstage', 'text' => 'Einfach zu bedienen und schnell einsatzbereit für jede Party.'],
|
||||
['title' => 'Firmenfeiern', 'text' => 'Mit klarer Abwicklung, Rechnung und professionellem Auftritt.'],
|
||||
['title' => 'Jubiläen und Vereinsfeste', 'text' => 'Ideal für Veranstaltungen mit vielen Gästen und unkompliziertem Ablauf.'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -661,8 +676,12 @@ function publicFaqItems(array $config): array
|
||||
'answer' => 'Ein Miettag entspricht immer einer Übernachtung. Montag bis Dienstag ist also 1 Miettag, Freitag bis Sonntag sind 2 Miettage.',
|
||||
],
|
||||
[
|
||||
'question' => 'Ist die Online-Anfrage direkt verbindlich?',
|
||||
'answer' => 'Nein. Sie senden zunächst eine Buchungsanfrage. Verbindlich wird der Auftrag erst nach unserer Bestätigung.',
|
||||
'question' => 'Was kostet die Fotobox je nach Zielort?',
|
||||
'answer' => 'Selbstabholung kostet ' . formatCurrency((int) $config['pricing']['delivery_rates']['self_pickup']['price_cents']) . ' pro Miettag. Lieferung nach Hannover kostet ' . formatCurrency((int) $config['pricing']['delivery_rates']['hannover']['price_cents']) . ', in die Region Hannover ' . formatCurrency((int) $config['pricing']['delivery_rates']['region_hannover']['price_cents']) . ' und nach Hameln, Braunschweig, Hildesheim oder Celle ' . formatCurrency((int) $config['pricing']['delivery_rates']['extended_region']['price_cents']) . ' pro Miettag.',
|
||||
],
|
||||
[
|
||||
'question' => 'Wann erhalte ich eine Rückmeldung?',
|
||||
'answer' => 'In der Regel erhalten Sie innerhalb von 24 Stunden eine Rückmeldung zur Verfügbarkeit und zum weiteren Ablauf.',
|
||||
],
|
||||
[
|
||||
'question' => 'Welche Zahlungsarten sind möglich?',
|
||||
@@ -672,13 +691,17 @@ function publicFaqItems(array $config): array
|
||||
'question' => 'Wie laufen Abholung und Rückgabe ab?',
|
||||
'answer' => 'Standardmäßig gilt ' . $company['pickup_window'] . '. Die Rückgabe erfolgt bis ' . $company['return_window'] . '. Lieferung und Aufbau sind ebenfalls möglich.',
|
||||
],
|
||||
[
|
||||
'question' => 'Wie schnell ist die Fotobox einsatzbereit?',
|
||||
'answer' => 'Nach Aufbau und Stromversorgung ist die Fotobox in wenigen Minuten startklar. Bei der Übergabe erhalten Sie zudem eine kurze Einweisung.',
|
||||
],
|
||||
[
|
||||
'question' => 'Wann erhalten wir die Fotos?',
|
||||
'answer' => 'Die Bilder können vor Ort per WLAN geteilt werden. Zusätzlich erhalten Sie nach dem Event alle Fotos digital gesammelt.',
|
||||
],
|
||||
[
|
||||
'question' => 'Gibt es eine Rechnung mit Kundendaten?',
|
||||
'answer' => 'Ja. Im Verwaltungsprozess können Rechnungen mit vollständigen Kundendaten erzeugt und als PDF bereitgestellt werden.',
|
||||
'question' => 'Kann ich auch auf Rechnung zahlen?',
|
||||
'answer' => 'Ja. Auf Wunsch erhalten Sie eine Rechnung mit vollständigen Kundendaten. Alternativ ist auch PayPal möglich.',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -686,30 +709,33 @@ function publicFaqItems(array $config): array
|
||||
function publicServiceModules(): array
|
||||
{
|
||||
return [
|
||||
['title' => 'Technikpaket', 'items' => ['DSLR-Kamera', 'Studioblitz mit Softbox', 'Bedienbildschirm', 'WLAN-Fotofreigabe']],
|
||||
['title' => 'Eventbetrieb', 'items' => ['Schneller Aufbau', 'Intuitive Bedienung', 'Digitale Galerie', 'Saubere Rückgabeplanung']],
|
||||
['title' => 'Kaufmännische Abwicklung', 'items' => ['Anfrageerfassung', 'Rechnungsstellung', 'Zahlungsstatus', 'Admin-Verwaltung']],
|
||||
['title' => 'Im Preis enthalten', 'items' => ['Fotobox mit Stativ', 'Spiegelreflexkamera', 'Studioblitz mit Softbox', 'Alle Bilder digital']],
|
||||
['title' => 'Das macht es einfach', 'items' => ['Kurze Einweisung bei der Übergabe', 'Schneller Aufbau', 'WLAN-Download vor Ort', 'Einfache Bedienung per Knopfdruck']],
|
||||
['title' => 'Auf Wunsch zusätzlich', 'items' => ['Lieferung und Aufbau', 'Vor-Ort-Betreuung', 'Rechnung für Firmenkunden', 'Persönliche Rücksprache zum Ablauf']],
|
||||
];
|
||||
}
|
||||
|
||||
function publicServiceStandards(array $config): array
|
||||
{
|
||||
$company = $config['company'];
|
||||
$rates = $config['pricing']['delivery_rates'];
|
||||
|
||||
return [
|
||||
'Klare Preisangabe mit ' . $config['pricing']['label'],
|
||||
'Direkt sichtbare Kontaktwege: ' . $company['phone'] . ' und ' . $company['email'],
|
||||
'Pflichtseiten für Impressum, Datenschutz und Mietbedingungen',
|
||||
'Barrierearme Formulare mit eindeutigen Beschriftungen und Fehlermeldungen',
|
||||
'Selbstabholung ab ' . formatCurrency((int) $rates['self_pickup']['price_cents']) . ' pro Miettag',
|
||||
'Lieferung nach Hannover ab ' . formatCurrency((int) $rates['hannover']['price_cents']) . ' pro Miettag',
|
||||
'WLAN-Download direkt auf das Handy Ihrer Gäste',
|
||||
'Kurze Einweisung bei der Übergabe',
|
||||
$company['pickup_window'] . ' · ' . $company['return_window'],
|
||||
'Rückmeldung in der Regel innerhalb von 24 Stunden',
|
||||
];
|
||||
}
|
||||
|
||||
function publicBookingChecklist(array $config): array
|
||||
{
|
||||
return [
|
||||
'Startdatum und Rückgabedatum bereithalten',
|
||||
'Lieferart auswählen: Selbstabholung, Lieferung oder Betreuung',
|
||||
'Rechnungsdaten und Veranstaltungsort eintragen',
|
||||
'Datenschutz und Mietbedingungen vor dem Absenden bestätigen',
|
||||
'Wunschtermin auswählen',
|
||||
'Leistungsart, Liefergebiet und Zahlungsart festlegen',
|
||||
'Kontaktdaten und Veranstaltungsort eintragen',
|
||||
'Anfrage unverbindlich absenden',
|
||||
];
|
||||
}
|
||||
|
||||
+27
-10
@@ -1,4 +1,10 @@
|
||||
<section class="admin-section narrow-section">
|
||||
<?php
|
||||
$priceRates = [];
|
||||
foreach ($deliveryZoneOptions as $zoneKey => $zone) {
|
||||
$priceRates[$zoneKey] = (int) $zone['price_cents'];
|
||||
}
|
||||
?>
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Manuelle Buchung</p>
|
||||
@@ -14,8 +20,9 @@
|
||||
<div class="flash flash-error"><?= h((string) $flashError) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="<?= h(url('admin/create')) ?>" class="booking-form admin-form" data-day-rate="<?= h((string) $defaults['price_per_day_cents']) ?>">
|
||||
<form method="post" action="<?= h(url('admin/create')) ?>" class="booking-form admin-form" data-day-rate="<?= h((string) $defaults['price_per_day_cents']) ?>" data-price-rates="<?= h((string) json_encode($priceRates, JSON_THROW_ON_ERROR)) ?>">
|
||||
<?= csrfField() ?>
|
||||
<input type="hidden" name="price_per_day_cents" value="<?= h((string) ($old['price_per_day_cents'] ?? $defaults['price_per_day_cents'])) ?>">
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<span class="form-step">Verwaltung</span>
|
||||
@@ -67,10 +74,6 @@
|
||||
<span>Rückgabedatum</span>
|
||||
<input type="date" name="end_date" data-booking-end value="<?= h((string) ($old['end_date'] ?? '')) ?>" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Preis pro Miettag in Cent</span>
|
||||
<input type="number" name="price_per_day_cents" min="0" value="<?= h((string) ($old['price_per_day_cents'] ?? $defaults['price_per_day_cents'])) ?>" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Status</span>
|
||||
<select name="status">
|
||||
@@ -90,10 +93,20 @@
|
||||
</label>
|
||||
<label>
|
||||
<span>Lieferart</span>
|
||||
<select name="delivery_mode">
|
||||
<option value="self_pickup" <?= selected((string) ($old['delivery_mode'] ?? $defaults['delivery_mode']), 'self_pickup') ?>>Selbstabholung</option>
|
||||
<option value="delivery_setup" <?= selected((string) ($old['delivery_mode'] ?? ''), 'delivery_setup') ?>>Lieferung und Aufbau</option>
|
||||
<option value="on_site_support" <?= selected((string) ($old['delivery_mode'] ?? ''), 'on_site_support') ?>>Lieferung, Aufbau und Vor-Ort-Betreuung</option>
|
||||
<select name="delivery_mode" data-delivery-mode>
|
||||
<?php foreach ($deliveryModeOptions as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>" <?= selected((string) ($old['delivery_mode'] ?? $defaults['delivery_mode']), $value) ?>><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Liefergebiet</span>
|
||||
<select name="delivery_zone" data-delivery-zone>
|
||||
<?php foreach ($deliveryZoneOptions as $value => $zone): ?>
|
||||
<option value="<?= h($value) ?>" <?= selected((string) ($old['delivery_zone'] ?? $defaults['delivery_zone']), $value) ?>>
|
||||
<?= h($zone['label']) ?> · <?= h(formatCurrency((int) $zone['price_cents'])) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
@@ -112,6 +125,7 @@
|
||||
<textarea name="internal_notes" rows="4"><?= h((string) ($old['internal_notes'] ?? '')) ?></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<p class="form-help">Preislogik: Abholung <?= h(formatCurrency((int) $deliveryZoneOptions['self_pickup']['price_cents'])) ?>, Hannover <?= h(formatCurrency((int) $deliveryZoneOptions['hannover']['price_cents'])) ?>, Region Hannover <?= h(formatCurrency((int) $deliveryZoneOptions['region_hannover']['price_cents'])) ?>, Hameln/Braunschweig/Hildesheim/Celle <?= h(formatCurrency((int) $deliveryZoneOptions['extended_region']['price_cents'])) ?> pro Miettag.</p>
|
||||
</div>
|
||||
|
||||
<div class="booking-summary-card">
|
||||
@@ -119,6 +133,10 @@
|
||||
<span>Mietdauer</span>
|
||||
<strong data-summary-days>Noch nicht gewählt</strong>
|
||||
</div>
|
||||
<div class="summary-line">
|
||||
<span>Preis pro Miettag</span>
|
||||
<strong data-summary-rate><?= h(formatCurrency((int) ($old['price_per_day_cents'] ?? $defaults['price_per_day_cents']))) ?></strong>
|
||||
</div>
|
||||
<div class="summary-line summary-line-total">
|
||||
<span>Gesamtpreis</span>
|
||||
<strong data-summary-total><?= h(formatCurrency((int) $defaults['price_per_day_cents'])) ?></strong>
|
||||
@@ -128,4 +146,3 @@
|
||||
<button type="submit" class="button-primary">Buchung speichern</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<div><dt>Mietzeitraum</dt><dd><?= h(formatDate($booking['start_date'])) ?> bis <?= h(formatDate($booking['end_date'])) ?></dd></div>
|
||||
<div><dt>Miettage</dt><dd><?= h((string) $booking['total_days']) ?></dd></div>
|
||||
<div><dt>Leistung</dt><dd><?= h($booking['delivery_mode_label']) ?></dd></div>
|
||||
<div><dt>Liefergebiet</dt><dd><?= h($booking['delivery_zone_label'] ?? '-') ?></dd></div>
|
||||
<div><dt>Zahlungsart</dt><dd><?= h($booking['payment_method_label']) ?></dd></div>
|
||||
<div><dt>Gesamt</dt><dd><?= h(formatCurrency((int) $booking['subtotal_cents'])) ?></dd></div>
|
||||
</dl>
|
||||
@@ -106,4 +107,3 @@
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+28
-28
@@ -1,15 +1,14 @@
|
||||
<section class="hero">
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow">Professionelle Fotobox-Vermietung</p>
|
||||
<h1>Fotobox mieten für Hochzeiten, Geburtstage und Firmenfeiern.</h1>
|
||||
<p class="eyebrow">Fotobox-Verleih für <?= h($company['service_area']) ?></p>
|
||||
<h1>Fotobox mieten für Hochzeit, Geburtstag und Firmenfeier.</h1>
|
||||
<p class="hero-text">
|
||||
Hochwertige Technik, klare Preislogik pro Miettag und ein Buchungsablauf,
|
||||
der auch kaufmännisch sauber funktioniert. Anfrage senden, Bestätigung erhalten,
|
||||
Bilder digital bekommen.
|
||||
Professionelle Fotobox mit Spiegelreflexkamera, Studioblitz und digitaler Bildübergabe.
|
||||
Lieferung oder Selbstabholung möglich. Schon ab <?= h(formatCurrency((int) $dayRate)) ?> pro Miettag bei Selbstabholung.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="button-primary" href="<?= h(url('buchen')) ?>">Verfügbarkeit prüfen</a>
|
||||
<a class="button-secondary" href="<?= h(url('leistungen')) ?>">Leistungen ansehen</a>
|
||||
<a class="button-secondary" href="<?= h(url('preise')) ?>">Preise ansehen</a>
|
||||
</div>
|
||||
<div class="trust-grid">
|
||||
<?php foreach ($trustFacts as $fact): ?>
|
||||
@@ -23,8 +22,8 @@
|
||||
|
||||
<aside class="hero-panel">
|
||||
<div class="hero-panel-top">
|
||||
<span>Service mit Struktur</span>
|
||||
<strong>Vom ersten Termin bis zur Rechnung</strong>
|
||||
<span>Beliebt für Hochzeiten, Geburtstage und Firmenfeiern</span>
|
||||
<strong>Professionelle Fotos mit wenig Aufwand</strong>
|
||||
</div>
|
||||
<div class="device-stage">
|
||||
<div class="device-glow"></div>
|
||||
@@ -48,11 +47,11 @@
|
||||
</div>
|
||||
<div class="hero-panel-bottom">
|
||||
<div>
|
||||
<span>Abholung</span>
|
||||
<span>Mietbeginn</span>
|
||||
<strong><?= h($company['pickup_window']) ?></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Rückgabe</span>
|
||||
<span>Mietende</span>
|
||||
<strong><?= h($company['return_window']) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,11 +60,11 @@
|
||||
|
||||
<section class="section section-tight">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Warum diese Seite anders aufgebaut ist</p>
|
||||
<h2>Kein Party-Prospekt, sondern eine ruhige Buchungsseite für einen echten Mietservice.</h2>
|
||||
<p class="eyebrow">Warum unsere Fotobox</p>
|
||||
<h2>Klare Leistungen. Klare Preise. Klare Abläufe.</h2>
|
||||
<p>
|
||||
Die Agenten-Recherche hat klar gezeigt: Kundenfreundlich ist eine verständliche Service-Seite
|
||||
mit Preis, Ablauf, Verfügbarkeit und einem Verwaltungsprozess im Hintergrund.
|
||||
Sie sehen sofort, was enthalten ist, wie ein Miettag berechnet wird
|
||||
und wie Ihre Anfrage abläuft. So planen Sie Ihr Event ohne unnötige Rückfragen und ohne Technikstress.
|
||||
</p>
|
||||
</div>
|
||||
<div class="feature-card-grid">
|
||||
@@ -81,7 +80,7 @@
|
||||
<section class="section split-section">
|
||||
<div class="content-card">
|
||||
<p class="eyebrow">Ablauf</p>
|
||||
<h2>So läuft Ihre Anfrage ab</h2>
|
||||
<h2>So einfach mieten Sie die Fotobox</h2>
|
||||
<ol class="step-list">
|
||||
<?php foreach ($processSteps as $index => $step): ?>
|
||||
<li>
|
||||
@@ -95,8 +94,8 @@
|
||||
</ol>
|
||||
</div>
|
||||
<div class="content-card editorial-card">
|
||||
<p class="eyebrow">Standards</p>
|
||||
<h2>Kommerziell gedacht, nicht nur hübsch.</h2>
|
||||
<p class="eyebrow">Auf einen Blick</p>
|
||||
<h2>Alles, was für eine entspannte Buchung wichtig ist.</h2>
|
||||
<ul class="check-list">
|
||||
<?php foreach ($serviceStandards as $standard): ?>
|
||||
<li><?= h($standard) ?></li>
|
||||
@@ -107,8 +106,8 @@
|
||||
|
||||
<section class="section">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Leistungsmodule</p>
|
||||
<h2>Technik, Eventbetrieb und Verwaltung greifen ineinander.</h2>
|
||||
<p class="eyebrow">Leistungen</p>
|
||||
<h2>Alles drin für eine Fotobox, die sofort einsatzbereit ist.</h2>
|
||||
</div>
|
||||
<div class="module-grid">
|
||||
<?php foreach ($serviceModules as $module): ?>
|
||||
@@ -127,7 +126,7 @@
|
||||
<section class="section">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Anlässe</p>
|
||||
<h2>Für Privatfeiern und professionelle Events geeignet.</h2>
|
||||
<h2>Die passende Fotobox für Ihr Event in <?= h($company['service_area']) ?>.</h2>
|
||||
</div>
|
||||
<div class="occasion-grid">
|
||||
<?php foreach ($occasionCards as $occasion): ?>
|
||||
@@ -142,8 +141,8 @@
|
||||
<section class="section split-section">
|
||||
<div class="content-card">
|
||||
<p class="eyebrow">Verfügbarkeit</p>
|
||||
<h2>Aktuell geblockte oder bestätigte Zeiträume</h2>
|
||||
<p>Die Übersicht stammt direkt aus dem Verwaltungssystem und zeigt belegte Termine.</p>
|
||||
<h2>Bereits reservierte Termine</h2>
|
||||
<p>Hier sehen Sie, welche Zeiträume aktuell angefragt, reserviert oder bereits bestätigt sind.</p>
|
||||
<div class="availability-list">
|
||||
<?php if ($bookings === []): ?>
|
||||
<article class="availability-card">
|
||||
@@ -154,24 +153,25 @@
|
||||
<?php foreach ($bookings as $booking): ?>
|
||||
<article class="availability-card">
|
||||
<div>
|
||||
<strong><?= h($booking['reference']) ?></strong>
|
||||
<span><?= h(formatDate($booking['start_date'])) ?> bis <?= h(formatDate($booking['end_date'])) ?></span>
|
||||
<strong><?= h(formatDate($booking['start_date'])) ?> bis <?= h(formatDate($booking['end_date'])) ?></strong>
|
||||
<span><?= h((string) ($booking['delivery_zone_label'] ?: $booking['delivery_mode_label'])) ?></span>
|
||||
</div>
|
||||
<span class="<?= h(statusPillClass((string) $booking['status'])) ?>"><?= h($booking['status_label']) ?></span>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<a class="button-secondary" href="<?= h(url('verfuegbarkeit')) ?>">Gesamte Verfügbarkeit ansehen</a>
|
||||
<a class="button-secondary" href="<?= h(url('verfuegbarkeit')) ?>">Wunschtermin prüfen</a>
|
||||
</div>
|
||||
|
||||
<div class="content-card emphasis-card">
|
||||
<p class="eyebrow">Nächster Schritt</p>
|
||||
<h2>In wenigen Minuten zur Anfrage</h2>
|
||||
<p class="eyebrow">Jetzt anfragen</p>
|
||||
<h2>Unverbindlich Verfügbarkeit anfragen</h2>
|
||||
<ul class="check-list">
|
||||
<?php foreach ($bookingChecklist as $item): ?>
|
||||
<li><?= h($item) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<p>Lieferpreise richten sich nach dem Zielort: Hannover, Region Hannover oder Hameln, Braunschweig, Hildesheim und Celle.</p>
|
||||
<div class="pricing-example-list">
|
||||
<?php foreach ($pricingExamples as $example): ?>
|
||||
<article>
|
||||
@@ -180,6 +180,6 @@
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<a class="button-primary button-block" href="<?= h(url('buchen')) ?>">Zur Buchungsanfrage</a>
|
||||
<a class="button-primary button-block" href="<?= h(url('buchen')) ?>">Wunschtermin anfragen</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+37
-12
@@ -2,16 +2,15 @@
|
||||
$app = appConfig();
|
||||
$company = $app['company'];
|
||||
$metaTitle = isset($pageTitle) ? $pageTitle . ' | ' . $company['name'] : $company['name'];
|
||||
$metaDescription = $metaDescription ?? 'Professionelle Fotobox-Vermietung mit klarer Buchungsanfrage und Verwaltungsbereich.';
|
||||
$metaDescription = $metaDescription ?? 'Professionelle Fotobox-Vermietung mit klaren Preisen, einfacher Anfrage und digitaler Bildübergabe.';
|
||||
$isAdminArea = str_contains($viewPath, '/admin/');
|
||||
$styleVersion = is_file(dirname(__DIR__) . '/assets/styles.css') ? (string) filemtime(dirname(__DIR__) . '/assets/styles.css') : '1';
|
||||
$scriptVersion = is_file(dirname(__DIR__) . '/assets/app.js') ? (string) filemtime(dirname(__DIR__) . '/assets/app.js') : '1';
|
||||
$currentPath = currentPath();
|
||||
|
||||
$publicNav = [
|
||||
['label' => 'Leistungen', 'path' => '/leistungen'],
|
||||
['label' => 'Fotobox', 'path' => '/fotobox', 'activePaths' => ['/fotobox', '/leistungen']],
|
||||
['label' => 'Preise', 'path' => '/preise'],
|
||||
['label' => 'Verfügbarkeit', 'path' => '/verfuegbarkeit'],
|
||||
['label' => 'Ablauf', 'path' => '/ablauf'],
|
||||
['label' => 'FAQ', 'path' => '/faq'],
|
||||
['label' => 'Kontakt', 'path' => '/kontakt'],
|
||||
@@ -78,14 +77,40 @@ $adminNav = [
|
||||
</form>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<nav class="site-nav site-nav-public" aria-label="Hauptnavigation">
|
||||
<?php foreach ($publicNav as $item): ?>
|
||||
<a class="<?= $currentPath === $item['path'] ? 'is-active' : '' ?>" href="<?= h(url($item['path'])) ?>"><?= h($item['label']) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="header-actions">
|
||||
<a class="button-secondary" href="<?= h(url('kontakt')) ?>">Kontakt</a>
|
||||
<a class="button-primary" href="<?= h(url('buchen')) ?>">Buchungsanfrage</a>
|
||||
<div class="public-header-shell">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-toggle"
|
||||
data-nav-toggle
|
||||
aria-expanded="false"
|
||||
aria-controls="public-navigation"
|
||||
>
|
||||
<span class="nav-toggle-box" aria-hidden="true">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</span>
|
||||
<span>Menü</span>
|
||||
</button>
|
||||
<div class="public-header-controls" id="public-navigation" data-nav-menu>
|
||||
<nav class="site-nav site-nav-public" aria-label="Hauptnavigation">
|
||||
<?php foreach ($publicNav as $item): ?>
|
||||
<?php $isActive = in_array($currentPath, $item['activePaths'] ?? [$item['path']], true); ?>
|
||||
<a class="<?= $isActive ? 'is-active' : '' ?>" href="<?= h(url($item['path'])) ?>"><?= h($item['label']) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="menu-meta">
|
||||
<span><?= h($company['service_area']) ?></span>
|
||||
<a href="mailto:<?= h($company['email']) ?>"><?= h($company['email']) ?></a>
|
||||
</div>
|
||||
<div class="header-actions header-actions-public">
|
||||
<a class="contact-chip" href="tel:<?= h($company['phone']) ?>">
|
||||
<span>Telefon</span>
|
||||
<strong><?= h($company['phone']) ?></strong>
|
||||
</a>
|
||||
<a class="button-primary" href="<?= h(url('verfuegbarkeit')) ?>">Verfügbarkeit prüfen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -112,7 +137,7 @@ $adminNav = [
|
||||
</div>
|
||||
<div>
|
||||
<h3>Seiten</h3>
|
||||
<a href="<?= h(url('leistungen')) ?>">Leistungen</a>
|
||||
<a href="<?= h(url('fotobox')) ?>">Fotobox</a>
|
||||
<a href="<?= h(url('preise')) ?>">Preise</a>
|
||||
<a href="<?= h(url('buchen')) ?>">Buchen</a>
|
||||
<a href="<?= h(url('faq')) ?>">FAQ</a>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section class="page-hero">
|
||||
<p class="eyebrow">Ablauf</p>
|
||||
<h1>Von der Anfrage bis zur Rückgabe klar geführt.</h1>
|
||||
<p>Die Seite ist so gebaut, dass Privatkunden und Firmenkunden denselben klaren Ablauf erleben.</p>
|
||||
<h1>So einfach mieten Sie Ihre Fotobox</h1>
|
||||
<p>Von der Anfrage bis zur Rückgabe: In wenigen Schritten zu Ihrer Fotobox.</p>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
@@ -22,21 +22,21 @@
|
||||
|
||||
<section class="section split-section">
|
||||
<article class="content-card">
|
||||
<h2>Abholung und Rückgabe</h2>
|
||||
<h2>Abholung oder Lieferung</h2>
|
||||
<ul class="check-list">
|
||||
<li><?= h($company['pickup_window']) ?></li>
|
||||
<li><?= h($company['return_window']) ?></li>
|
||||
<li>Bei der Abholung erhalten Sie eine kurze Einweisung in die Fotobox.</li>
|
||||
<li>Lieferung und Aufbau können im Anfrageprozess gewählt werden.</li>
|
||||
<li>Der Mietzeitraum wird immer über Übernachtungen berechnet.</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="content-card">
|
||||
<h2>Verwaltung im Hintergrund</h2>
|
||||
<h2>Was nach Ihrer Anfrage passiert</h2>
|
||||
<ul class="check-list">
|
||||
<li>Anfragen werden im Backend geprüft und bestätigt.</li>
|
||||
<li>Für bestätigte Aufträge können Rechnungen mit Kundendaten erstellt werden.</li>
|
||||
<li>Zahlungsstatus und interne Notizen bleiben jederzeit nachvollziehbar.</li>
|
||||
<li>Wir prüfen die Verfügbarkeit Ihres Wunschtermins.</li>
|
||||
<li>Sie erhalten eine Rückmeldung zum Termin und zum weiteren Ablauf.</li>
|
||||
<li>Auf Wunsch erstellen wir eine Rechnung mit Ihren Kundendaten.</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<section class="page-hero">
|
||||
<p class="eyebrow">Buchungsanfrage</p>
|
||||
<h1>Fotobox jetzt anfragen.</h1>
|
||||
<h1>Verfügbarkeit Ihrer Fotobox prüfen</h1>
|
||||
<p>
|
||||
Wählen Sie Ihren Zeitraum, legen Sie Leistungsart und Zahlungsart fest und senden Sie Ihre Anfrage direkt an die Verwaltung.
|
||||
Wählen Sie Abhol- und Rückgabetag und senden Sie uns Ihre unverbindliche Anfrage.
|
||||
Ein Miettag entspricht immer einer Übernachtung.
|
||||
Selbstabholung kostet <?= h(formatCurrency((int) $dayRate)) ?> pro Miettag, Lieferungen werden je nach Zielort berechnet.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="section split-section">
|
||||
<article class="content-card emphasis-card">
|
||||
<p class="eyebrow">Vor dem Absenden</p>
|
||||
<h2>Was wir für eine saubere Bearbeitung brauchen</h2>
|
||||
<p class="eyebrow">In 2 Minuten zur Anfrage</p>
|
||||
<h2>Diese Angaben benötigen wir für Ihre Anfrage</h2>
|
||||
<p>Mit Ihren Angaben prüfen wir den Termin, erfassen die Rechnungsdaten und melden uns zeitnah mit einer Bestätigung oder Rückfrage.</p>
|
||||
<ul class="check-list">
|
||||
<?php foreach ($bookingChecklist as $item): ?>
|
||||
<li><?= h($item) ?></li>
|
||||
@@ -25,4 +28,3 @@
|
||||
<?php require dirname(__DIR__) . '/partials/public-booking-form.php'; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
+2
-3
@@ -1,7 +1,7 @@
|
||||
<section class="page-hero">
|
||||
<p class="eyebrow">FAQ</p>
|
||||
<h1>Häufige Fragen vor der Anfrage.</h1>
|
||||
<p>Hier finden Sie die Punkte, die in Vermietung, Zahlung und Rückgabe am häufigsten geklärt werden müssen.</p>
|
||||
<h1>Häufige Fragen zur Fotobox-Miete</h1>
|
||||
<p>Hier finden Sie Antworten zu Mietdauer, Zahlung, Lieferung, Rückgabe und digitaler Bildübergabe.</p>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
@@ -14,4 +14,3 @@
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section class="page-hero">
|
||||
<p class="eyebrow">Kontakt</p>
|
||||
<h1>Direkt erreichbar für Fragen zu Termin, Lieferung und Rechnung.</h1>
|
||||
<p>Wenn Sie vor der Anfrage noch etwas abstimmen möchten, erreichen Sie uns über die folgenden Kontaktwege.</p>
|
||||
<h1>Wir beraten Sie gerne persönlich.</h1>
|
||||
<p>Wenn Sie vor Ihrer Anfrage noch Fragen zu Termin, Lieferung oder Zahlungsart haben, erreichen Sie uns direkt über die folgenden Kontaktwege.</p>
|
||||
</section>
|
||||
|
||||
<section class="section split-section">
|
||||
@@ -16,14 +16,13 @@
|
||||
</div>
|
||||
</article>
|
||||
<article class="content-card emphasis-card">
|
||||
<h2>Was wir schnell beantworten können</h2>
|
||||
<h2>Wobei wir Sie unterstützen</h2>
|
||||
<ul class="check-list">
|
||||
<li>Prüfung von Wunschterminen</li>
|
||||
<li>Lieferung, Aufbau und regionale Einsatzorte</li>
|
||||
<li>Fragen zu Rechnung, Zahlungsart und Mietdauer</li>
|
||||
<li>Abstimmung von Firmenveranstaltungen und Sonderfällen</li>
|
||||
</ul>
|
||||
<a class="button-primary" href="<?= h(url('buchen')) ?>">Zur Buchungsanfrage</a>
|
||||
<a class="button-primary" href="<?= h(url('buchen')) ?>">Jetzt unverbindlich anfragen</a>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<section class="page-hero">
|
||||
<p class="eyebrow">Leistungen & Ausstattung</p>
|
||||
<h1>Eine Fotobox, die technisch überzeugt und organisatorisch mitdenkt.</h1>
|
||||
<p class="eyebrow">Fotobox & Leistungen</p>
|
||||
<h1>Professionelle Fotobox für Hochzeit, Geburtstag und Firmenfeier.</h1>
|
||||
<p>
|
||||
Diese Seite zeigt nicht nur die Technik, sondern den gesamten Service:
|
||||
Bildqualität, Bedienbarkeit, Logistik, digitale Übergabe und die kaufmännische Abwicklung.
|
||||
Hochwertige Bilder, einfache Bedienung und eine klare Abwicklung.
|
||||
Hier sehen Sie, was im Preis enthalten ist und welche Leistungen zusätzlich möglich sind.
|
||||
Von der Spiegelreflexkamera bis zum WLAN-Download ist alles auf eine einfache Nutzung ausgelegt.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -33,10 +34,9 @@
|
||||
|
||||
<section class="section cta-band">
|
||||
<div>
|
||||
<p class="eyebrow">Buchung</p>
|
||||
<h2>Sie wissen schon, was Sie brauchen?</h2>
|
||||
<p>Dann prüfen Sie direkt Ihren Zeitraum und senden Sie Ihre Anfrage digital.</p>
|
||||
<p class="eyebrow">Wunschtermin</p>
|
||||
<h2>Prüfen Sie direkt die Verfügbarkeit Ihrer Fotobox.</h2>
|
||||
<p>Senden Sie uns Ihre Anfrage unverbindlich online. Wir melden uns zeitnah zurück.</p>
|
||||
</div>
|
||||
<a class="button-primary" href="<?= h(url('buchen')) ?>">Jetzt anfragen</a>
|
||||
<a class="button-primary" href="<?= h(url('verfuegbarkeit')) ?>">Verfügbarkeit prüfen</a>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</article>
|
||||
<article class="legal-card">
|
||||
<h2>3. Zahlung</h2>
|
||||
<p>Zahlungen sind per Rechnung / Überweisung oder per PayPal möglich. Die gewählte Zahlungsart wird bei der Anfrage erfasst und kann im Verwaltungsprozess hinterlegt werden.</p>
|
||||
<p>Zahlungen sind per Rechnung / Überweisung oder per PayPal möglich. Die gewünschte Zahlungsart wird direkt in Ihrer Anfrage angegeben.</p>
|
||||
</article>
|
||||
<article class="legal-card">
|
||||
<h2>4. Übergabe und Rückgabe</h2>
|
||||
@@ -26,4 +26,3 @@
|
||||
<p>Bitte melden Sie Störungen oder Schäden unverzüglich. Für einen verbindlichen gewerblichen Einsatz sollten Haftungs- und Ausfallregelungen vor dem Live-Start individuell ergänzt werden.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
+31
-21
@@ -1,36 +1,47 @@
|
||||
<section class="page-hero">
|
||||
<p class="eyebrow">Preise & Mietlogik</p>
|
||||
<h1>Klare Preise ohne versteckte Logik.</h1>
|
||||
<h1>Fotobox-Preise auf einen Blick</h1>
|
||||
<p>
|
||||
Der Standardpreis beträgt <strong><?= h(formatCurrency((int) $dayRate)) ?></strong> pro Miettag.
|
||||
Ein Miettag entspricht immer einer Übernachtung.
|
||||
<strong>Abholung ab <?= h(formatCurrency((int) $dayRate)) ?> pro Miettag.</strong>
|
||||
Lieferpreise richten sich nach dem Zielort. Ein Miettag entspricht immer einer Übernachtung.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="section split-section">
|
||||
<article class="content-card emphasis-card">
|
||||
<p class="eyebrow">Grundpreis</p>
|
||||
<h2><?= h(formatCurrency((int) $dayRate)) ?> pro Miettag</h2>
|
||||
<p>Montag bis Dienstag = 1 Miettag. Freitag bis Sonntag = 2 Miettage.</p>
|
||||
<p class="eyebrow">Preis je Liefergebiet</p>
|
||||
<h2>Abholung oder Lieferung nach Region</h2>
|
||||
<p>Ein Miettag entspricht einer Übernachtung. Montag bis Dienstag = 1 Miettag. Freitag bis Sonntag = 2 Miettage.</p>
|
||||
<ul class="check-list">
|
||||
<li>Technikpaket mit DSLR-Kamera, Blitz und Softbox</li>
|
||||
<li>Digitale Bildübergabe inklusive</li>
|
||||
<li>Zahlung per Rechnung, Überweisung oder PayPal</li>
|
||||
<li>Verbindlichkeit erst nach Bestätigung Ihrer Anfrage</li>
|
||||
<?php foreach ($pricingExamples as $example): ?>
|
||||
<li><strong><?= h($example['title']) ?>:</strong> <?= h($example['text']) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="content-card">
|
||||
<p class="eyebrow">Preisbeispiele</p>
|
||||
<p class="eyebrow">Was im Preis enthalten ist</p>
|
||||
<div class="pricing-example-list">
|
||||
<?php foreach ($pricingExamples as $example): ?>
|
||||
<article>
|
||||
<strong><?= h($example['title']) ?></strong>
|
||||
<span><?= h($example['text']) ?></span>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
<article>
|
||||
<strong>Technik</strong>
|
||||
<span>Fotobox, Spiegelreflexkamera, Studioblitz und Softbox</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong>Bilder</strong>
|
||||
<span>Alle Fotos digital inklusive</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong>Abwicklung</strong>
|
||||
<span>Zahlung per Rechnung / Überweisung oder PayPal</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong>Anfrage</strong>
|
||||
<span>Verbindlich wird Ihre Buchung erst nach unserer Bestätigung</span>
|
||||
</article>
|
||||
</div>
|
||||
<p class="small-note">
|
||||
Lieferung, Aufbau oder Vor-Ort-Betreuung werden im Anfrageprozess passend zum Anlass abgestimmt.
|
||||
Hannover kostet <?= h(formatCurrency((int) $config['pricing']['delivery_rates']['hannover']['price_cents'])) ?>,
|
||||
die Region Hannover <?= h(formatCurrency((int) $config['pricing']['delivery_rates']['region_hannover']['price_cents'])) ?> und
|
||||
Hameln, Braunschweig, Hildesheim oder Celle <?= h(formatCurrency((int) $config['pricing']['delivery_rates']['extended_region']['price_cents'])) ?> pro Miettag.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
@@ -39,7 +50,7 @@
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Zahlung</p>
|
||||
<h2>Rechnung, Überweisung oder PayPal</h2>
|
||||
<p>Die gewünschte Zahlungsart wird bereits in der Anfrage hinterlegt und kann im Backend verwaltet werden.</p>
|
||||
<p>Die gewünschte Zahlungsart geben Sie direkt in Ihrer Anfrage an. Auf Wunsch erhalten Sie eine Rechnung mit vollständigen Kundendaten.</p>
|
||||
</div>
|
||||
<div class="trust-grid">
|
||||
<article class="trust-card">
|
||||
@@ -48,7 +59,7 @@
|
||||
</article>
|
||||
<article class="trust-card">
|
||||
<span>PayPal</span>
|
||||
<strong>Als Zahlungsart auswählbar</strong>
|
||||
<strong>Direkt in der Anfrage auswählbar</strong>
|
||||
</article>
|
||||
<article class="trust-card">
|
||||
<span>Steuerhinweis</span>
|
||||
@@ -56,4 +67,3 @@
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,27 +1,91 @@
|
||||
<section class="page-hero">
|
||||
<p class="eyebrow">Verfügbarkeit</p>
|
||||
<h1>Geblockte und bereits bestätigte Zeiträume im Blick.</h1>
|
||||
<h1>Prüfen Sie, ob Ihr Wunschtermin noch frei ist.</h1>
|
||||
<p>
|
||||
Die Übersicht zeigt aktuelle Belegungen aus dem Verwaltungssystem.
|
||||
Für Ihren Wunschtermin senden Sie am besten direkt eine Anfrage.
|
||||
Hier sehen Sie bereits belegte Termine im Monatskalender.
|
||||
Ist Ihr Wunschtermin noch frei, senden Sie uns direkt Ihre unverbindliche Anfrage.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Buchungskalender</p>
|
||||
<h2>Belegte Termine auf einen Blick</h2>
|
||||
<p>Die Kalenderansicht zeigt angefragte, reservierte und bestätigte Zeiträume für die nächsten Monate.</p>
|
||||
</div>
|
||||
<div class="calendar-legend" aria-label="Legende zur Verfügbarkeit">
|
||||
<span class="status-pill status-requested">Anfrage</span>
|
||||
<span class="status-pill status-reserved">Reserviert</span>
|
||||
<span class="status-pill status-confirmed">Bestätigt</span>
|
||||
</div>
|
||||
<div class="public-calendar-grid">
|
||||
<?php foreach ($availabilityCalendarMonths as $month): ?>
|
||||
<article class="calendar-month-card">
|
||||
<div class="calendar-month-header">
|
||||
<h2><?= h($month['label']) ?></h2>
|
||||
<span><?= h((string) $month['entry_count']) ?> Termine</span>
|
||||
</div>
|
||||
<div class="calendar-weekdays" aria-hidden="true">
|
||||
<span>Mo</span>
|
||||
<span>Di</span>
|
||||
<span>Mi</span>
|
||||
<span>Do</span>
|
||||
<span>Fr</span>
|
||||
<span>Sa</span>
|
||||
<span>So</span>
|
||||
</div>
|
||||
<div class="calendar-days">
|
||||
<?php foreach ($month['days'] as $day): ?>
|
||||
<?php if (!empty($day['is_padding'])): ?>
|
||||
<div class="calendar-day calendar-day-empty" aria-hidden="true"></div>
|
||||
<?php else: ?>
|
||||
<div class="calendar-day<?= !empty($day['is_booked']) ? ' is-booked calendar-day-' . h((string) $day['status']) : '' ?><?= !empty($day['is_today']) ? ' is-today' : '' ?>">
|
||||
<span class="calendar-day-number"><?= h((string) $day['day']) ?></span>
|
||||
<?php if (!empty($day['is_booked'])): ?>
|
||||
<span class="calendar-day-state"><?= h((string) $day['status_label']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="calendar-entry-list">
|
||||
<?php if ($month['entries'] === []): ?>
|
||||
<article class="calendar-entry">
|
||||
<strong>Derzeit kein belegter Zeitraum.</strong>
|
||||
<span>Dieser Monat ist aktuell noch frei.</span>
|
||||
</article>
|
||||
<?php else: ?>
|
||||
<?php foreach ($month['entries'] as $entry): ?>
|
||||
<article class="calendar-entry">
|
||||
<div>
|
||||
<strong><?= h($entry['date_label']) ?></strong>
|
||||
<span><?= h($entry['delivery_label']) ?> · <?= h($entry['day_count_label']) ?></span>
|
||||
</div>
|
||||
<span class="<?= h(statusPillClass((string) $entry['status'])) ?>"><?= h($entry['status_label']) ?></span>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section split-section">
|
||||
<article class="content-card">
|
||||
<h2>Aktuelle Belegung</h2>
|
||||
<div class="availability-list">
|
||||
<?php if ($bookings === []): ?>
|
||||
<?php if ($availabilityBookings === []): ?>
|
||||
<article class="availability-card">
|
||||
<strong>Momentan gibt es keine festen Einträge.</strong>
|
||||
<span>Ihre Anfrage kann direkt neu aufgenommen werden.</span>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($bookings as $booking): ?>
|
||||
<?php foreach ($availabilityBookings as $booking): ?>
|
||||
<article class="availability-card">
|
||||
<div>
|
||||
<strong><?= h($booking['reference']) ?></strong>
|
||||
<span><?= h(formatDate($booking['start_date'])) ?> bis <?= h(formatDate($booking['end_date'])) ?></span>
|
||||
<strong><?= h(formatDate($booking['start_date'])) ?> bis <?= h(formatDate($booking['end_date'])) ?></strong>
|
||||
<span><?= h((string) ($booking['delivery_zone_label'] ?: $booking['delivery_mode_label'])) ?> · <?= h((string) $booking['total_days']) ?> <?= $booking['total_days'] === 1 ? 'Miettag' : 'Miettage' ?></span>
|
||||
</div>
|
||||
<span class="<?= h(statusPillClass((string) $booking['status'])) ?>"><?= h($booking['status_label']) ?></span>
|
||||
</article>
|
||||
@@ -29,13 +93,18 @@
|
||||
</div>
|
||||
</article>
|
||||
<article class="content-card emphasis-card">
|
||||
<h2>Direkt zur Anfrage</h2>
|
||||
<h2>Jetzt unverbindlich anfragen</h2>
|
||||
<ul class="check-list">
|
||||
<li>Zeitraum nach Übernachtungen wählen</li>
|
||||
<li>Lieferart und Zahlungsart festlegen</li>
|
||||
<li>Kundendaten für Rechnung und Rückfragen erfassen</li>
|
||||
<li>Wunschtermin auswählen</li>
|
||||
<li>Leistungsart, Liefergebiet und Zahlungsart festlegen</li>
|
||||
<li>Kontaktdaten und Veranstaltungsort eintragen</li>
|
||||
</ul>
|
||||
<a class="button-primary button-block" href="<?= h(url('buchen')) ?>">Jetzt Termin anfragen</a>
|
||||
<p class="small-note">
|
||||
Selbstabholung: <?= h(formatCurrency((int) $config['pricing']['delivery_rates']['self_pickup']['price_cents'])) ?> ·
|
||||
Hannover: <?= h(formatCurrency((int) $config['pricing']['delivery_rates']['hannover']['price_cents'])) ?> ·
|
||||
Region Hannover: <?= h(formatCurrency((int) $config['pricing']['delivery_rates']['region_hannover']['price_cents'])) ?> ·
|
||||
Hameln, Braunschweig, Hildesheim oder Celle: <?= h(formatCurrency((int) $config['pricing']['delivery_rates']['extended_region']['price_cents'])) ?>
|
||||
</p>
|
||||
<a class="button-primary button-block" href="<?= h(url('buchen')) ?>">Wunschtermin anfragen</a>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
$oldData = is_array($old ?? null) ? $old : [];
|
||||
$priceRates = [];
|
||||
foreach ($deliveryZoneOptions as $zoneKey => $zone) {
|
||||
$priceRates[$zoneKey] = (int) $zone['price_cents'];
|
||||
}
|
||||
?>
|
||||
<div class="booking-form-shell">
|
||||
<?php if (!empty($flashSuccess)): ?>
|
||||
@@ -10,7 +14,7 @@ $oldData = is_array($old ?? null) ? $old : [];
|
||||
<div class="flash flash-error"><?= h((string) $flashError) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="<?= h(url('book')) ?>" class="booking-form" data-day-rate="<?= h((string) $dayRate) ?>">
|
||||
<form method="post" action="<?= h(url('book')) ?>" class="booking-form" data-day-rate="<?= h((string) $dayRate) ?>" data-price-rates="<?= h((string) json_encode($priceRates, JSON_THROW_ON_ERROR)) ?>">
|
||||
<?= csrfField() ?>
|
||||
<input type="hidden" name="price_per_day_cents" value="<?= h((string) $dayRate) ?>">
|
||||
|
||||
@@ -35,15 +39,25 @@ $oldData = is_array($old ?? null) ? $old : [];
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<span class="form-step">Schritt 2</span>
|
||||
<h3>Leistung und Zahlung festlegen</h3>
|
||||
<h3>Paket und Zahlung wählen</h3>
|
||||
</div>
|
||||
<div class="form-grid form-grid-two">
|
||||
<label>
|
||||
<span>Leistungsart</span>
|
||||
<select name="delivery_mode">
|
||||
<option value="self_pickup" <?= selected((string) ($oldData['delivery_mode'] ?? 'self_pickup'), 'self_pickup') ?>>Selbstabholung</option>
|
||||
<option value="delivery_setup" <?= selected((string) ($oldData['delivery_mode'] ?? ''), 'delivery_setup') ?>>Lieferung und Aufbau</option>
|
||||
<option value="on_site_support" <?= selected((string) ($oldData['delivery_mode'] ?? ''), 'on_site_support') ?>>Lieferung, Aufbau und Vor-Ort-Betreuung</option>
|
||||
<select name="delivery_mode" data-delivery-mode>
|
||||
<?php foreach ($deliveryModeOptions as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>" <?= selected((string) ($oldData['delivery_mode'] ?? 'self_pickup'), $value) ?>><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Liefergebiet</span>
|
||||
<select name="delivery_zone" data-delivery-zone>
|
||||
<?php foreach ($deliveryZoneOptions as $value => $zone): ?>
|
||||
<option value="<?= h($value) ?>" <?= selected((string) ($oldData['delivery_zone'] ?? 'self_pickup'), $value) ?>>
|
||||
<?= h($zone['label']) ?> · <?= h(formatCurrency((int) $zone['price_cents'])) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
@@ -54,12 +68,18 @@ $oldData = is_array($old ?? null) ? $old : [];
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<p class="form-help">
|
||||
Abholung kostet <?= h(formatCurrency((int) $deliveryZoneOptions['self_pickup']['price_cents'])) ?> pro Miettag.
|
||||
Lieferung nach Hannover kostet <?= h(formatCurrency((int) $deliveryZoneOptions['hannover']['price_cents'])) ?>,
|
||||
in die Region Hannover <?= h(formatCurrency((int) $deliveryZoneOptions['region_hannover']['price_cents'])) ?> und
|
||||
nach Hameln, Braunschweig, Hildesheim oder Celle <?= h(formatCurrency((int) $deliveryZoneOptions['extended_region']['price_cents'])) ?> pro Miettag.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<span class="form-step">Schritt 3</span>
|
||||
<h3>Kundendaten erfassen</h3>
|
||||
<h3>Kontaktdaten und Veranstaltungsort</h3>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
@@ -112,7 +132,7 @@ $oldData = is_array($old ?? null) ? $old : [];
|
||||
</div>
|
||||
<div class="summary-line">
|
||||
<span>Preis pro Miettag</span>
|
||||
<strong><?= h(formatCurrency((int) $dayRate)) ?></strong>
|
||||
<strong data-summary-rate><?= h(formatCurrency((int) $dayRate)) ?></strong>
|
||||
</div>
|
||||
<div class="summary-line summary-line-total">
|
||||
<span>Voraussichtlicher Gesamtpreis</span>
|
||||
@@ -131,7 +151,7 @@ $oldData = is_array($old ?? null) ? $old : [];
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button-primary button-block">Buchungsanfrage senden</button>
|
||||
<p class="form-note">Keine Sofortabbuchung. Ihr Auftrag wird erst nach persönlicher Bestätigung verbindlich.</p>
|
||||
<button type="submit" class="button-primary button-block">Buchungsanfrage unverbindlich senden</button>
|
||||
<p class="form-note">Sie erhalten schnell eine Rückmeldung zur Verfügbarkeit und zum weiteren Ablauf.</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user