DeepLearning101 commited on
Commit
307d97e
·
verified ·
1 Parent(s): 5a645b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -80
app.py CHANGED
@@ -29,11 +29,12 @@ 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 (JS Error)"
 
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]
@@ -48,20 +49,18 @@ def send_confirmation_hybrid(booking_id):
48
 
49
  log_msg = f"🆔 {booking_id}: "
50
 
51
- # 2. 判斷:是「發送提醒」還是「發送確認」?
52
- # 如果狀態包含 "確認",代表客人已經確認過了 -> 發送行前提醒
 
53
  is_reminder = "確認" in current_status
54
 
55
  if is_reminder:
56
- # ==========================================
57
- # 🔔 模式 A:行前提醒 (Reminder)
58
- # ==========================================
59
  action_label = "提醒"
60
- mail_subject = f"🔔 行前提醒: {booking['date']} - Cié Cié Taipei"
61
 
62
- # LINE: 純文字提醒,無按鈕
63
  line_text = (
64
- f"🔔 行前提醒\n\n"
65
  f"{booking['name']} 您好,期待今晚與您相見!\n\n"
66
  f"📅 日期:{booking['date']}\n"
67
  f"⏰ 時間:{booking['time']}\n"
@@ -69,7 +68,6 @@ def send_confirmation_hybrid(booking_id):
69
  f"座位已為您準備好,若需變更請聯繫我們。"
70
  )
71
 
72
- # Email: 沒有確認按鈕,只有取消連結
73
  mail_html = f"""
74
  <div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
75
  <h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
@@ -82,20 +80,16 @@ def send_confirmation_hybrid(booking_id):
82
  </ul>
83
  </div>
84
  <div style="text-align:center; margin-top:30px;">
85
- <span style="color:#888; font-size:14px;">這是一封行前提醒,您無需再次確認。</span><br><br>
86
  <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>
87
  </div>
88
  </div>
89
  """
90
-
91
  else:
92
- # ==========================================
93
- # 🚀 模式 B:訂位確認 (Confirmation)
94
- # ==========================================
95
  action_label = "確認"
96
  mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
97
 
98
- # LINE: 要求確認
99
  line_text = (
100
  f"✅ 訂位確認\n\n"
101
  f"{booking['name']} 您好,已收到您的預約。\n\n"
@@ -105,7 +99,6 @@ def send_confirmation_hybrid(booking_id):
105
  f"請務必查收 Email 並點擊「確認出席」,謝謝!"
106
  )
107
 
108
- # Email: 有巨大的確認按鈕
109
  mail_html = f"""
110
  <div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
111
  <h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
@@ -124,44 +117,49 @@ def send_confirmation_hybrid(booking_id):
124
  </div>
125
  """
126
 
127
- # 3. 執行發送
128
- # Send Email
 
129
  if email and "@" in email and GAS_MAIL_URL:
130
  try:
131
  requests.post(GAS_MAIL_URL, json={"to": email, "subject": mail_subject, "htmlBody": mail_html, "name": "Cié Cié Taipei"})
132
  log_msg += f"✅ Mail({action_label}) "
133
- except: log_msg += "❌ MailErr "
 
 
134
 
135
- # Send LINE
136
- if user_id and len(str(user_id)) > 10 and LINE_ACCESS_TOKEN:
137
  try:
138
  requests.post("https://api.line.me/v2/bot/message/push",
139
  headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}"},
140
  json={"to": user_id, "messages": [{"type": "text", "text": line_text}]})
141
  log_msg += f"✅ LINE({action_label}) "
142
- except: log_msg += "❌ LINEErr"
 
 
143
 
144
- # 4. 更新狀態 (僅在非提醒模式下更新,避免覆蓋已確認狀態)
145
  if not is_reminder:
146
  supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
147
 
148
  return log_msg
149
- except Exception as e: return f"Error: {e}"
150
 
151
- # 🔥🔥🔥 前端卡片生成 🔥🔥🔥
 
 
 
152
  def render_booking_cards():
153
  df = get_bookings()
154
-
155
  count_html = f"<div style='color:#bbb; margin-bottom:20px; text-align:right; font-size:16px; padding: 0 10px;'>📊 共找到 <span style='color:#fff; font-weight:bold;'>{len(df)}</span> 筆資料</div>"
156
-
157
- if df.empty:
158
- return f"{count_html}<div style='text-align:center; padding:60px; color:#666; font-size:1.5em;'>📭 目前沒有訂位資料</div>"
159
 
160
  cards_html = f"{count_html}<div style='display: flex; flex-direction: column; gap: 24px; padding-bottom: 50px;'>"
161
 
162
  for index, row in df.iterrows():
163
  status = row.get('status', '待處理')
164
- # 顏色邏輯
 
165
  status_color = "#ccc"; border_color = "#444"
166
  if '確認' in status: status_color = "#2ecc71"; border_color = "#2ecc71"
167
  elif '取消' in status: status_color = "#e74c3c"; border_color = "#e74c3c"
@@ -170,19 +168,18 @@ def render_booking_cards():
170
  is_canceled = '取消' in status
171
  is_confirmed = '確認' in status
172
 
173
- # JS 事件綁定 (只要不是取消,都能點)
174
  btn_onclick = "" if is_canceled else f"cardAction({row['id']})"
175
 
176
- # 按鈕外觀邏輯
177
  if is_canceled:
178
  btn_style = "background: #333; color: #666; cursor: not-allowed;"
179
  btn_text = "🚫 已取消"
180
  elif is_confirmed:
181
- # 🔔 已確認 -> 發送提醒
182
- btn_style = "background: #2c3e50; color: #fff; border: 1px solid #555; box-shadow: 0 0 10px rgba(46, 204, 113, 0.2);"
183
- btn_text = "🔔 發送提醒"
184
  elif '已發' in status:
185
- btn_style = "background: #444; color: #ddd; border: 1px solid #666;"
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);"
@@ -239,10 +236,7 @@ def render_booking_cards():
239
  <div style="font-size: 1.1em; color: #000; font-weight: 900; background: #e0e0e0; padding: 8px 12px; border-radius: 6px; font-family: monospace;">
240
  ID: {row['id']}
241
  </div>
242
-
243
- <button onclick="{btn_onclick}" style="
244
- border: none; padding: 14px 30px; border-radius: 8px; font-size: 1.1em;
245
- transition: all 0.2s; min-width: 150px; cursor: pointer; {btn_style}">
246
  {btn_text}
247
  </button>
248
  </div>
@@ -261,66 +255,45 @@ def check_login(user, password):
261
  admin_row: gr.update(visible=True),
262
  error_msg: ""
263
  }
264
- else:
265
- return {error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>"}
266
 
267
- # --- JS 邏輯 (強化版: 找按鈕更精確) ---
268
  js_logic = """
269
  function() {
270
  window.cardAction = function(id) {
271
- // 1. 更新隱藏 ID
272
  let idInput = document.querySelector('#hidden_id_input input');
273
- if (!idInput) return alert("System Error: Cannot find ID input");
274
-
275
- idInput.value = id;
276
- idInput.dispatchEvent(new Event('input', { bubbles: true }));
277
-
278
- // 2. 觸發隱藏按鈕 (針對 Off-screen 元件的點擊)
279
  setTimeout(() => {
280
  const sendBtn = document.querySelector('#hidden_send_btn');
281
  if (sendBtn) sendBtn.click();
282
- else alert("System Error: Cannot find Send button");
283
- }, 100);
284
  }
285
  }
286
  """
287
 
288
- # --- CSS (🔥 關鍵修正:移出螢幕而非隱藏,保證 JS 按得到) ---
289
  custom_css = """
290
  body, .gradio-container { background-color: #0F0F0F; color: #fff; }
291
-
292
- #booking_display {
293
- height: auto !important;
294
- max-height: none !important;
295
- overflow: visible !important;
296
- margin-bottom: 50px;
297
- }
298
-
299
  button:active { transform: scale(0.96); }
 
300
 
301
- #header-panel {
302
- background: #1a1a1a;
303
- padding: 15px;
304
- margin-bottom: 20px;
305
- border-radius: 10px;
306
- }
307
-
308
- /* 🔥 這裡最重要:不要用 display: none,而是移到畫面外 */
309
- /* 這樣 Gradio 會認為它是可見的,點擊事件才會生效 */
310
  #hidden_ops {
311
  position: absolute !important;
312
  left: -9999px !important;
313
- top: -9999px !important;
314
- width: 1px !important;
315
- height: 1px !important;
316
  overflow: hidden !important;
317
- opacity: 0 !important;
318
  }
319
  """
320
 
321
  # --- 介面 ---
322
  with gr.Blocks(title="Admin") as demo:
323
-
324
  with gr.Group(visible=True) as login_row:
325
  gr.Markdown("# 🔒 Login")
326
  with gr.Row():
@@ -336,7 +309,7 @@ with gr.Blocks(title="Admin") as demo:
336
 
337
  booking_display = gr.HTML(elem_id="booking_display")
338
 
339
- # 🔥 隱藏操作區:visible=True (讓元件存在),但 CSS 把它踢到外太空
340
  with gr.Column(visible=True, elem_id="hidden_ops"):
341
  hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
342
  hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
@@ -346,9 +319,9 @@ with gr.Blocks(title="Admin") as demo:
346
  login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
347
  render_booking_cards, outputs=booking_display
348
  )
349
-
350
  refresh_btn.click(render_booking_cards, outputs=booking_display)
351
 
 
352
  hidden_send_btn.click(
353
  send_confirmation_hybrid,
354
  inputs=hidden_id_input,
 
29
  except:
30
  return pd.DataFrame()
31
 
32
+ # 🔥🔥🔥 5.0 修正版:邏輯統一,不再分流,保證發送 🔥🔥🔥
33
  def send_confirmation_hybrid(booking_id):
34
+ if not booking_id: return "❌ 錯誤:未讀取到 ID"
35
+
36
  try:
37
+ # 1. 撈資料
38
  res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
39
  if not res.data: return "❌ 找不到訂單"
40
  booking = res.data[0]
 
49
 
50
  log_msg = f"🆔 {booking_id}: "
51
 
52
+ # 2. 準備內容 (不管發送有沒有成功,內容先準備好)
53
+ # 如果狀態包含「確認」,就準備「提醒信」的內容
54
+ # 否則,準備「確認信」的內容
55
  is_reminder = "確認" in current_status
56
 
57
  if is_reminder:
58
+ # --- 🅰️ 提醒信內容 (Reminder) ---
 
 
59
  action_label = "提醒"
60
+ mail_subject = f"🔔 訂位提醒: {booking['date']} - Cié Cié Taipei"
61
 
 
62
  line_text = (
63
+ f"🔔 訂位提醒\n\n"
64
  f"{booking['name']} 您好,期待今晚與您相見!\n\n"
65
  f"📅 日期:{booking['date']}\n"
66
  f"⏰ 時間:{booking['time']}\n"
 
68
  f"座位已為您準備好,若需變更請聯繫我們。"
69
  )
70
 
 
71
  mail_html = f"""
72
  <div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
73
  <h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
 
80
  </ul>
81
  </div>
82
  <div style="text-align:center; margin-top:30px;">
83
+ <span style="color:#888; font-size:14px;">這是一封行前提醒。</span><br><br>
84
  <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>
85
  </div>
86
  </div>
87
  """
 
88
  else:
89
+ # --- 🅱️ 確認信內容 (Confirmation) ---
 
 
90
  action_label = "確認"
91
  mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
92
 
 
93
  line_text = (
94
  f"✅ 訂位確認\n\n"
95
  f"{booking['name']} 您好,已收到您的預約。\n\n"
 
99
  f"請務必查收 Email 並點擊「確認出席」,謝謝!"
100
  )
101
 
 
102
  mail_html = f"""
103
  <div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px; font-family:sans-serif; border:1px solid #333;">
104
  <h2 style="text-align:center; border-bottom:1px solid #444; padding-bottom:15px;">Cié Cié Taipei</h2>
 
117
  </div>
118
  """
119
 
120
+ # 3. 🔥🔥🔥 關鍵:不管上面是走哪條路,這裡統一執行發送,絕對不跳過 🔥🔥🔥
121
+
122
+ # 發送 Email
123
  if email and "@" in email and GAS_MAIL_URL:
124
  try:
125
  requests.post(GAS_MAIL_URL, json={"to": email, "subject": mail_subject, "htmlBody": mail_html, "name": "Cié Cié Taipei"})
126
  log_msg += f"✅ Mail({action_label}) "
127
+ except Exception as e: log_msg += f"❌ MailErr({str(e)}) "
128
+ else:
129
+ log_msg += "⚠️ 無Email "
130
 
131
+ # 發送 LINE
132
+ if user_id and len(str(user_id)) > 5 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_label}) "
138
+ except Exception as e: log_msg += f"❌ LINEErr({str(e)}) "
139
+ else:
140
+ log_msg += "⚠️ 無LINE ID "
141
 
142
+ # 4. 更新資料庫 (只有確認信才更新狀態,提醒信不更新)
143
  if not is_reminder:
144
  supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
145
 
146
  return log_msg
 
147
 
148
+ except Exception as e:
149
+ return f"🔥 嚴重錯誤: {str(e)}"
150
+
151
+ # --- 介面渲染 (保持您要的樣式) ---
152
  def render_booking_cards():
153
  df = get_bookings()
 
154
  count_html = f"<div style='color:#bbb; margin-bottom:20px; text-align:right; font-size:16px; padding: 0 10px;'>📊 共找到 <span style='color:#fff; font-weight:bold;'>{len(df)}</span> 筆資料</div>"
155
+ if df.empty: return f"{count_html}<div style='text-align:center; padding:60px; color:#666; font-size:1.5em;'>📭 目前沒有訂位資料</div>"
 
 
156
 
157
  cards_html = f"{count_html}<div style='display: flex; flex-direction: column; gap: 24px; padding-bottom: 50px;'>"
158
 
159
  for index, row in df.iterrows():
160
  status = row.get('status', '待處理')
161
+
162
+ # 狀態與顏色
163
  status_color = "#ccc"; border_color = "#444"
164
  if '確認' in status: status_color = "#2ecc71"; border_color = "#2ecc71"
165
  elif '取消' in status: status_color = "#e74c3c"; border_color = "#e74c3c"
 
168
  is_canceled = '取消' in status
169
  is_confirmed = '確認' in status
170
 
171
+ # JS 綁定
172
  btn_onclick = "" if is_canceled else f"cardAction({row['id']})"
173
 
174
+ # 按鈕外觀
175
  if is_canceled:
176
  btn_style = "background: #333; color: #666; cursor: not-allowed;"
177
  btn_text = "🚫 已取消"
178
  elif is_confirmed:
179
+ btn_style = "background: #2c3e50; color: #fff; border: 1px solid #555; box-shadow: 0 0 8px rgba(46, 204, 113, 0.3);"
180
+ btn_text = "🔔 發送提醒"
 
181
  elif '已發' in status:
182
+ btn_style = "background: #2c3e50; color: #ddd; border: 1px solid #555;"
183
  btn_text = "🔄 重發確認"
184
  else:
185
  btn_style = "background: #d4af37; color: #000; font-weight:800; box-shadow: 0 4px 12px rgba(212, 175, 55, 0.5);"
 
236
  <div style="font-size: 1.1em; color: #000; font-weight: 900; background: #e0e0e0; padding: 8px 12px; border-radius: 6px; font-family: monospace;">
237
  ID: {row['id']}
238
  </div>
239
+ <button onclick="{btn_onclick}" style="border: none; padding: 14px 30px; border-radius: 8px; font-size: 1.1em; transition: all 0.2s; min-width: 150px; cursor: pointer; {btn_style}">
 
 
 
240
  {btn_text}
241
  </button>
242
  </div>
 
255
  admin_row: gr.update(visible=True),
256
  error_msg: ""
257
  }
258
+ else: return {error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>"}
 
259
 
260
+ # --- JS 邏輯 ---
261
  js_logic = """
262
  function() {
263
  window.cardAction = function(id) {
 
264
  let idInput = document.querySelector('#hidden_id_input input');
265
+ if (idInput) {
266
+ idInput.value = id;
267
+ idInput.dispatchEvent(new Event('input', { bubbles: true }));
268
+ }
 
 
269
  setTimeout(() => {
270
  const sendBtn = document.querySelector('#hidden_send_btn');
271
  if (sendBtn) sendBtn.click();
272
+ }, 150);
 
273
  }
274
  }
275
  """
276
 
277
+ # --- CSS (確保按鈕可被點擊) ---
278
  custom_css = """
279
  body, .gradio-container { background-color: #0F0F0F; color: #fff; }
280
+ #booking_display { height: auto !important; max-height: none !important; overflow: visible !important; margin-bottom: 50px; }
 
 
 
 
 
 
 
281
  button:active { transform: scale(0.96); }
282
+ #header-panel { background: #1a1a1a; padding: 15px; margin-bottom: 20px; border-radius: 10px; }
283
 
284
+ /* 絕對定位移出畫面,確保 JS 抓得到 */
 
 
 
 
 
 
 
 
285
  #hidden_ops {
286
  position: absolute !important;
287
  left: -9999px !important;
288
+ top: 0 !important;
289
+ width: 0 !important;
290
+ height: 0 !important;
291
  overflow: hidden !important;
 
292
  }
293
  """
294
 
295
  # --- 介面 ---
296
  with gr.Blocks(title="Admin") as demo:
 
297
  with gr.Group(visible=True) as login_row:
298
  gr.Markdown("# 🔒 Login")
299
  with gr.Row():
 
309
 
310
  booking_display = gr.HTML(elem_id="booking_display")
311
 
312
+ # 🔥 確保 visible=True
313
  with gr.Column(visible=True, elem_id="hidden_ops"):
314
  hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
315
  hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
 
319
  login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
320
  render_booking_cards, outputs=booking_display
321
  )
 
322
  refresh_btn.click(render_booking_cards, outputs=booking_display)
323
 
324
+ # 發送事件
325
  hidden_send_btn.click(
326
  send_confirmation_hybrid,
327
  inputs=hidden_id_input,