Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| from src.db.rooms import ( | |
| list_rooms, | |
| get_room_detail, | |
| change_room_status, | |
| ) | |
| from src.services.pricing_service import calculate_room_rate | |
| from src.services.auth_service import is_admin | |
| STATUS_COLORS = { | |
| "free": "#ffffff", | |
| "occupied": "#8e44ad", | |
| "need_cleaning": "#e74c3c", | |
| "out_of_service": "#95a5a6", | |
| } | |
| STATUS_TEXT_COLORS = { | |
| "free": "#000000", | |
| "occupied": "#ffffff", | |
| "need_cleaning": "#ffffff", | |
| "out_of_service": "#000000", | |
| } | |
| def render_rooms_tab( | |
| db_path: str, | |
| auth_state: gr.State, | |
| selected_room_state: gr.State, | |
| ): | |
| """ | |
| Rooms tab: | |
| - Excel-like grid by floor | |
| - Click room to view details | |
| - Status change with mandatory note | |
| - History display | |
| """ | |
| gr.Markdown("## 🧱 Rooms") | |
| with gr.Row(): | |
| grid_col = gr.Column(scale=3) | |
| detail_col = gr.Column(scale=2) | |
| # ========================================================== | |
| # ROOM GRID | |
| # ========================================================== | |
| with grid_col: | |
| rooms_md = gr.Markdown() | |
| def build_grid(): | |
| rooms = list_rooms(db_path) | |
| floors = {} | |
| for r in rooms: | |
| floor = str(r["room_no"])[0] | |
| floors.setdefault(floor, []).append(r) | |
| html = "" | |
| for floor in sorted(floors.keys()): | |
| html += f"<h4>Floor {floor}</h4>" | |
| html += "<div style='display:grid;grid-template-columns:repeat(10,1fr);gap:6px;'>" | |
| for r in floors[floor]: | |
| bg = STATUS_COLORS[r["status"]] | |
| fg = STATUS_TEXT_COLORS[r["status"]] | |
| warn = "⚠️" if r["has_problem"] else "" | |
| html += f""" | |
| <div | |
| style=" | |
| background:{bg}; | |
| color:{fg}; | |
| padding:10px; | |
| border-radius:6px; | |
| text-align:center; | |
| font-weight:600; | |
| cursor:pointer; | |
| border:1px solid #ccc; | |
| " | |
| onclick="window.gradioApp().dispatchEvent( | |
| new CustomEvent('select-room', {{ detail: '{r["room_no"]}' }}) | |
| )" | |
| > | |
| {r["room_no"]} {warn}<br/> | |
| <small>{r["status"]}</small> | |
| </div> | |
| """ | |
| html += "</div><br/>" | |
| return html | |
| rooms_md.value = build_grid() | |
| # ========================================================== | |
| # ROOM DETAIL PANEL | |
| # ========================================================== | |
| with detail_col: | |
| gr.Markdown("### Room Details") | |
| room_info = gr.Markdown("Select a room from the grid") | |
| rate_md = gr.Markdown() | |
| status_dd = gr.Dropdown( | |
| choices=["free", "occupied", "need_cleaning", "out_of_service"], | |
| label="Change status", | |
| ) | |
| note_tb = gr.Textbox(label="Mandatory note", lines=3) | |
| apply_btn = gr.Button("Apply Status Change", variant="primary") | |
| history_md = gr.Markdown() | |
| def load_room(room_no): | |
| if not room_no: | |
| return ( | |
| "Select a room from the grid", | |
| "", | |
| None, | |
| "", | |
| ) | |
| room, problems, history = get_room_detail(int(room_no), db_path) | |
| rate = calculate_room_rate( | |
| room_type=room["room_type"], | |
| has_problem=len(problems) > 0, | |
| ) | |
| info = f""" | |
| **Room:** {room["room_no"]} | |
| **Type:** {room["room_type"]} | |
| **Status:** {room["status"]} | |
| **Sellable:** {"Yes" if room["sellable"] else "No"} | |
| """ | |
| if problems: | |
| info += "\n\n**Active problems:**\n" | |
| for p in problems: | |
| info += f"- {p['problem_type']}\n" | |
| hist = "### Recent History\n" | |
| if history: | |
| for h in history: | |
| hist += f""" | |
| **{h['changed_at_utc']}** | |
| {h['from_status']} → {h['to_status']} | |
| _{h['note']}_ | |
| by **{h['changed_by']}** | |
| --- | |
| """ | |
| else: | |
| hist += "_No history_" | |
| return ( | |
| info, | |
| f"**Current Rate:** {rate if rate is not None else '—'} RO", | |
| room["status"], | |
| hist, | |
| ) | |
| # Custom JS event listener (safe, no duplicate blocks) | |
| gr.HTML( | |
| """ | |
| <script> | |
| document.addEventListener("select-room", (e) => { | |
| const room = e.detail; | |
| window.gradioApp().dispatchEvent( | |
| new CustomEvent("gradio-select-room", { detail: room }) | |
| ); | |
| }); | |
| </script> | |
| """ | |
| ) | |
| select_evt = gr.HTML(visible=False) | |
| select_evt.change( | |
| load_room, | |
| inputs=[selected_room_state], | |
| outputs=[room_info, rate_md, status_dd, history_md], | |
| ) | |
| def apply_change(auth, new_status, note): | |
| if not auth: | |
| return gr.Warning("Login required") | |
| if not note or not note.strip(): | |
| return gr.Warning("Note is required") | |
| if not selected_room_state.value: | |
| return gr.Warning("No room selected") | |
| change_room_status( | |
| room_no=int(selected_room_state.value), | |
| new_status=new_status, | |
| note=note.strip(), | |
| changed_by=auth["username"], | |
| db_path=db_path, | |
| ) | |
| rooms_md.value = build_grid() | |
| return gr.Markdown("✅ Status updated") | |
| apply_btn.click( | |
| apply_change, | |
| inputs=[auth_state, status_dd, note_tb], | |
| outputs=[history_md], | |
| ) | |