feat: groups price calculator admin crud, booking/offer flow and api
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import { Controller } from '@hotwired/stimulus'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = ['mount', 'error', 'message', 'resetButton', 'day', 'dateFrom', 'dateTo', 'submit']
|
||||
|
||||
static values = {
|
||||
hotelCode: String,
|
||||
selectedFrom: String,
|
||||
selectedTo: String,
|
||||
calendarRefreshUrl: String,
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.startDate = null
|
||||
this.endDate = null
|
||||
this.sessionRestored = false
|
||||
|
||||
// Re-apply highlights after every HTMX month swap (navigation or initial load).
|
||||
this.mountTarget.addEventListener('htmx:afterSwap', () => requestAnimationFrame(() => this.initAfterLoad()))
|
||||
this.mountTarget.addEventListener('htmx:responseError', () => this.showError())
|
||||
}
|
||||
|
||||
initAfterLoad() {
|
||||
// Restore from session values exactly once on the first swap.
|
||||
if (!this.sessionRestored) {
|
||||
this.sessionRestored = true
|
||||
const from = this.selectedFromValue
|
||||
const to = this.selectedToValue
|
||||
if (from && to) {
|
||||
this.startDate = from
|
||||
this.endDate = to
|
||||
if (this.hasResetButtonTarget) this.resetButtonTarget.classList.remove('hidden')
|
||||
}
|
||||
}
|
||||
this.updateDisplay()
|
||||
}
|
||||
|
||||
selectDate(event) {
|
||||
const btn = event.currentTarget
|
||||
btn.blur()
|
||||
const date = btn.dataset.date
|
||||
const status = btn.dataset.status
|
||||
|
||||
if (!status || status === 'blocked' || status === 'past') return
|
||||
|
||||
if (this.startDate && this.endDate) {
|
||||
this.resetSelection()
|
||||
}
|
||||
|
||||
if (!this.startDate) {
|
||||
if (status === 'checkout-only') {
|
||||
this.showMessage('Bitte wähle einen Anreisetag.')
|
||||
return
|
||||
}
|
||||
this.startDate = date
|
||||
this.endDate = null
|
||||
this.updateDisplay()
|
||||
this.showMessage('Bitte wähle das Abreisedatum.')
|
||||
return
|
||||
}
|
||||
|
||||
if (date === this.startDate) {
|
||||
this.resetSelection()
|
||||
return
|
||||
}
|
||||
|
||||
const [from, to] = date < this.startDate ? [date, this.startDate] : [this.startDate, date]
|
||||
|
||||
if (this.hasBlockedInRange(from, to)) {
|
||||
this.resetSelection()
|
||||
this.showMessage('Dieser Zeitraum enthält nicht verfügbare Daten.')
|
||||
return
|
||||
}
|
||||
|
||||
const startBtn = this.dayTargets.find(b => b.dataset.date === from)
|
||||
const minNights = parseInt(startBtn?.dataset.minNights) || 0
|
||||
const nights = (new Date(to) - new Date(from)) / 86400000
|
||||
|
||||
if (minNights > 0 && nights < minNights) {
|
||||
this.showMessage(`Weniger als ${minNights} Nächte nur auf Anfrage.`)
|
||||
} else {
|
||||
this.hideMessage()
|
||||
}
|
||||
|
||||
this.startDate = from
|
||||
this.endDate = to
|
||||
this.updateDisplay()
|
||||
this.activateSubmit(from, to)
|
||||
this.saveAndRefreshSummary(from, to)
|
||||
this.dispatch('range-select', { detail: { dateFrom: from, dateTo: to } })
|
||||
}
|
||||
|
||||
hasBlockedInRange(from, to) {
|
||||
for (const btn of this.dayTargets) {
|
||||
const d = btn.dataset.date
|
||||
const s = btn.dataset.status
|
||||
if (d >= from && d < to && (s === 'blocked' || s === 'past' || s === 'checkout-only')) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
for (const btn of this.dayTargets) {
|
||||
const d = btn.dataset.date
|
||||
btn.classList.remove('pc-day--pending-start', 'pc-day--selected-start', 'pc-day--selected-end', 'pc-day--in-range')
|
||||
if (!this.startDate) continue
|
||||
if (d === this.startDate) {
|
||||
btn.classList.add(this.endDate ? 'pc-day--selected-start' : 'pc-day--pending-start')
|
||||
} else if (this.endDate) {
|
||||
if (d === this.endDate) btn.classList.add('pc-day--selected-end')
|
||||
else if (d > this.startDate && d < this.endDate) btn.classList.add('pc-day--in-range')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activateSubmit(dateFrom, dateTo) {
|
||||
if (this.hasDateFromTarget) this.dateFromTarget.value = dateFrom
|
||||
if (this.hasDateToTarget) this.dateToTarget.value = dateTo
|
||||
if (this.hasSubmitTarget) {
|
||||
this.submitTarget.disabled = false
|
||||
this.submitTarget.classList.remove('opacity-50', 'cursor-not-allowed', 'pointer-events-none')
|
||||
}
|
||||
if (this.hasResetButtonTarget) this.resetButtonTarget.classList.remove('hidden')
|
||||
}
|
||||
|
||||
resetSelection() {
|
||||
this.startDate = null
|
||||
this.endDate = null
|
||||
this.updateDisplay()
|
||||
this.hideMessage()
|
||||
if (this.hasDateFromTarget) this.dateFromTarget.value = ''
|
||||
if (this.hasDateToTarget) this.dateToTarget.value = ''
|
||||
if (this.hasSubmitTarget) {
|
||||
this.submitTarget.disabled = true
|
||||
this.submitTarget.classList.add('opacity-50', 'cursor-not-allowed', 'pointer-events-none')
|
||||
}
|
||||
if (this.hasResetButtonTarget) this.resetButtonTarget.classList.add('hidden')
|
||||
this.saveAndRefreshSummary()
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.resetSelection()
|
||||
}
|
||||
|
||||
saveAndRefreshSummary(dateFrom = '', dateTo = '') {
|
||||
if (!this.hasCalendarRefreshUrlValue) return
|
||||
htmx.ajax('POST', this.calendarRefreshUrlValue, {
|
||||
values: { date_from: dateFrom, date_to: dateTo },
|
||||
swap: 'none',
|
||||
target: this.element,
|
||||
})
|
||||
}
|
||||
|
||||
showMessage(text) {
|
||||
if (this.hasMessageTarget) this.messageTarget.textContent = text
|
||||
}
|
||||
|
||||
hideMessage() {
|
||||
if (this.hasMessageTarget) this.messageTarget.textContent = ''
|
||||
}
|
||||
|
||||
showError() {
|
||||
if (this.hasErrorTarget) this.errorTarget.classList.remove('hidden')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user