| import gradio as gr |
| import os |
| import pandas as pd |
| import requests |
| from supabase import create_client, Client |
| from datetime import datetime, timedelta, timezone |
|
|
| |
| TAIPEI_TZ = timezone(timedelta(hours=8)) |
|
|
| SUPABASE_URL = os.getenv("SUPABASE_URL") |
| SUPABASE_KEY = os.getenv("SUPABASE_KEY") |
| GAS_MAIL_URL = os.getenv("GAS_MAIL_URL") |
| LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN") |
|
|
| |
| PUBLIC_SPACE_URL = os.getenv("HF_SPACE_URL", "https://ciecietaipei.github.io/booking.html") |
|
|
| REAL_ADMIN_USER = os.getenv("ADMIN_USER") or "Deep Learning 101" |
| REAL_ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") or "2016-11-11" |
|
|
| supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) |
|
|
| |
| |
| |
| def check_url_action(request: gr.Request): |
| """當客人點擊信件裡的連結來到此頁面時,攔截 action 參數並處理,隱藏登入畫面""" |
| if not request: return gr.update(visible=False), gr.update(visible=True) |
| |
| action = request.query_params.get('action') |
| bid = request.query_params.get('id') |
| |
| if action == 'confirm' and bid: |
| try: |
| supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute() |
| msg = f"<div style='text-align:center; padding:50px; color:#2ecc71;'><h1>✅ 訂位已成功確認!</h1><p>感謝您的回覆,期待您的光臨。您現在可以關閉此視窗。</p></div>" |
| return gr.update(value=msg, visible=True), gr.update(visible=False) |
| except Exception as e: |
| return gr.update(value=f"❌ 處理失敗: {str(e)}", visible=True), gr.update(visible=False) |
| |
| elif action == 'cancel' and bid: |
| try: |
| supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute() |
| msg = f"<div style='text-align:center; padding:50px; color:#e74c3c;'><h1>🚫 訂位已取消。</h1><p>期待下次為您服務。您現在可以關閉此視窗。</p></div>" |
| return gr.update(value=msg, visible=True), gr.update(visible=False) |
| except Exception as e: |
| return gr.update(value=f"❌ 處理失敗: {str(e)}", visible=True), gr.update(visible=False) |
| |
| |
| return gr.update(visible=False), gr.update(visible=True) |
|
|
|
|
| |
| |
| |
|
|
| def get_bookings(): |
| try: |
| res = supabase.table("bookings").select("*").order("created_at", desc=True).execute() |
| if not res.data: return pd.DataFrame() |
| return pd.DataFrame(res.data) |
| except Exception as e: |
| print("Fetch bookings error:", e) |
| return pd.DataFrame() |
|
|
| def send_confirmation_hybrid(booking_id): |
| if not booking_id: return "❌ 請輸入訂單 ID" |
| try: |
| res = supabase.table("bookings").select("*").eq("id", booking_id).execute() |
| if not res.data: return f"❌ 找不到 ID: {booking_id}" |
| booking = res.data[0] |
| |
| email = booking.get('email') |
| user_id = booking.get('user_id') |
| current_status = booking.get('status', '') |
| |
| confirm_link = f"{PUBLIC_SPACE_URL}?id={booking_id}&action=confirm" |
| cancel_link = f"{PUBLIC_SPACE_URL}?id={booking_id}&action=cancel" |
| log_msg = f"🆔 {booking_id} 處理結果: " |
| is_reminder = "確認" in current_status |
|
|
| if is_reminder: |
| |
| mail_subject = f"🔔 行前提醒: {booking['date']} - Cié Cié Taipei" |
| line_text = (f"🔔 行前提醒\n\n{booking['name']} 您好,期待今晚與您相見!\n\n" |
| f"📅 日期:{booking['date']}\n⏰ 時間:{booking['time']}\n👥 人數:{booking['pax']} 位\n\n" |
| f"座位已為您準備好,若需變更請聯繫我們。") |
| mail_html = f"""<div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif;"> |
| <h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2> |
| <p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong> 您好,行前提醒:</p> |
| <ul style="color:#ddd; line-height:1.8;"><li>📅 日期:{booking['date']}</li><li>⏰ 時間:{booking['time']}</li><li>👥 人數:{booking['pax']} 位</li></ul> |
| <div style="text-align:center; margin-top:30px;"><span style="color:#888;">無需再次確認。</span><br><br><a href="{cancel_link}" style="color:#aaa; font-size:12px;">若無法前來,請點此取消</a></div></div>""" |
| else: |
| |
| mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei" |
| line_text = (f"✅ 訂位確認\n\n{booking['name']} 您好,已收到您的預約。\n\n" |
| f"📅 日期:{booking['date']}\n⏰ 時間:{booking['time']}\n👥 人數:{booking['pax']} 位\n\n" |
| f"👉 請務必點擊下方連結「確認出席」,謝謝!\n{confirm_link}") |
| mail_html = f"""<div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif;"> |
| <h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2> |
| <p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong> 您好,請確認您的訂位:</p> |
| <ul style="color:#ddd; line-height:1.8;"><li>📅 日期:{booking['date']}</li><li>⏰ 時間:{booking['time']}</li><li>👥 人數:{booking['pax']} 位</li></ul> |
| <div style="text-align:center; margin-top:30px;"><a href="{confirm_link}" style="display:inline-block; background:#d4af37; color:#000; padding:12px 30px; text-decoration:none; border-radius:50px; font-weight:bold; margin-right:10px;">✅ 確認出席</a> |
| <a href="{cancel_link}" style="display:inline-block; border:1px solid #555; color:#aaa; padding:11px 29px; text-decoration:none; border-radius:50px;">🚫 取消</a></div></div>""" |
|
|
| |
| if email and "@" in email and GAS_MAIL_URL: |
| try: |
| requests.post(GAS_MAIL_URL, json={"to": email, "subject": mail_subject, "htmlBody": mail_html, "name": "Cié Cié Taipei"}) |
| log_msg += f"✅ Mail送出 " |
| except: log_msg += f"❌ Mail失敗 " |
| else: log_msg += "⚠️ 無Mail " |
| |
| if user_id and len(str(user_id)) > 5 and LINE_ACCESS_TOKEN: |
| try: |
| r = requests.post("https://api.line.me/v2/bot/message/push", headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}"}, json={"to": user_id, "messages": [{"type": "text", "text": line_text}]}) |
| if r.status_code == 200: log_msg += f"✅ LINE送出 " |
| else: log_msg += f"❌ LINE失敗 " |
| except: log_msg += f"❌ LINE錯誤 " |
| else: log_msg += "⚠️ 無LINE ID " |
|
|
| if not is_reminder: |
| supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute() |
| return log_msg |
| except Exception as e: return f"嚴重錯誤: {str(e)}" |
|
|
| def toggle_no_show(booking_id, is_noshow=True): |
| """標記或撤銷 No-Show 狀態""" |
| if not booking_id: return "❌ 請輸入訂單 ID" |
| status_text = "No-Show" if is_noshow else "待處理" |
| try: |
| supabase.table("bookings").update({"status": status_text}).eq("id", booking_id).execute() |
| if is_noshow: |
| return f"🚫 訂單 {booking_id} 已成功標記為 No-Show 黑名單!" |
| else: |
| return f"✅ 訂單 {booking_id} 已撤銷 No-Show,恢復為「待處理」。" |
| except Exception as e: |
| return f"❌ 錯誤: {str(e)}" |
|
|
| |
| def update_booking_status(booking_id, new_status): |
| if not booking_id: return "❌ 請輸入訂單 ID" |
| if not new_status: return "⚠️ 請選擇要更改的新狀態" |
| try: |
| res = supabase.table("bookings").update({"status": new_status}).eq("id", booking_id).execute() |
| if not res.data: raise Exception("找不到該筆訂單或權限不足") |
| return f"✅ 訂單 {booking_id} 狀態已成功更新為:【{new_status}】" |
| except Exception as e: |
| return f"❌ 狀態更新失敗: {str(e)}" |
|
|
| def render_booking_cards(): |
| df = get_bookings() |
| count_html = f"<div style='color:#bbb; margin-bottom:10px; text-align:right; font-size:14px;'>📊 共 <span style='color:#fff; font-weight:bold;'>{len(df)}</span> 筆資料</div>" |
| if df.empty: return f"{count_html}<div style='text-align:center; padding:60px; color:#666; font-size:1.5em;'>📭 目前沒有訂位資料</div>" |
| |
| cards_html = f"{count_html}<div style='display: flex; flex-direction: column; gap: 20px; padding-bottom: 20px;'>" |
| |
| for index, row in df.iterrows(): |
| status = row.get('status', '待處理') |
| has_line = bool(row.get('user_id') and len(str(row.get('user_id'))) > 5) |
| |
| status_bg = "#ccc"; status_tx = "#000"; border_color = "#444" |
| if '確認' in status: status_bg = "#2ecc71"; status_tx = "#000"; border_color = "#2ecc71" |
| elif '取消' in status: status_bg = "#e74c3c"; status_tx = "#fff"; border_color = "#e74c3c" |
| elif '已發' in status: status_bg = "#f1c40f"; status_tx = "#000"; border_color = "#f1c40f" |
| elif 'No-Show' in status: status_bg = "#000"; status_tx = "#e74c3c"; border_color = "#e74c3c" |
| elif '付款' in status: status_bg = "#3498db"; status_tx = "#fff"; border_color = "#3498db" |
|
|
| line_badge = "<span style='background:#00B900; color:white; padding:2px 6px; border-radius:4px; font-size:0.7em; margin-left:8px;'>LINE綁定</span>" if has_line else "" |
|
|
| card = f""" |
| <div class="booking-card" style="background:#1a1a1a; border-left:6px solid {border_color}; border-radius:12px; padding:20px; box-shadow:0 4px 15px rgba(0,0,0,0.5); position:relative;"> |
| <div style="display:flex; justify-content:space-between; align-items:start; margin-bottom:15px;"> |
| <div> |
| <span style="font-size:0.9em; color:#666; font-weight:bold; display:block;">訂單 ID</span> |
| <span style="font-size:2.5em; color:#fff; font-weight:900; line-height:1; font-family:monospace;">{row['id']}</span> |
| </div> |
| <div style="text-align:right;"> |
| <div style="background:{status_bg}; color:{status_tx}; padding:6px 12px; border-radius:4px; font-weight:bold; font-size:0.9em; display:inline-block; border: 1px solid {border_color};">{status}</div> |
| <div style="margin-top:5px;">{line_badge}</div> |
| </div> |
| </div> |
| <div style="display:grid; grid-template-columns: 1fr 1fr; gap:10px; margin-bottom:15px; border-top:1px solid #333; padding-top:15px;"> |
| <div><span style="color:#888; font-size:0.85em;">📅 日期</span><br><span style="color:#d4af37; font-size:1.2em; font-weight:bold;">{row['date']}</span></div> |
| <div><span style="color:#888; font-size:0.85em;">⏰ 時間</span><br><span style="color:#d4af37; font-size:1.5em; font-weight:900;">{row['time']}</span></div> |
| </div> |
| <div style="background:#222; padding:15px; border-radius:8px; margin-bottom:15px;"> |
| <div style="margin-bottom:8px;"><span style="color:#fff; font-size:1.4em; font-weight:bold;">{row['name']}</span> <span style="color:#aaa;">({row['pax']}位)</span></div> |
| <div style="font-size:1.1em; margin-bottom:5px;">📞 {row['tel']}</div> |
| <div style="font-size:0.9em; color:#888;">✉️ {row['email'] or '-'}</div> |
| </div> |
| <div style="background:#f1c40f11; padding:12px; border-radius:6px; border:1px solid #f1c40f33;"> |
| <span style="color:#aaa; font-size:0.8em;">📝 備註:</span> <span style="color:#f1c40f;">{row.get('remarks') or '無'}</span> |
| </div> |
| </div> |
| """ |
| cards_html += card |
| |
| cards_html += "</div>" |
| return cards_html |
|
|
|
|
| |
| |
| |
|
|
| def get_menu_items(): |
| try: |
| res = supabase.table("menu_items").select("*").order("category").order("created_at").execute() |
| if not res.data: |
| return pd.DataFrame(columns=['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結']) |
| |
| df = pd.DataFrame(res.data) |
| |
| df['available_times'] = df['available_times'].apply(lambda x: "、".join(x) if isinstance(x, list) else "全時段") |
| df['allow_takeout'] = df['allow_takeout'].apply(lambda x: "✅" if x else "❌") |
| df['require_prepay'] = df['require_prepay'].apply(lambda x: "🔥 需預付" if x else "一般") |
| df['is_active'] = df['is_active'].apply(lambda x: "🟢 販售中" if x else "🔴 已下架") |
| df['has_image'] = df.get('image_url', pd.Series()).apply(lambda x: "🔗 有" if pd.notnull(x) and str(x).strip() else "無") |
|
|
| display_df = df[['name', 'price', 'category', 'available_times', 'allow_takeout', 'require_prepay', 'is_active', 'has_image']] |
| display_df.columns = ['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結'] |
| return display_df |
| except Exception as e: |
| print("Fetch menu error:", e) |
| return pd.DataFrame(columns=['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結']) |
|
|
| def update_menu_dropdown(): |
| try: |
| res = supabase.table("menu_items").select("id, name, is_active").execute() |
| if not res.data: return gr.update(choices=[]) |
| choices = [f"[{'🟢販售中' if item['is_active'] else '🔴已下架'}] {item['name']} | {item['id']}" for item in res.data] |
| return gr.update(choices=choices) |
| except: return gr.update(choices=[]) |
|
|
| |
| def load_menu_data(selected_string): |
| if not selected_string: |
| return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", "" |
| |
| item_id = selected_string.split(" | ")[-1].strip() |
| try: |
| res = supabase.table("menu_items").select("*").eq("id", item_id).execute() |
| if not res.data: return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", "" |
| |
| item = res.data[0] |
| times = item.get("available_times", []) |
| if not times: times = ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"] |
| |
| return ( |
| item.get("name", ""), |
| item.get("description", ""), |
| item.get("price", 0), |
| item.get("category", "main"), |
| times, |
| item.get("require_prepay", False), |
| item.get("allow_takeout", True), |
| item.get("image_url", "") or "", |
| item_id |
| ) |
| except: |
| return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", "" |
|
|
| def add_menu_item(name, desc, price, category, available_times, require_prepay, allow_takeout, image_url_input): |
| if not name or not price: return "⚠️ 名稱與價格為必填", get_menu_items() |
| if not available_times: return "⚠️ 請至少選擇一個供應時段", get_menu_items() |
| final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None |
|
|
| try: |
| data = { |
| "name": name, "description": desc, "price": int(price), "category": category, |
| "available_times": available_times, "allow_takeout": allow_takeout, |
| "require_prepay": require_prepay, "is_active": True, "image_url": final_image_url |
| } |
| supabase.table("menu_items").insert(data).execute() |
| return f"✅ 成功新增上架:{name}", get_menu_items() |
| except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items() |
|
|
| |
| def update_menu_item(item_id, name, desc, price, category, available_times, require_prepay, allow_takeout, image_url_input): |
| if not item_id: return "⚠️ 請先從右側選擇並「載入編輯」一項餐點", get_menu_items() |
| if not name or not price: return "⚠️ 名稱與價格為必填", get_menu_items() |
| final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None |
|
|
| try: |
| data = { |
| "name": name, "description": desc, "price": int(price), "category": category, |
| "available_times": available_times, "allow_takeout": allow_takeout, |
| "require_prepay": require_prepay, "image_url": final_image_url |
| } |
| supabase.table("menu_items").update(data).eq("id", item_id).execute() |
| return f"💾 成功儲存修改:{name}", get_menu_items() |
| except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items() |
|
|
| def toggle_menu_item(selected_string, is_active): |
| if not selected_string: return "⚠️ 請選擇餐點", get_menu_items() |
| try: |
| item_id = selected_string.split(" | ")[-1].strip() |
| supabase.table("menu_items").update({"is_active": is_active}).eq("id", item_id).execute() |
| status_text = "上架" if is_active else "下架" |
| return f"✅ 餐點狀態已更新為:{status_text}", get_menu_items() |
| except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items() |
|
|
|
|
| |
| |
| |
|
|
| def check_login(user, password): |
| if user == REAL_ADMIN_USER and password == REAL_ADMIN_PASSWORD: |
| return { login_row: gr.update(visible=False), admin_tabs: gr.update(visible=True), error_msg: "" } |
| return { error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>" } |
|
|
| custom_css = """ |
| body, .gradio-container { background-color: #0F0F0F; color: #fff; } |
| #op-panel { position: sticky; top: 0; z-index: 100; background: #1a1a1a; border-bottom: 2px solid #d4af37; padding: 15px; margin-bottom: 20px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); } |
| #booking_display { height: auto !important; overflow: visible !important; } |
| """ |
|
|
| with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochrome()) as demo: |
| |
| |
| url_action_msg = gr.HTML(visible=False) |
| |
| with gr.Group(visible=True) as login_row: |
| gr.Markdown("# 🔒 老闆/管理員登入") |
| with gr.Row(): |
| username_input = gr.Textbox(label="使用者名稱") |
| password_input = gr.Textbox(label="密碼", type="password") |
| login_btn = gr.Button("登入系統", variant="primary") |
| error_msg = gr.Markdown("") |
| |
| with gr.Tabs(visible=False) as admin_tabs: |
| |
| |
| with gr.TabItem("🍷 訂位與通知管理"): |
| with gr.Column(elem_id="op-panel"): |
| gr.Markdown("### 🚀 訂單操作控制台") |
| |
| |
| with gr.Row(): |
| id_input = gr.Number(label="輸入訂單 ID", precision=0, scale=1) |
| new_status_dropdown = gr.Dropdown( |
| label="手動選擇新狀態", |
| choices=["待處理", "待處理 (已付訂金)", "待付款", "已發確認信", "顧客已確認", "顧客已取消", "No-Show", "已完成 (結案)"], |
| scale=1 |
| ) |
| |
| |
| with gr.Row(): |
| send_btn = gr.Button("🚀 發信/LINE", variant="primary", scale=1) |
| update_status_btn = gr.Button("💾 變更狀態", variant="secondary", scale=1) |
| noshow_btn = gr.Button("🚫 標記 No-Show", variant="stop", scale=1) |
| revert_noshow_btn = gr.Button("✅ 撤銷 No-Show", scale=1) |
| refresh_btn = gr.Button("🔄 刷新列表", scale=1) |
| |
| log_output = gr.Textbox(label="執行結果日誌", lines=1) |
|
|
| booking_display = gr.HTML(elem_id="booking_display") |
| |
| |
| update_status_btn.click( |
| update_booking_status, |
| inputs=[id_input, new_status_dropdown], |
| outputs=log_output |
| ).then(render_booking_cards, outputs=booking_display) |
|
|
| |
| refresh_btn.click(render_booking_cards, outputs=booking_display) |
| send_btn.click(send_confirmation_hybrid, inputs=id_input, outputs=log_output).then(render_booking_cards, outputs=booking_display) |
| noshow_btn.click(toggle_no_show, inputs=[id_input, gr.State(True)], outputs=log_output).then(render_booking_cards, outputs=booking_display) |
| revert_noshow_btn.click(toggle_no_show, inputs=[id_input, gr.State(False)], outputs=log_output).then(render_booking_cards, outputs=booking_display) |
|
|
| |
| with gr.TabItem("🍽️ 菜單動態管理"): |
| gr.Markdown("### ✨ 上架與編輯餐點") |
| |
| |
| m_edit_id = gr.State("") |
|
|
| with gr.Row(): |
| |
| with gr.Column(scale=1): |
| m_name = gr.Textbox(label="餐點名稱 *") |
| m_desc = gr.Textbox(label="餐點描述 (選填)") |
| m_price = gr.Number(label="價格 (TWD) *", precision=0) |
| m_cat = gr.Dropdown(choices=["main", "snack", "drink", "other"], label="分類", value="main") |
| m_image_url = gr.Textbox(label="餐點照片網址 (選填)", placeholder="例如: https://ciecietaipei.github.io/assets/steak.jpg") |
| m_times = gr.CheckboxGroup( |
| choices=["白天 (11:00-18:30)", "晚餐 (18:30-21:30)", "宵夜 (21:30後)"], |
| value=["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], label="🕒 適用時段 *" |
| ) |
| with gr.Row(): |
| m_takeout = gr.Checkbox(label="🛍️ 開放外帶", value=True) |
| m_prepay = gr.Checkbox(label="🔥 需全額預付", value=False) |
| |
| with gr.Row(): |
| m_add_btn = gr.Button("➕ 作為新餐點上架", variant="primary") |
| m_update_btn = gr.Button("💾 儲存修改 (需先載入)", variant="secondary") |
| |
| m_form_log = gr.Textbox(label="執行結果", interactive=False) |
| |
| |
| with gr.Column(scale=2): |
| gr.Markdown("### 📋 目前線上菜單") |
| menu_df = gr.Dataframe(interactive=False, wrap=True) |
| m_refresh_btn = gr.Button("🔄 刷新菜單") |
| |
| gr.Markdown("#### ⚙️ 快速操作 (編輯與上下架)") |
| with gr.Row(): |
| m_toggle_dropdown = gr.Dropdown(label="選擇要操作的餐點", choices=[], scale=3) |
| m_load_btn = gr.Button("✏️ 載入編輯", scale=1) |
| m_set_active = gr.Button("🟢 上架", scale=1) |
| m_set_inactive = gr.Button("🔴 下架", scale=1) |
| m_toggle_log = gr.Textbox(label="操作狀態", interactive=False) |
|
|
| |
| m_load_btn.click( |
| load_menu_data, |
| inputs=[m_toggle_dropdown], |
| outputs=[m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url, m_edit_id] |
| ).then(lambda: "✅ 已載入至左側表單,修改後請點擊「儲存修改」", outputs=m_toggle_log) |
|
|
| |
| m_add_btn.click( |
| add_menu_item, |
| inputs=[m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url], |
| outputs=[m_form_log, menu_df] |
| ).then(update_menu_dropdown, outputs=m_toggle_dropdown) |
|
|
| m_update_btn.click( |
| update_menu_item, |
| inputs=[m_edit_id, m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url], |
| outputs=[m_form_log, menu_df] |
| ).then(update_menu_dropdown, outputs=m_toggle_dropdown) |
| |
| |
| m_refresh_btn.click(get_menu_items, outputs=menu_df).then(update_menu_dropdown, outputs=m_toggle_dropdown) |
| m_set_active.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(True)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown) |
| m_set_inactive.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(False)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown) |
|
|
| |
| demo.load(check_url_action, inputs=None, outputs=[url_action_msg, login_row]) |
|
|
| |
| login_btn.click( |
| check_login, inputs=[username_input, password_input], outputs=[login_row, admin_tabs, error_msg] |
| ).then( |
| render_booking_cards, outputs=booking_display |
| ).then( |
| get_menu_items, outputs=menu_df |
| ).then( |
| update_menu_dropdown, outputs=m_toggle_dropdown |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |