feat: price calendar and price table for group houses
Ports AjaxCalendarController::priceCalendarAction and priceTableAction (MyE&P price config and price table), used on the /gruppe/gruppenhaus-* pages and example trips. Page cache tags and "do not cache" on API errors go through the frontend.cache.collector and frontend.cache.instruction request attributes instead of TSFE. Left out: priceConfig (no caller) and range (daytrip calendar via bpn-connect, out of scope), so bpn-connect needs no credentials. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
294d270bc1
commit
c1b4979583
5 files changed
+446
-3
No files matched your search
@@ -0,0 +1,76 @@
|
||||
<html data-namespace-typo3-fluid="true" lang="en"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
|
||||
|
||||
<f:if condition="{error}">
|
||||
<f:then>
|
||||
<div class="p-4 text-sm text-red-600">
|
||||
Der Kalender konnte aktuell nicht geladen werden.
|
||||
</div>
|
||||
<div class="hidden" data-price-calendar-error-detail>{errorMessage}</div>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<button type="button" class="px-3 py-1 text-sm bg-zinc-100 hover:bg-zinc-200 rounded transition-colors disabled:opacity-40 disabled:cursor-default disabled:pointer-events-none"
|
||||
data-price-calendar-target="prevButton"
|
||||
data-action="click->price-calendar#prevMonths"
|
||||
disabled>← früher</button>
|
||||
<button type="button" class="px-3 py-1 text-sm bg-zinc-100 hover:bg-zinc-200 rounded transition-colors disabled:opacity-40 disabled:cursor-default disabled:pointer-events-none"
|
||||
data-price-calendar-target="nextButton"
|
||||
data-action="click->price-calendar#nextMonths">später →</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<f:for each="{calendarMonths}" as="month" iteration="i">
|
||||
<div data-month-index="{i.index}" data-price-calendar-target="month">
|
||||
<div class="font-semibold text-center py-2 text-zinc-700">
|
||||
<f:format.date format="%B %Y">{month.date}</f:format.date>
|
||||
</div>
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-1 text-center text-xs text-zinc-500 font-normal">Mo</th>
|
||||
<th class="py-1 text-center text-xs text-zinc-500 font-normal">Di</th>
|
||||
<th class="py-1 text-center text-xs text-zinc-500 font-normal">Mi</th>
|
||||
<th class="py-1 text-center text-xs text-zinc-500 font-normal">Do</th>
|
||||
<th class="py-1 text-center text-xs text-zinc-500 font-normal">Fr</th>
|
||||
<th class="py-1 text-center text-xs text-zinc-500 font-normal">Sa</th>
|
||||
<th class="py-1 text-center text-xs text-zinc-500 font-normal">So</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<f:for each="{month.weeks}" as="week">
|
||||
<tr>
|
||||
<f:for each="{week}" as="day">
|
||||
<td class="p-px">
|
||||
<f:if condition="{day.inMonth}">
|
||||
<f:then>
|
||||
<f:variable name="dateKey" value="{f:format.date(format: 'Y-m-d', date: day.date)}"/>
|
||||
<f:variable name="dayData" value="{priceDataByDate.{dateKey}}"/>
|
||||
<button type="button"
|
||||
class="pc-day"
|
||||
data-price-calendar-target="day"
|
||||
data-action="click->price-calendar#selectDate"
|
||||
data-date="{dateKey}"
|
||||
data-status="{dayData.status}"
|
||||
data-min-nights="{dayData.minNights}"
|
||||
<f:if condition="{dayData.tooltip}">aria-label="{dayData.tooltip}" data-cooltipz-dir="top"</f:if>>
|
||||
{f:format.date(format: 'j', date: day.date)}
|
||||
</button>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<div class="pc-day"> </div>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</td>
|
||||
</f:for>
|
||||
</tr>
|
||||
</f:for>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</f:for>
|
||||
</div>
|
||||
</f:else>
|
||||
</f:if>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,79 @@
|
||||
<html data-namespace-typo3-fluid="true" lang="en"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
|
||||
|
||||
<f:if condition="{priceTable.success} && {priceTable.data -> f:count()}">
|
||||
<div class="w-full overflow-x-auto mb-4">
|
||||
<h3 class="font-bold mt-4 mb-2">
|
||||
Preise {year}
|
||||
</h3>
|
||||
<table class="w-full mb-4 bg-zinc-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base" scope="col">
|
||||
von
|
||||
</th>
|
||||
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base" scope="col">
|
||||
bis
|
||||
</th>
|
||||
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base" scope="col">
|
||||
Saison
|
||||
</th>
|
||||
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-right text-sm md:text-base" scope="col">
|
||||
Pax bis
|
||||
</th>
|
||||
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-right text-sm md:text-base" scope="col">
|
||||
Mindestmiete pro Nacht
|
||||
</th>
|
||||
<th class="px-2 py-1 md:p-2 text-right text-sm md:text-base" scope="col">
|
||||
weitere Personen pro Nacht
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<f:for each="{priceTable.data}" as="row">
|
||||
<tr class="odd:bg-zinc-50 hover:bg-zinc-100 odd:hover:bg-zinc-100">
|
||||
<td class="border-r border-zinc-200 px-2 py-1 md:p-2 text-sm md:text-base">
|
||||
{row.dateFrom -> f:format.date(format: 'd.m.Y')}
|
||||
</td>
|
||||
<td class="border-r border-zinc-200 px-2 py-1 md:p-2 text-sm md:text-base">
|
||||
{row.dateTo -> f:format.date(format: 'd.m.Y')}
|
||||
</td>
|
||||
<td class="border-r border-zinc-200 px-2 py-1 md:p-2 text-sm md:text-base">
|
||||
<f:switch expression="{row.season}">
|
||||
<f:case value="adv_secondary">VNS</f:case>
|
||||
<f:case value="secondary">NS</f:case>
|
||||
<f:case value="peak">HS</f:case>
|
||||
<f:defaultCase>{row.season}</f:defaultCase>
|
||||
</f:switch>
|
||||
</td>
|
||||
<td class="border-r border-zinc-200 px-2 py-1 md:p-2 text-right text-sm md:text-base">
|
||||
{row.includedPax}
|
||||
</td>
|
||||
<td class="border-r border-zinc-200 px-2 py-1 md:p-2 text-right text-sm md:text-base">
|
||||
<f:if condition="{row.type} == 'discount'">
|
||||
<f:then>
|
||||
<f:if condition="{row.defaultPricePerNight}">
|
||||
<span class="line-through text-zinc-400 mr-1">{row.defaultPricePerNight -> f:format.currency(currencySign: row.currency)}</span>
|
||||
</f:if>
|
||||
{row.pricePerNight -> f:format.currency(currencySign: row.currency)}
|
||||
</f:then>
|
||||
<f:else>
|
||||
{row.pricePerNight -> f:format.currency(currencySign: row.currency)}
|
||||
</f:else>
|
||||
</f:if>
|
||||
</td>
|
||||
<td class="px-2 py-1 md:p-2 text-right text-sm md:text-base">
|
||||
{row.priceAdditionalPerson -> f:format.currency(currencySign: row.currency)}
|
||||
</td>
|
||||
</tr>
|
||||
</f:for>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
VNS = Vorteils-Nebensaison, NS = Nebensaison, HS = Hauptsaison
|
||||
</div>
|
||||
</div>
|
||||
</f:if>
|
||||
|
||||
</html>
|
||||
Reference in new issue
Block a user