From d661bbbc84d715ce2ea79a531acc1235615072b2 Mon Sep 17 00:00:00 2001 From: HotelSync Date: Mon, 16 Mar 2026 20:13:22 +0300 Subject: [PATCH] Add role permissions, loyalty accrual rules, guest statuses, and room service guest page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UsersPage: add "Роли и права" tab with per-module permission editor per role; create/delete custom roles - LoyaltyPage: add "Правила начисления" tab with togglable accrual rules editor; add "Ручное начисление" tab with guest search by name/phone/email, add/deduct points with reason and history; fix banner text (HotelSync is a PMS, not a hotel chain) - GuestsPage: add "Статусы гостей" tab (moved from Settings) - SettingsPage: remove Guests section; add compact calendar toggle in Appearance; toggle persists via localStorage - BookingCalendar: read compact mode default from localStorage - App.tsx: add public route /room-service/:slug → GuestRoomServicePage - GuestRoomServicePage: new public guest-facing page for ordering room service with menu, cart, payment, and order status tracking - RoomServicePage: QR/link sharing, payment mode toggles, new order notifications - RentalPage/rentalData: buffer time between bookings (bufferMinutes field) Co-Authored-By: Claude Sonnet 4.6 --- src/App.tsx | 2 + src/components/calendar/BookingCalendar.tsx | 8 +- src/data/rentalData.ts | 1 + src/pages/GuestRoomServicePage.tsx | 420 ++++++++++ src/pages/GuestsPage.tsx | 118 ++- src/pages/LoyaltyPage.tsx | 841 ++++++++++++++++---- src/pages/RentalPage.tsx | 29 + src/pages/RoomServicePage.tsx | 162 +++- src/pages/SettingsPage.tsx | 113 +-- src/pages/UsersPage.tsx | 795 +++++++++++++----- 10 files changed, 2002 insertions(+), 487 deletions(-) create mode 100644 src/pages/GuestRoomServicePage.tsx diff --git a/src/App.tsx b/src/App.tsx index 0103b37..e56561c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,6 +35,7 @@ import { LoyaltyPage } from './pages/LoyaltyPage' import { MaintenancePage } from './pages/MaintenancePage' import { DiscountsPage } from './pages/DiscountsPage' import { GuestReviewPage } from './pages/GuestReviewPage' +import { GuestRoomServicePage } from './pages/GuestRoomServicePage' export default function App() { return ( @@ -48,6 +49,7 @@ export default function App() { {/* Public */} } /> } /> + } /> {/* App routes */} }> diff --git a/src/components/calendar/BookingCalendar.tsx b/src/components/calendar/BookingCalendar.tsx index 29369f1..0c66fc7 100644 --- a/src/components/calendar/BookingCalendar.tsx +++ b/src/components/calendar/BookingCalendar.tsx @@ -74,8 +74,10 @@ export function BookingCalendar({ rooms, bookings, onBookingCreate, onBookingUpd const [dragStart, setDragStart] = useState<{ roomId: string; dayIdx: number } | null>(null) const [dragEnd, setDragEnd] = useState(null) - // Compact mode - const [compact, setCompact] = useState(false) + // Compact mode (default from Settings → Appearance) + const [compact, setCompact] = useState( + () => localStorage.getItem('calendarCompact') === 'true', + ) const rowHeight = compact ? ROW_HEIGHT_COMPACT : ROW_HEIGHT // Modals @@ -253,7 +255,7 @@ export function BookingCalendar({ rooms, bookings, onBookingCreate, onBookingUpd + )} +
+

{config.name}

+

Room Service

+
+ {step === 'menu' && cartCount > 0 && ( + + )} + + + +
+ + {/* ── MENU ── */} + {step === 'menu' && ( +
+ {/* Category tabs */} +
+ {CATEGORIES.map(cat => ( + + ))} +
+ + {/* Items */} +
+ {MENU.filter(i => i.category === category && !i.stopList).map(item => ( +
+ {item.emoji} +
+
+

{item.name}

+ {item.popular && ( + Хит + )} +
+

+ {item.time} мин +

+

{formatCurrency(item.price)}

+
+ {/* Counter */} + {(cart[item.id] ?? 0) === 0 ? ( + + ) : ( +
+ + {cart[item.id]} + +
+ )} +
+ ))} +
+ + {/* Sticky cart button */} + {cartCount > 0 && ( +
+ +
+ )} +
+ )} + + {/* ── CART ── */} + {step === 'cart' && ( +
+

Ваш заказ

+ +
+ {cartItems.map((item, i) => ( +
+ {item.emoji} +
+

{item.name}

+

{formatCurrency(item.price)} × {cart[item.id]}

+
+
+ + {cart[item.id]} + +
+ {formatCurrency(item.price * cart[item.id])} +
+ ))} + +
+
+ Сумма заказа + {formatCurrency(subtotal)} +
+
+ Обслуживание {config.serviceChargePct}% + +{formatCurrency(svcCharge)} +
+
+ Итого + {formatCurrency(total)} +
+
+
+ + {/* Room number */} +
+
+ + setRoomNumber(e.target.value)} + /> +
+
+ +