Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -29,37 +29,48 @@ def get_bookings():
|
|
| 29 |
except:
|
| 30 |
return pd.DataFrame()
|
| 31 |
|
| 32 |
-
# 🔥🔥🔥
|
| 33 |
def send_confirmation_hybrid(booking_id):
|
| 34 |
if not booking_id: return "❌ 無效 ID"
|
| 35 |
try:
|
|
|
|
| 36 |
res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
|
| 37 |
if not res.data: return "❌ 找不到訂單"
|
| 38 |
booking = res.data[0]
|
| 39 |
-
email, user_id = booking.get('email'), booking.get('user_id')
|
| 40 |
-
current_status = booking.get('status', '')
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
log_msg = f"🆔 {booking_id}: "
|
| 46 |
|
| 47 |
confirm_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=confirm"
|
| 48 |
cancel_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=cancel"
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
if
|
| 52 |
-
#
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
| 55 |
|
| 56 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
mail_html = f"""
|
| 58 |
<div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
|
| 59 |
<h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
|
| 60 |
-
<p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong>
|
| 61 |
-
<
|
| 62 |
-
<div style="background:#2a2a2a; padding:15px; border-radius:8px; margin:20px 0; border-left:4px solid #2ecc71;">
|
| 63 |
<ul style="color:#ddd; padding-left:20px; line-height:1.8;">
|
| 64 |
<li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
|
| 65 |
<li>⏰ 時間:<strong style="color:#fff;">{booking['time']}</strong></li>
|
|
@@ -67,22 +78,34 @@ def send_confirmation_hybrid(booking_id):
|
|
| 67 |
</ul>
|
| 68 |
</div>
|
| 69 |
<div style="text-align:center; margin-top:30px;">
|
| 70 |
-
<span style="color:#888;
|
| 71 |
-
<a href="{cancel_link}" style="display:inline-block; border:1px solid #555; color:#aaa; padding:10px
|
| 72 |
</div>
|
| 73 |
</div>
|
| 74 |
"""
|
| 75 |
-
|
| 76 |
else:
|
| 77 |
-
#
|
|
|
|
|
|
|
|
|
|
| 78 |
mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
|
| 79 |
-
line_text = f"✅ 訂位確認\n\n{booking['name']} 您好,已收到您的預約。\n\n📅 日期:{booking['date']}\n⏰ 時間:{booking['time']}\n👥 人數:{booking['pax']}位\n\n請查收 Email 確認信並點擊確認按鈕,謝謝!"
|
| 80 |
|
| 81 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
mail_html = f"""
|
| 83 |
<div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
|
| 84 |
<h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
|
| 85 |
-
<p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong>
|
| 86 |
<div style="background:#2a2a2a; padding:15px; border-radius:8px; margin:20px 0; border-left:4px solid #d4af37;">
|
| 87 |
<ul style="color:#ddd; padding-left:20px; line-height:1.8;">
|
| 88 |
<li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
|
|
@@ -91,39 +114,37 @@ def send_confirmation_hybrid(booking_id):
|
|
| 91 |
</ul>
|
| 92 |
</div>
|
| 93 |
<div style="text-align:center; margin-top:30px;">
|
| 94 |
-
<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;">✅
|
| 95 |
-
<a href="{cancel_link}" style="display:inline-block; border:1px solid #555; color:#aaa; padding:11px 29px; text-decoration:none; border-radius:50px;">🚫
|
| 96 |
</div>
|
| 97 |
</div>
|
| 98 |
"""
|
| 99 |
-
action_log = "確認信"
|
| 100 |
|
| 101 |
-
#
|
|
|
|
| 102 |
if email and "@" in email and GAS_MAIL_URL:
|
| 103 |
try:
|
| 104 |
requests.post(GAS_MAIL_URL, json={"to": email, "subject": mail_subject, "htmlBody": mail_html, "name": "Cié Cié Taipei"})
|
| 105 |
-
log_msg += f"✅ Mail({
|
| 106 |
except: log_msg += "❌ MailErr "
|
| 107 |
|
| 108 |
-
#
|
| 109 |
if user_id and len(str(user_id)) > 10 and LINE_ACCESS_TOKEN:
|
| 110 |
try:
|
| 111 |
requests.post("https://api.line.me/v2/bot/message/push",
|
| 112 |
headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}"},
|
| 113 |
json={"to": user_id, "messages": [{"type": "text", "text": line_text}]})
|
| 114 |
-
log_msg += f"✅ LINE({
|
| 115 |
except: log_msg += "❌ LINEErr"
|
| 116 |
|
| 117 |
-
#
|
| 118 |
-
|
| 119 |
-
# 如果本來是待處理,發送後變更為已發確認信
|
| 120 |
-
if not is_confirmed:
|
| 121 |
supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
|
| 122 |
|
| 123 |
return log_msg
|
| 124 |
except Exception as e: return f"Error: {e}"
|
| 125 |
|
| 126 |
-
# 🔥🔥🔥 卡片渲染 (
|
| 127 |
def render_booking_cards():
|
| 128 |
df = get_bookings()
|
| 129 |
|
|
@@ -147,23 +168,23 @@ def render_booking_cards():
|
|
| 147 |
status_color = "#f1c40f"; border_color = "#f1c40f"
|
| 148 |
|
| 149 |
is_canceled = '取消' in status
|
| 150 |
-
is_confirmed = '確認' in status
|
| 151 |
|
|
|
|
| 152 |
btn_onclick = "" if is_canceled else f"cardAction({row['id']})"
|
| 153 |
|
| 154 |
-
# 按鈕樣式邏輯
|
| 155 |
if is_canceled:
|
| 156 |
btn_style = "background: #333; color: #666; cursor: not-allowed;"
|
| 157 |
btn_text = "🚫 已取消"
|
| 158 |
elif is_confirmed:
|
| 159 |
-
# 🔔
|
| 160 |
-
|
| 161 |
-
|
|
|
|
| 162 |
elif '已發' in status:
|
| 163 |
-
btn_style = "background: #
|
| 164 |
btn_text = "🔄 重發確認"
|
| 165 |
else:
|
| 166 |
-
# 待處理狀態 -> 顯示發送確認
|
| 167 |
btn_style = "background: #d4af37; color: #000; font-weight:800; box-shadow: 0 4px 12px rgba(212, 175, 55, 0.5);"
|
| 168 |
btn_text = "🚀 發送確認"
|
| 169 |
|
|
@@ -276,7 +297,6 @@ def check_login(user, password):
|
|
| 276 |
js_logic = """
|
| 277 |
function() {
|
| 278 |
window.cardAction = function(id) {
|
| 279 |
-
// 抓取輸入框 (兼容 Number 和 Textbox 結構)
|
| 280 |
let idInput = document.querySelector('#hidden_id_input input') ||
|
| 281 |
document.querySelector('#hidden_id_input textarea');
|
| 282 |
|
|
@@ -293,7 +313,7 @@ function() {
|
|
| 293 |
}
|
| 294 |
"""
|
| 295 |
|
| 296 |
-
# --- CSS (
|
| 297 |
custom_css = """
|
| 298 |
body, .gradio-container { background-color: #0F0F0F; color: #fff; }
|
| 299 |
|
|
@@ -313,7 +333,6 @@ button:active { transform: scale(0.96); }
|
|
| 313 |
border-radius: 10px;
|
| 314 |
}
|
| 315 |
|
| 316 |
-
/* 🔥 關鍵:隱藏操作區,但保持 visible=True 以供 JS 存取 */
|
| 317 |
#hidden_ops {
|
| 318 |
display: none !important;
|
| 319 |
}
|
|
@@ -322,7 +341,6 @@ button:active { transform: scale(0.96); }
|
|
| 322 |
# --- 介面 ---
|
| 323 |
with gr.Blocks(title="Admin") as demo:
|
| 324 |
|
| 325 |
-
# 1. 登入
|
| 326 |
with gr.Group(visible=True) as login_row:
|
| 327 |
gr.Markdown("# 🔒 Login")
|
| 328 |
with gr.Row():
|
|
@@ -331,30 +349,26 @@ with gr.Blocks(title="Admin") as demo:
|
|
| 331 |
login_btn = gr.Button("Enter", variant="primary")
|
| 332 |
error_msg = gr.Markdown("")
|
| 333 |
|
| 334 |
-
# 2. 後台
|
| 335 |
with gr.Group(visible=False) as admin_row:
|
| 336 |
with gr.Row(variant="panel", elem_id="header-panel"):
|
| 337 |
gr.Markdown("### 🍷 Cié Cié Dashboard")
|
| 338 |
refresh_btn = gr.Button("🔄 刷新列表", size="sm", variant="secondary")
|
| 339 |
|
| 340 |
-
# 卡片顯示區
|
| 341 |
booking_display = gr.HTML(elem_id="booking_display")
|
| 342 |
|
| 343 |
-
# 🔥 隱藏的操作區 (visible=True
|
| 344 |
with gr.Column(visible=True, elem_id="hidden_ops"):
|
| 345 |
hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
|
| 346 |
hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
|
| 347 |
|
| 348 |
log_output = gr.Textbox(label="系統日誌 (System Log)", lines=1, interactive=False)
|
| 349 |
|
| 350 |
-
# 綁定事件
|
| 351 |
login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
|
| 352 |
render_booking_cards, outputs=booking_display
|
| 353 |
)
|
| 354 |
|
| 355 |
refresh_btn.click(render_booking_cards, outputs=booking_display)
|
| 356 |
|
| 357 |
-
# 發送事件
|
| 358 |
hidden_send_btn.click(
|
| 359 |
send_confirmation_hybrid,
|
| 360 |
inputs=hidden_id_input,
|
|
@@ -364,7 +378,6 @@ with gr.Blocks(title="Admin") as demo:
|
|
| 364 |
outputs=booking_display
|
| 365 |
)
|
| 366 |
|
| 367 |
-
# 載入 JS 與 CSS
|
| 368 |
demo.launch(css=custom_css, js=js_logic)
|
| 369 |
|
| 370 |
if __name__ == "__main__":
|
|
|
|
| 29 |
except:
|
| 30 |
return pd.DataFrame()
|
| 31 |
|
| 32 |
+
# 🔥🔥🔥 核心修正:這裡決定「按下去要發什麼」 🔥🔥🔥
|
| 33 |
def send_confirmation_hybrid(booking_id):
|
| 34 |
if not booking_id: return "❌ 無效 ID"
|
| 35 |
try:
|
| 36 |
+
# 1. 先去資料庫抓最新的狀態
|
| 37 |
res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
|
| 38 |
if not res.data: return "❌ 找不到訂單"
|
| 39 |
booking = res.data[0]
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
email = booking.get('email')
|
| 42 |
+
user_id = booking.get('user_id')
|
| 43 |
+
current_status = booking.get('status', '') # 抓取當前狀態
|
|
|
|
| 44 |
|
| 45 |
confirm_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=confirm"
|
| 46 |
cancel_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=cancel"
|
| 47 |
+
|
| 48 |
+
log_msg = f"🆔 {booking_id}: "
|
| 49 |
|
| 50 |
+
# 2. 判斷邏輯:是「已確認」還是「未確認」?
|
| 51 |
+
if "顧客已確認" in current_status:
|
| 52 |
+
# ==========================================
|
| 53 |
+
# 🔔 模式 A:發送「行前提醒」 (Reminder)
|
| 54 |
+
# ==========================================
|
| 55 |
+
action_type = "提醒"
|
| 56 |
+
mail_subject = f"🔔 行前提醒: {booking['date']} - Cié Cié Taipei"
|
| 57 |
|
| 58 |
+
# LINE 內容:強調期待光臨,沒有確認按鈕
|
| 59 |
+
line_text = (
|
| 60 |
+
f"🔔 行前提醒\n\n"
|
| 61 |
+
f"{booking['name']} 您好,期待今晚與您相見!\n\n"
|
| 62 |
+
f"📅 日期:{booking['date']}\n"
|
| 63 |
+
f"⏰ 時間:{booking['time']}\n"
|
| 64 |
+
f"👥 人數:{booking['pax']} 位\n\n"
|
| 65 |
+
f"若需更改行程,請聯繫我們或點擊下方連結取消。"
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Email 內容:純提醒,只留取消按鈕
|
| 69 |
mail_html = f"""
|
| 70 |
<div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
|
| 71 |
<h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
|
| 72 |
+
<p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong> 您好,這是您的訂位提醒:</p>
|
| 73 |
+
<div style="background:#2a2a2a; padding:15px; border-radius:8px; margin:20px 0; border-left:4px solid #d4af37;">
|
|
|
|
| 74 |
<ul style="color:#ddd; padding-left:20px; line-height:1.8;">
|
| 75 |
<li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
|
| 76 |
<li>⏰ 時間:<strong style="color:#fff;">{booking['time']}</strong></li>
|
|
|
|
| 78 |
</ul>
|
| 79 |
</div>
|
| 80 |
<div style="text-align:center; margin-top:30px;">
|
| 81 |
+
<span style="color:#888;">我們已為您準備好座位,期待您的光臨。</span><br><br>
|
| 82 |
+
<a href="{cancel_link}" style="display:inline-block; border:1px solid #555; color:#aaa; padding:10px 20px; text-decoration:none; border-radius:50px; font-size:12px;">若無法前來,請點此取消</a>
|
| 83 |
</div>
|
| 84 |
</div>
|
| 85 |
"""
|
| 86 |
+
|
| 87 |
else:
|
| 88 |
+
# ==========================================
|
| 89 |
+
# 🚀 模式 B:發送「訂位確認」 (Confirmation)
|
| 90 |
+
# ==========================================
|
| 91 |
+
action_type = "確認"
|
| 92 |
mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
|
|
|
|
| 93 |
|
| 94 |
+
# LINE 內容:包含確認請求
|
| 95 |
+
line_text = (
|
| 96 |
+
f"✅ 訂位確認\n\n"
|
| 97 |
+
f"{booking['name']} 您好,已收到您的預約。\n\n"
|
| 98 |
+
f"📅 日期:{booking['date']}\n"
|
| 99 |
+
f"⏰ 時間:{booking['time']}\n"
|
| 100 |
+
f"👥 人數:{booking['pax']} 位\n\n"
|
| 101 |
+
f"請務必查收 Email 確認信並點擊「確認出席」按鈕,謝謝!"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
# Email 內容:包含巨大的確認按鈕
|
| 105 |
mail_html = f"""
|
| 106 |
<div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
|
| 107 |
<h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
|
| 108 |
+
<p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong> 您好,請確認您的訂位:</p>
|
| 109 |
<div style="background:#2a2a2a; padding:15px; border-radius:8px; margin:20px 0; border-left:4px solid #d4af37;">
|
| 110 |
<ul style="color:#ddd; padding-left:20px; line-height:1.8;">
|
| 111 |
<li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
|
|
|
|
| 114 |
</ul>
|
| 115 |
</div>
|
| 116 |
<div style="text-align:center; margin-top:30px;">
|
| 117 |
+
<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;">✅ 確認出席 (Confirm)</a>
|
| 118 |
+
<a href="{cancel_link}" style="display:inline-block; border:1px solid #555; color:#aaa; padding:11px 29px; text-decoration:none; border-radius:50px;">🚫 取消 (Cancel)</a>
|
| 119 |
</div>
|
| 120 |
</div>
|
| 121 |
"""
|
|
|
|
| 122 |
|
| 123 |
+
# 3. 執行發送 (共用發送模組)
|
| 124 |
+
# Send Email
|
| 125 |
if email and "@" in email and GAS_MAIL_URL:
|
| 126 |
try:
|
| 127 |
requests.post(GAS_MAIL_URL, json={"to": email, "subject": mail_subject, "htmlBody": mail_html, "name": "Cié Cié Taipei"})
|
| 128 |
+
log_msg += f"✅ Mail({action_type}) "
|
| 129 |
except: log_msg += "❌ MailErr "
|
| 130 |
|
| 131 |
+
# Send LINE
|
| 132 |
if user_id and len(str(user_id)) > 10 and LINE_ACCESS_TOKEN:
|
| 133 |
try:
|
| 134 |
requests.post("https://api.line.me/v2/bot/message/push",
|
| 135 |
headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}"},
|
| 136 |
json={"to": user_id, "messages": [{"type": "text", "text": line_text}]})
|
| 137 |
+
log_msg += f"✅ LINE({action_type}) "
|
| 138 |
except: log_msg += "❌ LINEErr"
|
| 139 |
|
| 140 |
+
# 4. 更新資料庫狀態 (只有在非確認狀態下才更新,避免覆蓋「已確認」)
|
| 141 |
+
if action_type == "確認":
|
|
|
|
|
|
|
| 142 |
supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
|
| 143 |
|
| 144 |
return log_msg
|
| 145 |
except Exception as e: return f"Error: {e}"
|
| 146 |
|
| 147 |
+
# 🔥🔥🔥 卡片渲染 (介面邏輯:決定按鈕長怎樣) 🔥🔥🔥
|
| 148 |
def render_booking_cards():
|
| 149 |
df = get_bookings()
|
| 150 |
|
|
|
|
| 168 |
status_color = "#f1c40f"; border_color = "#f1c40f"
|
| 169 |
|
| 170 |
is_canceled = '取消' in status
|
| 171 |
+
is_confirmed = '確認' in status # 判斷是否已確認
|
| 172 |
|
| 173 |
+
# 🟢 修正重點:只要不是已取消,按鈕永遠可以點擊,只是文字不同
|
| 174 |
btn_onclick = "" if is_canceled else f"cardAction({row['id']})"
|
| 175 |
|
|
|
|
| 176 |
if is_canceled:
|
| 177 |
btn_style = "background: #333; color: #666; cursor: not-allowed;"
|
| 178 |
btn_text = "🚫 已取消"
|
| 179 |
elif is_confirmed:
|
| 180 |
+
# 🔔 狀態是「顧客已確認」時,按鈕變成「發送提醒」
|
| 181 |
+
# 點擊後 -> 觸發 send_confirmation_hybrid -> 函式內會判斷狀態 -> 發送「提醒信」
|
| 182 |
+
btn_style = "background: #2c3e50; color: #fff; border: 1px solid #555; box-shadow: 0 0 8px rgba(46, 204, 113, 0.3);"
|
| 183 |
+
btn_text = "🔔 發送提醒"
|
| 184 |
elif '已發' in status:
|
| 185 |
+
btn_style = "background: #2c3e50; color: #ddd; border: 1px solid #555;"
|
| 186 |
btn_text = "🔄 重發確認"
|
| 187 |
else:
|
|
|
|
| 188 |
btn_style = "background: #d4af37; color: #000; font-weight:800; box-shadow: 0 4px 12px rgba(212, 175, 55, 0.5);"
|
| 189 |
btn_text = "🚀 發送確認"
|
| 190 |
|
|
|
|
| 297 |
js_logic = """
|
| 298 |
function() {
|
| 299 |
window.cardAction = function(id) {
|
|
|
|
| 300 |
let idInput = document.querySelector('#hidden_id_input input') ||
|
| 301 |
document.querySelector('#hidden_id_input textarea');
|
| 302 |
|
|
|
|
| 313 |
}
|
| 314 |
"""
|
| 315 |
|
| 316 |
+
# --- CSS (修復元件隱藏問題) ---
|
| 317 |
custom_css = """
|
| 318 |
body, .gradio-container { background-color: #0F0F0F; color: #fff; }
|
| 319 |
|
|
|
|
| 333 |
border-radius: 10px;
|
| 334 |
}
|
| 335 |
|
|
|
|
| 336 |
#hidden_ops {
|
| 337 |
display: none !important;
|
| 338 |
}
|
|
|
|
| 341 |
# --- 介面 ---
|
| 342 |
with gr.Blocks(title="Admin") as demo:
|
| 343 |
|
|
|
|
| 344 |
with gr.Group(visible=True) as login_row:
|
| 345 |
gr.Markdown("# 🔒 Login")
|
| 346 |
with gr.Row():
|
|
|
|
| 349 |
login_btn = gr.Button("Enter", variant="primary")
|
| 350 |
error_msg = gr.Markdown("")
|
| 351 |
|
|
|
|
| 352 |
with gr.Group(visible=False) as admin_row:
|
| 353 |
with gr.Row(variant="panel", elem_id="header-panel"):
|
| 354 |
gr.Markdown("### 🍷 Cié Cié Dashboard")
|
| 355 |
refresh_btn = gr.Button("🔄 刷新列表", size="sm", variant="secondary")
|
| 356 |
|
|
|
|
| 357 |
booking_display = gr.HTML(elem_id="booking_display")
|
| 358 |
|
| 359 |
+
# 🔥 隱藏的操作區 (確保 visible=True)
|
| 360 |
with gr.Column(visible=True, elem_id="hidden_ops"):
|
| 361 |
hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
|
| 362 |
hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
|
| 363 |
|
| 364 |
log_output = gr.Textbox(label="系統日誌 (System Log)", lines=1, interactive=False)
|
| 365 |
|
|
|
|
| 366 |
login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
|
| 367 |
render_booking_cards, outputs=booking_display
|
| 368 |
)
|
| 369 |
|
| 370 |
refresh_btn.click(render_booking_cards, outputs=booking_display)
|
| 371 |
|
|
|
|
| 372 |
hidden_send_btn.click(
|
| 373 |
send_confirmation_hybrid,
|
| 374 |
inputs=hidden_id_input,
|
|
|
|
| 378 |
outputs=booking_display
|
| 379 |
)
|
| 380 |
|
|
|
|
| 381 |
demo.launch(css=custom_css, js=js_logic)
|
| 382 |
|
| 383 |
if __name__ == "__main__":
|