feat: persist rate overrides to DB when applying prices in AvailabilityPage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ interface AvailabilityCat {
|
||||
|
||||
function buildPriceGrid(
|
||||
categories: AvailabilityCat[],
|
||||
days = 60,
|
||||
days = 90,
|
||||
): Record<string, Record<string, PriceCell>> {
|
||||
const result: Record<string, Record<string, PriceCell>> = {}
|
||||
const today = new Date()
|
||||
@@ -29,13 +29,11 @@ function buildPriceGrid(
|
||||
for (let i = 0; i < days; i++) {
|
||||
const d = addDays(today, i)
|
||||
const dateStr = format(d, 'yyyy-MM-dd')
|
||||
const isWeekend = [5, 6].includes(getDay(d))
|
||||
const basePrice = Math.round(cat.basePrice * (isWeekend ? 1.25 : 1.0))
|
||||
const channelPrices: Record<string, number> = {}
|
||||
for (const ch of RATE_CHANNELS) {
|
||||
channelPrices[ch.id] = Math.round(basePrice * (DEFAULT_CHANNEL_MARKUP[ch.id] ?? 1))
|
||||
channelPrices[ch.id] = Math.round(cat.basePrice * (DEFAULT_CHANNEL_MARKUP[ch.id] ?? 1))
|
||||
}
|
||||
result[cat.id][dateStr] = { price: basePrice, extraPerson: 0, minNights: 1, channelPrices, closed: false }
|
||||
result[cat.id][dateStr] = { price: cat.basePrice, extraPerson: 0, minNights: 1, channelPrices, closed: false }
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -684,63 +682,65 @@ export function AvailabilityPage() {
|
||||
const [pickerDate, setPickerDate] = useState(format(new Date(), DATE_FMT))
|
||||
const navPickerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Load categories, rooms, and rate periods from API
|
||||
// Load categories, rooms, rate periods and overrides from API
|
||||
useEffect(() => {
|
||||
if (!slug) return
|
||||
setLoading(true)
|
||||
Promise.all([
|
||||
api.categories.list(slug),
|
||||
api.rooms.list(slug),
|
||||
api.ratePeriods.list(slug),
|
||||
]).then(([cats, rooms, apiPeriods]) => {
|
||||
api.categories.list(slug).catch(() => []),
|
||||
api.rooms.list(slug).catch(() => []),
|
||||
api.ratePeriods.list(slug).catch(() => []),
|
||||
api.rateOverrides.list(slug).catch(() => []),
|
||||
]).then(([cats, rooms, apiPeriods, apiOverrides]) => {
|
||||
// Build AvailabilityCat[] from real categories, derive basePrice from rooms
|
||||
const availCats: AvailabilityCat[] = cats.map(cat => {
|
||||
const catRooms = rooms.filter(r => r.categoryId === cat.id)
|
||||
const basePrice = catRooms.length > 0
|
||||
? Math.min(...catRooms.map(r => r.baseRate))
|
||||
: 3000
|
||||
return {
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
color: cat.color || '#6366f1',
|
||||
basePrice,
|
||||
}
|
||||
return { id: cat.id, name: cat.name, color: cat.color || '#6366f1', basePrice }
|
||||
})
|
||||
|
||||
// If no categories defined, fall back to room types as groups
|
||||
// If no categories, fall back to room types as groups
|
||||
const colors = ['#6366f1','#10b981','#f59e0b','#ef4444','#8b5cf6','#06b6d4']
|
||||
const finalCats = availCats.length > 0 ? availCats : (() => {
|
||||
const typeMap = new Map<string, number>()
|
||||
for (const r of rooms) {
|
||||
if (!r.type) continue
|
||||
if (!typeMap.has(r.type)) typeMap.set(r.type, r.baseRate)
|
||||
else typeMap.set(r.type, Math.min(typeMap.get(r.type)!, r.baseRate))
|
||||
}
|
||||
const colors = ['#6366f1','#10b981','#f59e0b','#ef4444','#8b5cf6','#06b6d4']
|
||||
return Array.from(typeMap.entries()).map(([name, price], i) => ({
|
||||
id: name.toLowerCase().replace(/\s+/g, '_'),
|
||||
name,
|
||||
color: colors[i % colors.length],
|
||||
basePrice: price,
|
||||
name, color: colors[i % colors.length], basePrice: price,
|
||||
}))
|
||||
})()
|
||||
|
||||
setRoomCategories(finalCats)
|
||||
setPrices(buildPriceGrid(finalCats))
|
||||
|
||||
// Map API rate periods to local RatePeriod format
|
||||
// Build initial grid then apply saved overrides on top
|
||||
const grid = buildPriceGrid(finalCats)
|
||||
for (const o of apiOverrides) {
|
||||
if (!grid[o.category_id]) continue
|
||||
grid[o.category_id][o.date] = {
|
||||
price: o.price,
|
||||
extraPerson: o.extra_person,
|
||||
minNights: o.min_nights,
|
||||
channelPrices: o.channel_prices,
|
||||
closed: o.closed,
|
||||
}
|
||||
}
|
||||
setPrices(grid)
|
||||
|
||||
// Map API rate periods
|
||||
const mappedPeriods: RatePeriod[] = apiPeriods.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
startDate: p.startDate,
|
||||
endDate: p.endDate,
|
||||
notes: p.notes ?? undefined,
|
||||
categoryPrices: p.categoryPrices,
|
||||
channelMarkup: p.channelMarkup,
|
||||
extraPersonPrice: p.extraPersonPrice,
|
||||
minNights: p.minNights,
|
||||
daysOfWeek: p.daysOfWeek ?? undefined,
|
||||
id: p.id, name: p.name, startDate: p.startDate, endDate: p.endDate,
|
||||
notes: p.notes ?? undefined, categoryPrices: p.categoryPrices,
|
||||
channelMarkup: p.channelMarkup, extraPersonPrice: p.extraPersonPrice,
|
||||
minNights: p.minNights, daysOfWeek: p.daysOfWeek ?? undefined,
|
||||
}))
|
||||
setPeriods(mappedPeriods)
|
||||
}).catch(console.error).finally(() => setLoading(false))
|
||||
}).finally(() => setLoading(false))
|
||||
}, [slug])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -797,6 +797,13 @@ export function AvailabilityPage() {
|
||||
const catsToUpdate = onlyCategoryId
|
||||
? roomCategories.filter(c => c.id === onlyCategoryId)
|
||||
: roomCategories
|
||||
|
||||
const overrides: Array<{
|
||||
category_id: string; date: string; price: number
|
||||
extra_person: number; min_nights: number
|
||||
channel_prices: Record<string, number>; closed: boolean
|
||||
}> = []
|
||||
|
||||
setPrices(prev => {
|
||||
const next = { ...prev }
|
||||
for (const cat of catsToUpdate) {
|
||||
@@ -810,10 +817,26 @@ export function AvailabilityPage() {
|
||||
next[cat.id][d] = {
|
||||
price: basePrice, extraPerson, minNights, channelPrices, closed,
|
||||
}
|
||||
overrides.push({
|
||||
category_id: cat.id,
|
||||
date: d,
|
||||
price: basePrice,
|
||||
extra_person: extraPerson,
|
||||
min_nights: minNights,
|
||||
channel_prices: channelPrices,
|
||||
closed,
|
||||
})
|
||||
}
|
||||
}
|
||||
return next
|
||||
})
|
||||
|
||||
if (overrides.length > 0) {
|
||||
api.rateOverrides.bulkUpsert(slug, overrides).catch(err =>
|
||||
console.error('Failed to save rate overrides:', err)
|
||||
)
|
||||
}
|
||||
|
||||
setShowEditPanel(false)
|
||||
setSelection(null)
|
||||
if (onlyCategoryId) setSelectedCat(null)
|
||||
|
||||
Reference in New Issue
Block a user