";
}, [computedLines, weekdayNights, weekendNights, holidayNightsList, kids6to12, childrenCost, under5, guestName, checkIn, checkOutDate, nightsNum, requestedGuests, allocatedGuests, slab5Subtotal, slab18Subtotal, discountAmount, gst5, gst18, grandTotal]);
const openPrintable = () => {
try {
const html = buildPrintableHTML();
const blob = new Blob([html], {
type: "text/html"
});
const url = URL.createObjectURL(blob);
setPrintUrl(url);
window.open(url, "_blank");
} catch (e) {}
};
return /*#__PURE__*/h("div", {
style: {
fontFamily: SANS,
background: C.creamSoft,
color: C.ink,
minHeight: "100%"
},
className: "w-full"
}, /*#__PURE__*/h("style", null, `
input[type="date"]::-webkit-calendar-picker-indicator { opacity: 0.6; }
.qb-select, .qb-input { font-family: ${SANS}; background: #fff; border: 1px solid #DCD4C0; border-radius: 8px; padding: 7px 10px; font-size: 14px; color: ${C.ink}; outline: none; }
.qb-select:focus, .qb-input:focus { border-color: ${C.forest}; box-shadow: 0 0 0 3px ${C.goldLight}55; }
.qb-btn-ghost:hover { background: ${C.cream}; }
`), /*#__PURE__*/h("div", {
style: {
background: C.forestDark,
color: C.creamSoft
},
className: "px-5 py-5 sm:px-8"
}, /*#__PURE__*/h("div", {
className: "max-w-3xl mx-auto flex items-center justify-between"
}, /*#__PURE__*/h("div", {
className: "flex items-center gap-3"
}, /*#__PURE__*/h("img", {
src: LOGO_SRC,
alt: "Mihra Mist Resort",
className: "w-11 h-11 rounded-full object-cover",
style: {
border: `1px solid ${C.gold}`
}
}), /*#__PURE__*/h("div", null, /*#__PURE__*/h("div", {
style: {
fontFamily: SERIF,
letterSpacing: 0.5
},
className: "text-2xl sm:text-3xl font-semibold"
}, "Mihra Mist Resort"), /*#__PURE__*/h("div", {
style: {
color: C.goldLight
},
className: "text-xs sm:text-sm tracking-wide uppercase mt-0.5"
}, "Guest Quote Builder"))), /*#__PURE__*/h("div", {
style: {
background: C.forest,
borderColor: C.gold
},
className: "hidden sm:block text-right border rounded-lg px-3 py-2 text-xs"
}, /*#__PURE__*/h("div", {
style: {
color: C.goldLight
}
}, "Rooms accommodate"), /*#__PURE__*/h("div", {
className: "text-lg font-semibold"
}, allocatedGuests)))), /*#__PURE__*/h("div", {
className: "max-w-3xl mx-auto px-4 sm:px-8 pt-4"
}, /*#__PURE__*/h("div", {
className: "flex gap-2"
}, /*#__PURE__*/h("button", {
onClick: () => setActiveTab("quote"),
className: "text-sm font-medium px-4 py-2 rounded-lg transition",
style: activeTab === "quote" ? {
background: C.forest,
color: "#fff"
} : {
background: "#fff",
color: C.forestDark,
border: `1px solid ${C.cream}`
}
}, "Quote Builder"), /*#__PURE__*/h("button", {
onClick: () => setActiveTab("availability"),
className: "text-sm font-medium px-4 py-2 rounded-lg transition",
style: activeTab === "availability" ? {
background: C.forest,
color: "#fff"
} : {
background: "#fff",
color: C.forestDark,
border: `1px solid ${C.cream}`
}
}, "Availability & Bookings"))), activeTab === "quote" && /*#__PURE__*/h("div", {
className: "max-w-3xl mx-auto px-4 sm:px-8 py-6 space-y-5"
}, /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold mb-3"
}, "Stay details"), /*#__PURE__*/h("div", {
className: "grid grid-cols-1 sm:grid-cols-4 gap-3"
}, /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Guest name (optional)", /*#__PURE__*/h("input", {
className: "qb-input",
value: guestName,
onChange: e => setGuestName(e.target.value),
placeholder: "e.g. Anitha Rao"
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Number of guests", /*#__PURE__*/h("input", {
type: "number",
min: 0,
className: "qb-input",
value: guestCount,
onChange: e => setGuestCount(e.target.value),
placeholder: "As requested"
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Check-in date", /*#__PURE__*/h("input", {
type: "date",
className: "qb-input",
value: checkIn,
onChange: e => setCheckIn(e.target.value)
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Number of nights", /*#__PURE__*/h("input", {
type: "number",
min: 1,
className: "qb-input",
value: nights,
onChange: e => setNights(e.target.value)
}))), /*#__PURE__*/h("div", {
className: "mt-3 text-sm text-stone-600 flex flex-wrap gap-x-4 gap-y-1"
}, /*#__PURE__*/h("span", null, "Check-out: ", /*#__PURE__*/h("strong", {
style: {
color: C.forestDark
}
}, fmtDate(checkOutDate))), /*#__PURE__*/h("span", null, weekdayNights, " weekday night", weekdayNights !== 1 ? "s" : "", " (Mon\u2013Thu)"), /*#__PURE__*/h("span", null, weekendNights, " weekend night", weekendNights !== 1 ? "s" : "", " (Fri\u2013Sun)"), holidayNightsList.length > 0 && /*#__PURE__*/h("span", {
style: {
color: C.forest
},
className: "font-medium"
}, "incl. ", holidayNightsList.length, " holiday night", holidayNightsList.length !== 1 ? "s" : "", " @ weekend rate")), /*#__PURE__*/h("div", {
className: "mt-3 border-t pt-3",
style: {
borderColor: C.cream
}
}, /*#__PURE__*/h("div", {
className: "text-xs font-medium text-stone-600 mb-1.5"
}, "Nights in this stay \u2014 tick any national holiday / festival (Onam, Deepavali, Gandhi Jayanti, etc.) to bill it at weekend rate"), /*#__PURE__*/h("div", {
className: "flex flex-wrap gap-1.5"
}, nightDates.map(n => /*#__PURE__*/h("label", {
key: n.iso,
className: "flex items-center gap-1.5 text-xs px-2 py-1 rounded-md cursor-pointer",
style: {
background: n.isWeekend ? C.goldLight : C.creamSoft,
border: `1px solid ${n.isWeekend ? C.gold : C.cream}`,
opacity: n.dayIsWeekend ? 0.85 : 1
},
title: n.dayIsWeekend ? "Already a weekend night" : "Tick if this date is a national holiday / festival"
}, !n.dayIsWeekend && /*#__PURE__*/h("input", {
type: "checkbox",
checked: !!n.isHoliday,
onChange: () => toggleHoliday(n.iso),
className: "cursor-pointer"
}), /*#__PURE__*/h("span", null, fmtDayName(n.date), " ", fmtDateShort(n.date)), n.isHoliday && /*#__PURE__*/h("span", {
style: {
color: C.forestDark
},
className: "font-semibold"
}, "Holiday"))))), guestMismatch && /*#__PURE__*/h("div", {
className: "mt-2 flex items-start gap-1.5 text-xs rounded-lg px-2.5 py-2",
style: {
background: "#FBEFDD",
color: "#7A5A1E"
}
}, /*#__PURE__*/h(AlertTriangle, {
size: 14,
className: "mt-0.5 shrink-0"
}), /*#__PURE__*/h("span", null, requestedGuests, " guest", requestedGuests !== 1 ? "s" : "", " requested, but the rooms selected below accommodate ", allocatedGuests, ". Adjust room count or extra adults to match."))), /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("div", {
className: "flex items-center justify-between mb-3"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold"
}, "Rooms"), /*#__PURE__*/h("button", {
onClick: addLine,
style: {
background: C.forest,
color: "#fff"
},
className: "flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg hover:opacity-90 transition"
}, /*#__PURE__*/h(Plus, {
size: 15
}), " Add room")), /*#__PURE__*/h("div", {
className: "space-y-3"
}, computedLines.map(line => /*#__PURE__*/h("div", {
key: line.id,
style: {
borderColor: C.cream,
background: C.creamSoft
},
className: "border rounded-lg p-3"
}, /*#__PURE__*/h("div", {
className: "flex flex-col sm:flex-row sm:items-end gap-2.5"
}, /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600 flex-1 min-w-0"
}, "Room category", /*#__PURE__*/h("select", {
className: "qb-select",
value: line.categoryId,
onChange: e => updateLine(line.id, {
categoryId: e.target.value
})
}, ROOM_CATEGORIES.map(c => /*#__PURE__*/h("option", {
key: c.id,
value: c.id
}, c.name)))), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600 w-full sm:w-24"
}, "Rooms", /*#__PURE__*/h("input", {
type: "number",
min: 1,
className: "qb-input",
value: line.qty,
onChange: e => updateLine(line.id, {
qty: e.target.value
})
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600 w-full sm:w-36"
}, "Extra adults (max ", line.qty, ")", line.cat ? ` \u2014 base ${line.cat.base}/room` : "", /*#__PURE__*/h("input", {
type: "number",
min: 0,
max: line.qty,
className: "qb-input",
value: line.extra,
onChange: e => updateLine(line.id, {
extra: e.target.value
})
})), /*#__PURE__*/h("button", {
onClick: () => removeLine(line.id),
disabled: lines.length === 1,
className: "qb-btn-ghost flex items-center justify-center rounded-lg p-2 text-stone-400 hover:text-red-500 disabled:opacity-30 disabled:hover:text-stone-400 transition",
title: "Remove room line"
}, /*#__PURE__*/h(Trash2, {
size: 16
}))), line.cat && /*#__PURE__*/h("div", {
className: "mt-2 text-xs text-stone-500 space-y-0.5"
}, /*#__PURE__*/h("div", {
className: "flex flex-wrap gap-x-3"
}, weekdayNights > 0 && /*#__PURE__*/h("span", null, weekdayNights, " weekday night(s) @ ", inr(line.cat.weekday), " ", /*#__PURE__*/h("em", {
className: "not-italic",
style: {
color: C.forest
}
}, "(GST ", (line.weekdaySlab * 100).toFixed(0), "%)")), weekendNights > 0 && /*#__PURE__*/h("span", null, weekendNights, " weekend night(s) @ ", inr(line.cat.weekend), " ", /*#__PURE__*/h("em", {
className: "not-italic",
style: {
color: C.forest
}
}, "(GST ", (line.weekendSlab * 100).toFixed(0), "%)")), line.extra > 0 && /*#__PURE__*/h("span", null, "+", inr(line.cat.extra), "/night \u00d7 ", line.extra, " extra adult(s)")), /*#__PURE__*/h("div", {
className: "flex"
}, /*#__PURE__*/h("span", {
className: "ml-auto font-medium",
style: {
color: C.forestDark
}
}, "Line total: ", inr(line.lineRevenue))), lineAvailability[line.id] && line.qty > lineAvailability[line.id].minAvailable && /*#__PURE__*/h("div", {
className: "flex items-start gap-1.5 mt-1.5 rounded-md px-2 py-1.5",
style: {
background: "#FBD9D3",
color: "#8a3b2f"
}
}, /*#__PURE__*/h(AlertTriangle, {
size: 13,
className: "mt-0.5 shrink-0"
}), /*#__PURE__*/h("span", null, "Only ", Math.max(0, lineAvailability[line.id].minAvailable), " of ", line.qty, " ", line.cat.name, " available for these dates (of ", lineAvailability[line.id].total, " total) \u2014 already booked on ", lineAvailability[line.id].conflicts.slice(0, 3).map(c => fmtDateShort(addDays(c.iso, 0))).join(", "), lineAvailability[line.id].conflicts.length > 3 ? " and more" : "", ". Check Availability & Bookings before confirming."))))))), /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold mb-3"
}, "Children & adjustments"), /*#__PURE__*/h("div", {
className: "grid grid-cols-2 sm:grid-cols-4 gap-3"
}, /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Children 0\u20135 (free)", /*#__PURE__*/h("input", {
type: "number",
min: 0,
className: "qb-input",
value: under5,
onChange: e => setUnder5(e.target.value)
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Children 6\u201312 (\u20b9500/night)", /*#__PURE__*/h("input", {
type: "number",
min: 0,
className: "qb-input",
value: kids6to12,
onChange: e => setKids6to12(e.target.value)
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Discount type", /*#__PURE__*/h("select", {
className: "qb-select",
value: discountType,
onChange: e => setDiscountType(e.target.value)
}, /*#__PURE__*/h("option", {
value: "percent"
}, "Percent (%)"), /*#__PURE__*/h("option", {
value: "flat"
}, "Flat (\u20b9)"))), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Discount value", /*#__PURE__*/h("input", {
type: "number",
min: 0,
className: "qb-input",
value: discountValue,
onChange: e => setDiscountValue(e.target.value)
}))), /*#__PURE__*/h("p", {
className: "text-xs text-stone-500 mt-2"
}, "Above 12 years considered adult \u2014 add as an extra adult on the relevant room instead."), /*#__PURE__*/h("p", {
className: "text-xs text-stone-500 mt-1"
}, "GST is applied automatically per government slab: 5% where a night's room tariff is \u20b97,500 or below, 18% where it exceeds \u20b97,500.")), /*#__PURE__*/h("section", {
style: {
background: C.forestDark,
color: C.creamSoft
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF
},
className: "text-lg font-semibold mb-3"
}, "Quote summary"), /*#__PURE__*/h("div", {
className: "space-y-1.5 text-sm"
}, /*#__PURE__*/h(Row, {
label: "Rent \u2014 5% GST slab (tariff \u2264 \u20b97,500)",
value: inr(slab5Subtotal)
}), /*#__PURE__*/h(Row, {
label: "Rent \u2014 18% GST slab (tariff > \u20b97,500)",
value: inr(slab18Subtotal)
}), childrenCost > 0 && /*#__PURE__*/h(Row, {
label: `Children (6\u201312) \u00d7 ${kids6to12} (included above)`,
value: inr(childrenCost),
muted: true
}), /*#__PURE__*/h(Row, {
label: "Subtotal",
value: inr(subtotalBeforeDiscount)
}), discountAmount > 0 && /*#__PURE__*/h(Row, {
label: "Discount",
value: `\u2212 ${inr(discountAmount)}`,
muted: true
}), /*#__PURE__*/h(Row, {
label: "GST @ 5%",
value: inr(gst5)
}), /*#__PURE__*/h(Row, {
label: "GST @ 18%",
value: inr(gst18)
}), /*#__PURE__*/h("div", {
style: {
borderColor: C.forest
},
className: "border-t my-2"
}), /*#__PURE__*/h("div", {
className: "flex items-baseline justify-between"
}, /*#__PURE__*/h("span", {
style: {
fontFamily: SERIF
},
className: "text-lg"
}, "Total"), /*#__PURE__*/h("span", {
style: {
fontFamily: SERIF,
color: C.goldLight
},
className: "text-2xl font-semibold"
}, inr(grandTotal)))), /*#__PURE__*/h("div", {
className: "flex flex-wrap gap-2 mt-4"
}, /*#__PURE__*/h("button", {
onClick: copyQuote,
style: {
background: C.gold,
color: C.ink
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition"
}, copyState === "copied" ? /*#__PURE__*/h(Check, {
size: 15
}) : /*#__PURE__*/h(Copy, {
size: 15
}), copyState === "copied" ? "Copied" : "Copy quote text"), /*#__PURE__*/h("a", {
href: whatsappHref,
target: "_blank",
rel: "noreferrer",
style: {
background: "#25D366",
color: "#fff"
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition"
}, /*#__PURE__*/h(MessageCircle, {
size: 15
}), " Share on WhatsApp"), /*#__PURE__*/h("button", {
onClick: openPrintable,
style: {
background: C.goldLight,
color: C.ink
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition"
}, /*#__PURE__*/h(Printer, {
size: 15
}), " Print / Save PDF"), /*#__PURE__*/h("button", {
onClick: saveQuote,
disabled: saving,
style: {
background: C.forest,
color: "#fff"
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition disabled:opacity-60"
}, saving ? /*#__PURE__*/h(Loader2, {
size: 15,
className: "animate-spin"
}) : /*#__PURE__*/h(Save, {
size: 15
}), "Save quote"), /*#__PURE__*/h("button", {
onClick: confirmQuoteAsBooking,
disabled: confirming,
style: {
background: "#8a3b2f",
color: "#fff"
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition disabled:opacity-60"
}, confirming ? /*#__PURE__*/h(Loader2, {
size: 15,
className: "animate-spin"
}) : /*#__PURE__*/h(Check, {
size: 15
}), "Confirm booking & block dates"), saveMsg && /*#__PURE__*/h("span", {
className: "text-xs self-center",
style: {
color: C.goldLight
}
}, saveMsg)), confirmMsg && /*#__PURE__*/h("p", {
className: "text-xs mt-2",
style: {
color: C.goldLight
}
}, confirmMsg), printUrl && /*#__PURE__*/h("p", {
className: "text-xs mt-2",
style: {
color: C.goldLight
}
}, "If a new tab didn't open (pop-up blocked), ", /*#__PURE__*/h("a", {
href: printUrl,
target: "_blank",
rel: "noreferrer",
className: "underline"
}, "tap here to open the printable quote"), "."), copyState === "manual" && /*#__PURE__*/h("p", {
className: "text-xs mt-2",
style: {
color: C.goldLight
}
}, "Clipboard access is blocked here \u2014 the text below is selected, so press Ctrl/Cmd+C to copy it."), /*#__PURE__*/h("textarea", {
ref: textAreaRef,
readOnly: true,
value: shareText,
style: {
display: copyState === "manual" ? "block" : "none",
background: "#fff",
color: C.ink
},
className: "w-full mt-2 text-xs rounded-lg p-2 h-40 font-mono"
})), /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl shadow-sm overflow-hidden"
}, /*#__PURE__*/h("button", {
onClick: () => setSavedOpen(v => !v),
className: "w-full flex items-center justify-between px-4 sm:px-5 py-3.5"
}, /*#__PURE__*/h("span", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold"
}, "Saved quotes"), savedOpen ? /*#__PURE__*/h(ChevronUp, {
size: 18,
color: C.forest
}) : /*#__PURE__*/h(ChevronDown, {
size: 18,
color: C.forest
})), savedOpen && /*#__PURE__*/h("div", {
className: "px-4 sm:px-5 pb-4"
}, loadingSaved && /*#__PURE__*/h("div", {
className: "text-sm text-stone-500 flex items-center gap-2"
}, /*#__PURE__*/h(Loader2, {
size: 14,
className: "animate-spin"
}), " Loading\u2026"), !loadingSaved && savedQuotes.length === 0 && /*#__PURE__*/h("div", {
className: "text-sm text-stone-500"
}, "No saved quotes yet. Build a quote above and tap \"Save quote\"."), /*#__PURE__*/h("div", {
className: "space-y-2"
}, savedQuotes.map(q => /*#__PURE__*/h("div", {
key: q.id,
style: {
borderColor: C.cream
},
className: "border rounded-lg p-3 flex items-center justify-between gap-3"
}, /*#__PURE__*/h("div", {
className: "min-w-0"
}, /*#__PURE__*/h("div", {
className: "text-sm font-medium truncate",
style: {
color: C.forestDark
}
}, q.guestName && q.guestName.trim() ? q.guestName.trim() : "Unnamed guest"), /*#__PURE__*/h("div", {
className: "text-xs text-stone-500"
}, q.checkIn, " \u00b7 ", q.nights, " night", q.nights > 1 ? "s" : "", " \u00b7 ", inr(q.grandTotal || 0))), /*#__PURE__*/h("div", {
className: "flex items-center gap-2 shrink-0"
}, /*#__PURE__*/h("button", {
onClick: () => loadQuote(q),
className: "text-xs px-2.5 py-1.5 rounded-md",
style: {
background: C.cream,
color: C.forestDark
}
}, "Load"), /*#__PURE__*/h("button", {
onClick: () => deleteQuote(q.id),
className: "text-stone-400 hover:text-red-500 p-1.5",
title: "Delete"
}, /*#__PURE__*/h(X, {
size: 15
})))))))), /*#__PURE__*/h("p", {
className: "text-xs text-center text-stone-400 pb-2"
}, "Rates on CP basis \u00b7 MAP/AP on request \u00b7 Check-in 2:00 PM \u00b7 Check-out 11:00 AM \u00b7 Rates subject to revision")), activeTab === "availability" && /*#__PURE__*/h("div", {
className: "max-w-3xl mx-auto px-4 sm:px-8 py-6 space-y-5"
}, /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold mb-1"
}, "Room inventory"), /*#__PURE__*/h("p", {
className: "text-xs text-stone-500 mb-3"
}, "Total physical rooms per category \u2014 update any time. This is what availability checks and the calendar below are based on."), /*#__PURE__*/h("div", {
className: "grid grid-cols-2 sm:grid-cols-3 gap-3"
}, ROOM_CATEGORIES.map(c => /*#__PURE__*/h("label", {
key: c.id,
className: "flex flex-col gap-1 text-xs text-stone-600"
}, c.name, /*#__PURE__*/h("input", {
type: "number",
min: 0,
className: "qb-input",
value: inventory[c.id] ?? 0,
onChange: e => setInventoryValue(c.id, e.target.value)
}))))), /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold mb-3"
}, "Add a confirmed booking"), /*#__PURE__*/h("div", {
className: "grid grid-cols-1 sm:grid-cols-5 gap-3"
}, /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600 sm:col-span-2"
}, "Guest name", /*#__PURE__*/h("input", {
className: "qb-input",
value: newBooking.guestName,
onChange: e => setNewBooking({
...newBooking,
guestName: e.target.value
}),
placeholder: "e.g. Anitha Rao"
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600 sm:col-span-2"
}, "Room category", /*#__PURE__*/h("select", {
className: "qb-select",
value: newBooking.categoryId,
onChange: e => setNewBooking({
...newBooking,
categoryId: e.target.value
})
}, ROOM_CATEGORIES.map(c => /*#__PURE__*/h("option", {
key: c.id,
value: c.id
}, c.name)))), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Rooms", /*#__PURE__*/h("input", {
type: "number",
min: 1,
className: "qb-input",
value: newBooking.qty,
onChange: e => setNewBooking({
...newBooking,
qty: e.target.value
})
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Check-in", /*#__PURE__*/h("input", {
type: "date",
className: "qb-input",
value: newBooking.checkIn,
onChange: e => setNewBooking({
...newBooking,
checkIn: e.target.value
})
})), /*#__PURE__*/h("label", {
className: "flex flex-col gap-1 text-xs text-stone-600"
}, "Nights", /*#__PURE__*/h("input", {
type: "number",
min: 1,
className: "qb-input",
value: newBooking.nights,
onChange: e => setNewBooking({
...newBooking,
nights: e.target.value
})
}))), /*#__PURE__*/h("button", {
onClick: addBookingFromForm,
style: {
background: C.forest,
color: "#fff"
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition mt-3"
}, /*#__PURE__*/h(Plus, {
size: 15
}), " Add booking (blocks these dates)")), /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold mb-3"
}, "Current bookings (", bookings.length, ")"), !bookingsLoaded && /*#__PURE__*/h("div", {
className: "text-sm text-stone-500 flex items-center gap-2"
}, /*#__PURE__*/h(Loader2, {
size: 14,
className: "animate-spin"
}), " Loading\u2026"), bookingsLoaded && bookings.length === 0 && /*#__PURE__*/h("div", {
className: "text-sm text-stone-500"
}, "No bookings yet. Add one above, or confirm a quote from the Quote Builder tab."), /*#__PURE__*/h("div", {
className: "space-y-2"
}, bookings.map(b => {
const cat = catById(b.categoryId);
const bCheckIn = new Date(b.checkIn + "T00:00:00");
const bCheckOut = addDays(b.checkIn, b.nights);
return /*#__PURE__*/h("div", {
key: b.id,
style: {
borderColor: C.cream
},
className: "border rounded-lg p-3 flex items-center justify-between gap-3"
}, /*#__PURE__*/h("div", {
className: "min-w-0"
}, /*#__PURE__*/h("div", {
className: "text-sm font-medium truncate",
style: {
color: C.forestDark
}
}, b.guestName && b.guestName.trim() ? b.guestName.trim() : "Unnamed guest", " \u2014 ", cat ? cat.name : b.categoryId), /*#__PURE__*/h("div", {
className: "text-xs text-stone-500"
}, fmtDate(bCheckIn), " \u2192 ", fmtDate(bCheckOut), " \u00b7 ", b.qty, " room", b.qty > 1 ? "s" : "")), /*#__PURE__*/h("button", {
onClick: () => deleteBooking(b.id),
className: "text-stone-400 hover:text-red-500 p-1.5 shrink-0",
title: "Delete / release booking"
}, /*#__PURE__*/h(Trash2, {
size: 16
})));
}))), /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold mb-1"
}, CAL_DAYS, "-day availability calendar"), /*#__PURE__*/h("label", {
className: "flex items-center gap-2 text-xs text-stone-600 mb-3 mt-2"
}, "Start date", /*#__PURE__*/h("input", {
type: "date",
className: "qb-input",
value: calStart,
onChange: e => setCalStart(e.target.value)
})), /*#__PURE__*/h("div", {
style: {
overflowX: "auto"
}
}, /*#__PURE__*/h("table", {
style: {
borderCollapse: "collapse",
fontSize: "11px",
width: "100%"
}
}, /*#__PURE__*/h("thead", null, /*#__PURE__*/h("tr", null, /*#__PURE__*/h("th", {
style: {
padding: "4px 8px",
textAlign: "left",
color: C.forestDark,
position: "sticky",
left: 0,
background: "#fff"
}
}, "Category"), Array.from({
length: CAL_DAYS
}).map((_, i) => {
const d = addDays(calStart, i);
return /*#__PURE__*/h("th", {
key: i,
style: {
padding: "4px 5px",
textAlign: "center",
color: C.forestDark,
whiteSpace: "nowrap",
fontWeight: 500
}
}, fmtDayName(d), /*#__PURE__*/h("br", null), fmtDateShort(d));
}))), /*#__PURE__*/h("tbody", null, ROOM_CATEGORIES.map(cat => /*#__PURE__*/h("tr", {
key: cat.id
}, /*#__PURE__*/h("td", {
style: {
padding: "4px 8px",
fontWeight: 500,
whiteSpace: "nowrap",
color: C.ink,
position: "sticky",
left: 0,
background: "#fff"
}
}, cat.name), Array.from({
length: CAL_DAYS
}).map((_, i) => {
const d = addDays(calStart, i);
const iso = isoOf(d);
const total = inventory[cat.id] || 0;
const booked = bookedQtyOnDate(cat.id, iso, bookings);
const avail = total - booked;
const bg = avail <= 0 ? "#FBD9D3" : avail < total ? "#FBEFDD" : "#E7F0E3";
const fg = avail <= 0 ? "#8a3b2f" : avail < total ? "#7A5A1E" : "#3D4F35";
return /*#__PURE__*/h("td", {
key: i,
style: {
padding: "4px 5px",
textAlign: "center",
background: bg,
color: fg,
fontWeight: 600
}
}, avail, "/", total);
})))))), /*#__PURE__*/h("div", {
className: "flex flex-wrap gap-4 mt-3 text-xs text-stone-500"
}, /*#__PURE__*/h("span", null, /*#__PURE__*/h("span", {
style: {
display: "inline-block",
width: 10,
height: 10,
background: "#E7F0E3",
marginRight: 4,
borderRadius: 2
}
}), "Fully available"), /*#__PURE__*/h("span", null, /*#__PURE__*/h("span", {
style: {
display: "inline-block",
width: 10,
height: 10,
background: "#FBEFDD",
marginRight: 4,
borderRadius: 2
}
}), "Partially booked"), /*#__PURE__*/h("span", null, /*#__PURE__*/h("span", {
style: {
display: "inline-block",
width: 10,
height: 10,
background: "#FBD9D3",
marginRight: 4,
borderRadius: 2
}
}), "Fully booked"))), /*#__PURE__*/h("section", {
style: {
background: "#fff",
border: `1px solid ${C.cream}`
},
className: "rounded-xl p-4 sm:p-5 shadow-sm"
}, /*#__PURE__*/h("h2", {
style: {
fontFamily: SERIF,
color: C.forestDark
},
className: "text-lg font-semibold mb-1"
}, "Backup & restore"), /*#__PURE__*/h("p", {
className: "text-xs text-stone-500 mb-3"
}, "Inventory and bookings live only in this browser, on this computer. Export a backup regularly and keep the file safe \u2014 you can restore it here, or on another computer, at any time."), /*#__PURE__*/h("div", {
className: "flex flex-wrap items-center gap-2"
}, /*#__PURE__*/h("button", {
onClick: exportBackup,
disabled: exporting,
style: {
background: C.forest,
color: "#fff"
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition disabled:opacity-60"
}, exporting ? /*#__PURE__*/h(Loader2, {
size: 15,
className: "animate-spin"
}) : /*#__PURE__*/h(Save, {
size: 15
}), "Export backup (.json)"), /*#__PURE__*/h("button", {
onClick: () => fileInputRef.current && fileInputRef.current.click(),
style: {
background: C.goldLight,
color: C.ink
},
className: "flex items-center gap-1.5 text-sm font-medium px-3 py-2 rounded-lg hover:opacity-90 transition"
}, "Restore from backup"), /*#__PURE__*/h("input", {
ref: fileInputRef,
type: "file",
accept: "application/json",
onChange: importBackup,
style: {
display: "none"
}
})), importMsg && /*#__PURE__*/h("p", {
className: "text-xs mt-2",
style: {
color: C.forest
}
}, importMsg))));
}
function Row({
label,
value,
muted
}) {
return /*#__PURE__*/h("div", {
className: "flex items-baseline justify-between"
}, /*#__PURE__*/h("span", {
style: {
opacity: muted ? 0.7 : 0.9,
fontSize: muted ? "0.8em" : "1em"
}
}, label), /*#__PURE__*/h("span", {
style: {
opacity: muted ? 0.7 : 1,
fontSize: muted ? "0.8em" : "1em"
}
}, value));
}
mountApp(QuoteBuilder);