|
|
@@ -260,6 +260,7 @@
|
|
|
.panel-input:focus { border-bottom-color: var(--accent, #6366f1); }
|
|
|
.panel-input.is-sm { width: 2.5ch; }
|
|
|
.panel-input.is-door { width: 4ch; }
|
|
|
+ .panel-input.is-time { width: 9ch; }
|
|
|
.panel-value.is-grow { flex: 1; min-width: 0; }
|
|
|
.panel-value.is-grow .panel-input { width: 100%; }
|
|
|
.type-switch { display: inline-flex; align-items: baseline; gap: 2px; margin-left: 4px; font-size: 10.5px; }
|
|
|
@@ -1161,13 +1162,37 @@
|
|
|
<div class="panel-subtitle">{{ selection.nights }} night{{ selection.nights !== 1 ? 's' : '' }}</div>
|
|
|
</div>
|
|
|
<div class="panel-rows">
|
|
|
- <div class="panel-row">
|
|
|
+ <div class="panel-row" :class="{ 'is-saving': resCheckinTimeState==='saving', 'is-error': resCheckinTimeState==='error' }">
|
|
|
<span class="panel-label">Check-in</span>
|
|
|
- <span class="panel-value">{{ fmtLong(selection.checkin_date) }}</span>
|
|
|
+ <span class="panel-value">
|
|
|
+ {{ fmtDateNoYear(selection.checkin_date) }} @
|
|
|
+ <input class="panel-input is-time" type="time"
|
|
|
+ :value="editingCheckinTime ?? (selection.checkin_time || '')"
|
|
|
+ @focus="editingCheckinTime = selection.checkin_time || ''"
|
|
|
+ @input="editingCheckinTime = $event.target.value"
|
|
|
+ @blur="commitResField('checkin_time')"
|
|
|
+ @keydown.enter="$event.target.blur()"
|
|
|
+ @keydown.escape="editingCheckinTime = null; $event.target.blur()"
|
|
|
+ />
|
|
|
+ <span v-if="resCheckinTimeState==='saving'" class="row-spinner"></span>
|
|
|
+ <span v-else-if="resCheckinTimeState==='error'" class="row-err">Failed</span>
|
|
|
+ </span>
|
|
|
</div>
|
|
|
- <div class="panel-row">
|
|
|
+ <div class="panel-row" :class="{ 'is-saving': resCheckoutTimeState==='saving', 'is-error': resCheckoutTimeState==='error' }">
|
|
|
<span class="panel-label">Check-out</span>
|
|
|
- <span class="panel-value">{{ fmtLong(selection.checkout_date) }}</span>
|
|
|
+ <span class="panel-value">
|
|
|
+ {{ fmtDateNoYear(selection.checkout_date) }} @
|
|
|
+ <input class="panel-input is-time" type="time"
|
|
|
+ :value="editingCheckoutTime ?? (selection.checkout_time || '')"
|
|
|
+ @focus="editingCheckoutTime = selection.checkout_time || ''"
|
|
|
+ @input="editingCheckoutTime = $event.target.value"
|
|
|
+ @blur="commitResField('checkout_time')"
|
|
|
+ @keydown.enter="$event.target.blur()"
|
|
|
+ @keydown.escape="editingCheckoutTime = null; $event.target.blur()"
|
|
|
+ />
|
|
|
+ <span v-if="resCheckoutTimeState==='saving'" class="row-spinner"></span>
|
|
|
+ <span v-else-if="resCheckoutTimeState==='error'" class="row-err">Failed</span>
|
|
|
+ </span>
|
|
|
</div>
|
|
|
<div class="panel-row">
|
|
|
<span class="panel-label">Service</span>
|
|
|
@@ -1366,9 +1391,13 @@ createApp({
|
|
|
const editingWifiSsid = ref(null);
|
|
|
const editingWifiKey = ref(null);
|
|
|
const editingDoorCode = ref(null);
|
|
|
+ const editingCheckinTime = ref(null);
|
|
|
+ const editingCheckoutTime = ref(null);
|
|
|
const resSsidState = ref(null);
|
|
|
const resKeyState = ref(null);
|
|
|
const resDoorState = ref(null);
|
|
|
+ const resCheckinTimeState = ref(null);
|
|
|
+ const resCheckoutTimeState = ref(null);
|
|
|
const pendingDiscountType = ref(null);
|
|
|
const pendingClosed = ref(null);
|
|
|
const discountInputRef = ref(null);
|
|
|
@@ -1580,9 +1609,13 @@ createApp({
|
|
|
editingDoorCode.value = null;
|
|
|
editingWifiKey.value = null;
|
|
|
editingWifiSsid.value = null;
|
|
|
+ editingCheckinTime.value = null;
|
|
|
+ editingCheckoutTime.value = null;
|
|
|
resDoorState.value = null;
|
|
|
resKeyState.value = null;
|
|
|
resSsidState.value = null;
|
|
|
+ resCheckinTimeState.value = null;
|
|
|
+ resCheckoutTimeState.value = null;
|
|
|
if (sel?.type === 'res' && sel.chat_id && selectionSupportsChat.value) {
|
|
|
chatThread.value = null;
|
|
|
chatError.value = null;
|
|
|
@@ -1690,8 +1723,14 @@ createApp({
|
|
|
}
|
|
|
|
|
|
async function commitResField(field) {
|
|
|
- const editingMap = { wifi_ssid: editingWifiSsid, wifi_key: editingWifiKey, door_code: editingDoorCode };
|
|
|
- const stateMap = { wifi_ssid: resSsidState, wifi_key: resKeyState, door_code: resDoorState };
|
|
|
+ const editingMap = {
|
|
|
+ wifi_ssid: editingWifiSsid, wifi_key: editingWifiKey, door_code: editingDoorCode,
|
|
|
+ checkin_time: editingCheckinTime, checkout_time: editingCheckoutTime,
|
|
|
+ };
|
|
|
+ const stateMap = {
|
|
|
+ wifi_ssid: resSsidState, wifi_key: resKeyState, door_code: resDoorState,
|
|
|
+ checkin_time: resCheckinTimeState, checkout_time: resCheckoutTimeState,
|
|
|
+ };
|
|
|
const editRef = editingMap[field];
|
|
|
const stateRef = stateMap[field];
|
|
|
const newVal = editRef.value;
|
|
|
@@ -1710,12 +1749,15 @@ createApp({
|
|
|
if (!res.ok) throw new Error(await res.text());
|
|
|
const updated = await res.json();
|
|
|
if (selection.value?.uid !== targetUid) return;
|
|
|
- selection.value.wifi_key = updated.wifi_key ?? selection.value.wifi_key;
|
|
|
- selection.value.wifi_ssid = updated.wifi_ssid ?? selection.value.wifi_ssid;
|
|
|
- selection.value.door_code = updated.door_code ?? selection.value.door_code;
|
|
|
+ selection.value.wifi_key = updated.wifi_key ?? selection.value.wifi_key;
|
|
|
+ selection.value.wifi_ssid = updated.wifi_ssid ?? selection.value.wifi_ssid;
|
|
|
+ selection.value.door_code = updated.door_code ?? selection.value.door_code;
|
|
|
+ selection.value.checkin_time = updated.checkin_time ?? selection.value.checkin_time;
|
|
|
+ selection.value.checkout_time = updated.checkout_time ?? selection.value.checkout_time;
|
|
|
if (reservations.value[targetCheckin]) {
|
|
|
Object.assign(reservations.value[targetCheckin], {
|
|
|
wifi_key: updated.wifi_key, wifi_ssid: updated.wifi_ssid, door_code: updated.door_code,
|
|
|
+ checkin_time: updated.checkin_time, checkout_time: updated.checkout_time,
|
|
|
});
|
|
|
}
|
|
|
stateRef.value = null;
|
|
|
@@ -1742,6 +1784,10 @@ createApp({
|
|
|
return parseLocal(dateStr).toLocaleString('default', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
|
}
|
|
|
|
|
|
+ function fmtDateNoYear(dateStr) {
|
|
|
+ return parseLocal(dateStr).toLocaleString('default', { month: 'short', day: 'numeric' });
|
|
|
+ }
|
|
|
+
|
|
|
function fmtShort(ds) {
|
|
|
if (!ds) return '—';
|
|
|
const [y, m, d] = ds.split('-').map(Number);
|
|
|
@@ -2466,12 +2512,12 @@ createApp({
|
|
|
runHealthCheck, toggleSync, toggleBulk, fetchDiff, fixDay, fixResChanges, fixBusy, fixDayBusy, fixMonthBusy, FIX,
|
|
|
monthCats, fixMonth, fixMonthFeeds, fixMonthScripts, reloadCaches,
|
|
|
toggleClosed, commitPrice, switchDiscountType, commitDiscount, commitResField,
|
|
|
- editingDoorCode, editingWifiKey, editingWifiSsid,
|
|
|
- resDoorState, resKeyState, resSsidState,
|
|
|
+ editingDoorCode, editingWifiKey, editingWifiSsid, editingCheckinTime, editingCheckoutTime,
|
|
|
+ resDoorState, resKeyState, resSsidState, resCheckinTimeState, resCheckoutTimeState,
|
|
|
monthName, monthStatsLabel,
|
|
|
todayKey, inMonth, isClosed,
|
|
|
priceFor, discountFor, priceDataFor, dayServiceKeys, dayStatus,
|
|
|
- fmtDate, fmtLong, fmtShort, fmtDiscount, fmtField, capitalize, serviceName,
|
|
|
+ fmtDate, fmtLong, fmtDateNoYear, fmtShort, fmtDiscount, fmtField, capitalize, serviceName,
|
|
|
selectDay, selectReservation, fmtBulkDates,
|
|
|
reservationSpans, closureSpans,
|
|
|
prevMonth, nextMonth,
|