DeepLearning101 commited on
Commit
64ee3c6
·
verified ·
1 Parent(s): b51a54f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +45 -59
app.py CHANGED
@@ -29,30 +29,34 @@ def get_bookings():
29
  except:
30
  return pd.DataFrame()
31
 
32
- # 🔥🔥🔥 發送邏輯 (暴力發送) 🔥🔥🔥
33
  def send_confirmation_hybrid(booking_id):
34
- print(f"收到後端請求,ID: {booking_id}")
35
- if not booking_id: return " 錯誤:未讀取到 ID (Backend received None)"
 
 
 
36
 
37
  try:
 
38
  res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
39
- if not res.data: return "❌ 找不到訂單"
40
  booking = res.data[0]
41
 
42
  email = booking.get('email')
43
  user_id = booking.get('user_id')
44
  current_status = booking.get('status', '')
45
 
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
  log_msg = f"🆔 {booking_id}: "
51
 
52
- # 判斷是提醒還是確認
53
  is_reminder = "確認" in current_status
54
 
55
  if is_reminder:
 
56
  action_label = "提醒"
57
  mail_subject = f"🔔 訂位提醒: {booking['date']} - Cié Cié Taipei"
58
  line_text = (
@@ -80,6 +84,7 @@ def send_confirmation_hybrid(booking_id):
80
  </div>
81
  """
82
  else:
 
83
  action_label = "確認"
84
  mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
85
  line_text = (
@@ -108,14 +113,14 @@ def send_confirmation_hybrid(booking_id):
108
  </div>
109
  """
110
 
111
- # 執行發送
112
  if email and "@" in email and GAS_MAIL_URL:
113
  try:
114
  requests.post(GAS_MAIL_URL, json={"to": email, "subject": mail_subject, "htmlBody": mail_html, "name": "Cié Cié Taipei"})
115
  log_msg += f"✅ Mail({action_label}) "
116
  except Exception as e: log_msg += f"❌ MailErr({str(e)}) "
117
  else:
118
- log_msg += "⚠️ 無Email "
119
 
120
  if user_id and len(str(user_id)) > 5 and LINE_ACCESS_TOKEN:
121
  try:
@@ -130,11 +135,14 @@ def send_confirmation_hybrid(booking_id):
130
  if not is_reminder:
131
  supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
132
 
 
133
  return log_msg
134
 
135
- except Exception as e: return f"🔥 嚴重錯誤: {str(e)}"
 
 
136
 
137
- # 🔥🔥🔥 卡片渲染 (修正:不使用 onclick,改用 data-id) 🔥🔥🔥
138
  def render_booking_cards():
139
  df = get_bookings()
140
  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>"
@@ -153,13 +161,14 @@ def render_booking_cards():
153
  is_canceled = '取消' in status
154
  is_confirmed = '確認' in status
155
 
156
- # 🟢 修正:移除 onclick,改用 class 和 data-id 屬性
157
- # 這樣就不會依賴全域函式是否存在,而是靠下方的監聽器
158
- btn_attrs = f"class='op-btn' data-id='{row['id']}'" if not is_canceled else "disabled"
159
 
160
  if is_canceled:
161
  btn_style = "background: #333; color: #666; cursor: not-allowed;"
162
  btn_text = "🚫 已取消"
 
163
  elif is_confirmed:
164
  btn_style = "background: #2c3e50; color: #fff; border: 1px solid #555; box-shadow: 0 0 8px rgba(46, 204, 113, 0.3);"
165
  btn_text = "🔔 發送提醒"
@@ -221,10 +230,7 @@ def render_booking_cards():
221
  <div style="font-size: 1.1em; color: #000; font-weight: 900; background: #e0e0e0; padding: 8px 12px; border-radius: 6px; font-family: monospace;">
222
  ID: {row['id']}
223
  </div>
224
-
225
- <button {btn_attrs} style="
226
- border: none; padding: 14px 30px; border-radius: 8px; font-size: 1.1em;
227
- transition: all 0.2s; min-width: 150px; cursor: pointer; {btn_style}">
228
  {btn_text}
229
  </button>
230
  </div>
@@ -245,76 +251,55 @@ def check_login(user, password):
245
  }
246
  else: return {error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>"}
247
 
248
- # 🔥🔥🔥 絕對有效的事件監聽器 (Event Delegation) 🔥🔥🔥
249
- # 這次不是定義函式,而是直接監聽整個文件的點擊事件。
250
- # 只要點擊了 class="op-btn" 的元素,就執行。這不依賴作用域,一定有效。
251
  GLOBAL_JS = """
252
  <script>
253
  document.addEventListener('DOMContentLoaded', function() {
254
- console.log("🟢 訂位系統監聽器已啟動");
255
  });
256
 
257
- // 全域監聽點擊 (Event Delegation)
258
  document.addEventListener('click', function(e) {
259
- // 檢查點擊的目標是否是我們的操作按鈕
260
  if (e.target && e.target.classList.contains('op-btn')) {
261
  const id = e.target.getAttribute('data-id');
262
- console.log("👉 偵測到按鈕點擊,ID:", id);
263
 
264
- if (!id) return;
265
-
266
- // 1. 找輸入框
267
- const idInput = document.querySelector('#hidden_id_input input');
268
 
269
  if (idInput) {
270
- console.log("✅ 寫入 ID 到 Gradio...");
271
  idInput.value = id;
272
  idInput.dispatchEvent(new Event('input', { bubbles: true }));
273
 
274
- // 2. 觸發發送按鈕
275
  setTimeout(() => {
276
- const sendBtn = document.querySelector('#hidden_send_btn');
277
  if (sendBtn) {
278
- console.log("🚀 觸發 Python 發送邏輯...");
279
  sendBtn.click();
280
  } else {
281
- console.error("❌ 找不到 hidden_send_btn");
282
  }
283
  }, 100);
284
  } else {
285
- console.error("❌ 找不到 hidden_id_input");
286
- alert("系統錯誤:找不到對應的輸入框,請重新整理頁面。");
287
  }
288
  }
289
  });
290
  </script>
291
  """
292
 
293
- # --- CSS (確保按鈕可按) ---
294
  custom_css = """
295
  body, .gradio-container { background-color: #0F0F0F; color: #fff; }
296
  #booking_display { height: auto !important; max-height: none !important; overflow: visible !important; margin-bottom: 50px; }
297
  button:active { transform: scale(0.96); }
298
  #header-panel { background: #1a1a1a; padding: 15px; margin-bottom: 20px; border-radius: 10px; }
299
-
300
- /* 讓操作按鈕有點擊指標 */
301
  .op-btn { pointer-events: auto !important; }
302
 
303
- /* 隱藏操作區:移出畫面外 */
304
- #hidden_ops {
305
- position: absolute !important;
306
- left: -9999px !important;
307
- top: 0 !important;
308
- width: 0 !important;
309
- height: 0 !important;
310
- overflow: hidden !important;
311
- }
312
  """
313
 
314
- # --- 介面 ---
315
  with gr.Blocks(title="Admin") as demo:
316
-
317
- # 注入 JS (直接執行,不包在 function 裡)
318
  gr.HTML(GLOBAL_JS)
319
 
320
  with gr.Group(visible=True) as login_row:
@@ -330,23 +315,24 @@ with gr.Blocks(title="Admin") as demo:
330
  gr.Markdown("### 🍷 Cié Cié Dashboard")
331
  refresh_btn = gr.Button("🔄 刷新列表", size="sm", variant="secondary")
332
 
 
 
 
 
 
 
333
  booking_display = gr.HTML(elem_id="booking_display")
334
-
335
- # 隱藏操作區
336
- with gr.Column(visible=True, elem_id="hidden_ops"):
337
- hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
338
- hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
339
-
340
- log_output = gr.Textbox(label="系統日誌 (System Log)", lines=1, interactive=False)
341
 
342
  login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
343
  render_booking_cards, outputs=booking_display
344
  )
345
  refresh_btn.click(render_booking_cards, outputs=booking_display)
346
 
347
- hidden_send_btn.click(
 
348
  send_confirmation_hybrid,
349
- inputs=hidden_id_input,
350
  outputs=log_output
351
  ).then(
352
  render_booking_cards,
 
29
  except:
30
  return pd.DataFrame()
31
 
32
+ # 🔥🔥🔥 後端:暴力發送邏輯 (加上 Print Debug) 🔥🔥🔥
33
  def send_confirmation_hybrid(booking_id):
34
+ # Debug: 這裡會印在 Hugging Face 的 Logs 裡
35
+ print(f"🔥 [Backend] 收到發送請求,ID: {booking_id}")
36
+
37
+ if not booking_id:
38
+ return "❌ 錯誤:後端收到的 ID 為空!"
39
 
40
  try:
41
+ # 1. 撈資料
42
  res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
43
+ if not res.data: return f"❌ 找不到 ID 為 {booking_id} 的訂單"
44
  booking = res.data[0]
45
 
46
  email = booking.get('email')
47
  user_id = booking.get('user_id')
48
  current_status = booking.get('status', '')
49
 
 
50
  confirm_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=confirm"
51
  cancel_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=cancel"
52
 
53
  log_msg = f"🆔 {booking_id}: "
54
 
55
+ # 2. 判斷模式
56
  is_reminder = "確認" in current_status
57
 
58
  if is_reminder:
59
+ # 提醒模式
60
  action_label = "提醒"
61
  mail_subject = f"🔔 訂位提醒: {booking['date']} - Cié Cié Taipei"
62
  line_text = (
 
84
  </div>
85
  """
86
  else:
87
+ # 確認模式
88
  action_label = "確認"
89
  mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
90
  line_text = (
 
113
  </div>
114
  """
115
 
116
+ # 3. 發送
117
  if email and "@" in email and GAS_MAIL_URL:
118
  try:
119
  requests.post(GAS_MAIL_URL, json={"to": email, "subject": mail_subject, "htmlBody": mail_html, "name": "Cié Cié Taipei"})
120
  log_msg += f"✅ Mail({action_label}) "
121
  except Exception as e: log_msg += f"❌ MailErr({str(e)}) "
122
  else:
123
+ log_msg += "⚠️ 無Mail "
124
 
125
  if user_id and len(str(user_id)) > 5 and LINE_ACCESS_TOKEN:
126
  try:
 
135
  if not is_reminder:
136
  supabase.table("bookings").update({"status": "已發確認信"}).eq("id", booking_id).execute()
137
 
138
+ print(f"🔥 [Backend] 處理完成: {log_msg}")
139
  return log_msg
140
 
141
+ except Exception as e:
142
+ print(f"🔥 [Backend] 嚴重錯誤: {str(e)}")
143
+ return f"嚴重錯誤: {str(e)}"
144
 
145
+ # --- 卡片渲染 ( CSS 驅動點擊) ---
146
  def render_booking_cards():
147
  df = get_bookings()
148
  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>"
 
161
  is_canceled = '取消' in status
162
  is_confirmed = '確認' in status
163
 
164
+ # JS Class 標記
165
+ btn_class = "op-btn"
166
+ btn_data = f"data-id='{row['id']}'"
167
 
168
  if is_canceled:
169
  btn_style = "background: #333; color: #666; cursor: not-allowed;"
170
  btn_text = "🚫 已取消"
171
+ btn_class = "" # 取消狀態不給按
172
  elif is_confirmed:
173
  btn_style = "background: #2c3e50; color: #fff; border: 1px solid #555; box-shadow: 0 0 8px rgba(46, 204, 113, 0.3);"
174
  btn_text = "🔔 發送提醒"
 
230
  <div style="font-size: 1.1em; color: #000; font-weight: 900; background: #e0e0e0; padding: 8px 12px; border-radius: 6px; font-family: monospace;">
231
  ID: {row['id']}
232
  </div>
233
+ <button class="{btn_class}" {btn_data} style="border: none; padding: 14px 30px; border-radius: 8px; font-size: 1.1em; transition: all 0.2s; min-width: 150px; cursor: pointer; {btn_style}">
 
 
 
234
  {btn_text}
235
  </button>
236
  </div>
 
251
  }
252
  else: return {error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>"}
253
 
254
+ # 🔥🔥🔥 Debug JS:有彈窗,按鈕看得到 🔥🔥🔥
 
 
255
  GLOBAL_JS = """
256
  <script>
257
  document.addEventListener('DOMContentLoaded', function() {
258
+ console.log("🟢 Debug JS Loaded");
259
  });
260
 
 
261
  document.addEventListener('click', function(e) {
 
262
  if (e.target && e.target.classList.contains('op-btn')) {
263
  const id = e.target.getAttribute('data-id');
 
264
 
265
+ // 1. 彈窗確認 JS 有活著
266
+ // alert("JS 偵測到點擊!準備發送 ID: " + id);
267
+
268
+ const idInput = document.querySelector('#debug_id_input input');
269
 
270
  if (idInput) {
 
271
  idInput.value = id;
272
  idInput.dispatchEvent(new Event('input', { bubbles: true }));
273
 
 
274
  setTimeout(() => {
275
+ const sendBtn = document.querySelector('#debug_send_btn');
276
  if (sendBtn) {
277
+ console.log("🚀 Clicking send button...");
278
  sendBtn.click();
279
  } else {
280
+ alert("❌ 找不到發送按鈕 (#debug_send_btn)");
281
  }
282
  }, 100);
283
  } else {
284
+ alert("❌ 找不到輸入框 (#debug_id_input)");
 
285
  }
286
  }
287
  });
288
  </script>
289
  """
290
 
 
291
  custom_css = """
292
  body, .gradio-container { background-color: #0F0F0F; color: #fff; }
293
  #booking_display { height: auto !important; max-height: none !important; overflow: visible !important; margin-bottom: 50px; }
294
  button:active { transform: scale(0.96); }
295
  #header-panel { background: #1a1a1a; padding: 15px; margin-bottom: 20px; border-radius: 10px; }
 
 
296
  .op-btn { pointer-events: auto !important; }
297
 
298
+ /* 暫時不隱藏,方便 Debug */
299
+ /* #debug_ops { display: none; } */
 
 
 
 
 
 
 
300
  """
301
 
 
302
  with gr.Blocks(title="Admin") as demo:
 
 
303
  gr.HTML(GLOBAL_JS)
304
 
305
  with gr.Group(visible=True) as login_row:
 
315
  gr.Markdown("### 🍷 Cié Cié Dashboard")
316
  refresh_btn = gr.Button("🔄 刷新列表", size="sm", variant="secondary")
317
 
318
+ # ⚠️ 這裡我把隱藏區改名為 debug_ops 並且顯示出來
319
+ # 你會看到一個數字框和一個按鈕,這是正常的
320
+ with gr.Row(visible=True, elem_id="debug_ops"):
321
+ debug_id_input = gr.Number(label="Debug ID Input (Auto Fill)", elem_id="debug_id_input", precision=0)
322
+ debug_send_btn = gr.Button("Debug Send Button (Auto Click)", elem_id="debug_send_btn")
323
+
324
  booking_display = gr.HTML(elem_id="booking_display")
325
+ log_output = gr.Textbox(label="系統日誌 (System Log)", lines=1)
 
 
 
 
 
 
326
 
327
  login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
328
  render_booking_cards, outputs=booking_display
329
  )
330
  refresh_btn.click(render_booking_cards, outputs=booking_display)
331
 
332
+ # 綁定
333
+ debug_send_btn.click(
334
  send_confirmation_hybrid,
335
+ inputs=debug_id_input,
336
  outputs=log_output
337
  ).then(
338
  render_booking_cards,