DeepLearning101 commited on
Commit
5a645b7
·
verified ·
1 Parent(s): 16c25fa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -90
app.py CHANGED
@@ -29,48 +29,52 @@ 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
- # 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,35 +82,35 @@ def send_confirmation_hybrid(booking_id):
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>
112
  <li>⏰ 時間:<strong style="color:#fff;">{booking['time']}</strong></li>
@@ -120,12 +124,12 @@ def send_confirmation_hybrid(booking_id):
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
@@ -134,17 +138,17 @@ def send_confirmation_hybrid(booking_id):
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
 
@@ -157,32 +161,28 @@ def render_booking_cards():
157
 
158
  for index, row in df.iterrows():
159
  status = row.get('status', '待處理')
160
- status_color = "#ccc"
161
- border_color = "#444"
162
-
163
- if '確認' in status:
164
- status_color = "#2ecc71"; border_color = "#2ecc71"
165
- elif '取消' in status:
166
- status_color = "#e74c3c"; border_color = "#e74c3c"
167
- elif '已發' in status:
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);"
@@ -190,28 +190,14 @@ def render_booking_cards():
190
 
191
  card = f"""
192
  <div class="booking-card" style="
193
- background: #1a1a1a;
194
- border-left: 8px solid {border_color};
195
- border-radius: 12px;
196
- padding: 24px;
197
- box-shadow: 0 8px 20px rgba(0,0,0,0.6);
198
- font-family: '微軟正黑體', sans-serif;
199
- position: relative;
200
- margin-bottom: 10px;">
201
 
202
  <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; border-bottom:1px solid #333; padding-bottom:15px;">
203
  <div style="font-size:1.3em; color:#fff; font-weight:bold;">
204
  <span style="font-size:0.8em; color:#aaa; margin-right:8px; font-weight:normal;">📅 日期</span>{row['date']}
205
  </div>
206
- <div style="
207
- color: {status_color};
208
- background: {status_color}22;
209
- padding: 8px 16px;
210
- border-radius: 30px;
211
- font-size: 1em;
212
- font-weight: bold;
213
- letter-spacing: 1px;
214
- border: 1px solid {status_color}44;">
215
  {status}
216
  </div>
217
  </div>
@@ -250,26 +236,13 @@ def render_booking_cards():
250
  </div>
251
 
252
  <div style="display:flex; justify-content:space-between; align-items:center; border-top:1px solid #333; padding-top:20px;">
253
- <div style="
254
- font-size: 1.1em;
255
- color: #000;
256
- font-weight: 900;
257
- background: #e0e0e0;
258
- padding: 8px 12px;
259
- border-radius: 6px;
260
- font-family: monospace;">
261
  ID: {row['id']}
262
  </div>
263
 
264
  <button onclick="{btn_onclick}" style="
265
- border: none;
266
- padding: 14px 30px;
267
- border-radius: 8px;
268
- font-size: 1.1em;
269
- transition: all 0.2s;
270
- min-width: 150px;
271
- cursor: pointer;
272
- {btn_style}">
273
  {btn_text}
274
  </button>
275
  </div>
@@ -289,31 +262,30 @@ def check_login(user, password):
289
  error_msg: ""
290
  }
291
  else:
292
- return {
293
- error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>"
294
- }
295
 
296
- # --- JS 邏輯 ---
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
 
303
- if (idInput) {
304
- idInput.value = id;
305
- idInput.dispatchEvent(new Event('input', { bubbles: true }));
306
- }
307
 
 
308
  setTimeout(() => {
309
  const sendBtn = document.querySelector('#hidden_send_btn');
310
  if (sendBtn) sendBtn.click();
311
- }, 150);
 
312
  }
313
  }
314
  """
315
 
316
- # --- CSS (修復元件隱藏問題) ---
317
  custom_css = """
318
  body, .gradio-container { background-color: #0F0F0F; color: #fff; }
319
 
@@ -333,8 +305,16 @@ button:active { transform: scale(0.96); }
333
  border-radius: 10px;
334
  }
335
 
 
 
336
  #hidden_ops {
337
- display: none !important;
 
 
 
 
 
 
338
  }
339
  """
340
 
@@ -356,7 +336,7 @@ with gr.Blocks(title="Admin") as demo:
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")
 
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]
40
 
41
  email = booking.get('email')
42
  user_id = booking.get('user_id')
43
+ current_status = booking.get('status', '')
44
 
45
+ # 連結
46
  confirm_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=confirm"
47
  cancel_link = f"{PUBLIC_SPACE_URL}/?id={booking_id}&action=cancel"
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"
68
  f"👥 人數:{booking['pax']} 位\n\n"
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>
76
+ <p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong> 您好,期待您的光臨!</p>
77
+ <div style="background:#2a2a2a; padding:15px; border-radius:8px; margin:20px 0; border-left:4px solid #f1c40f;">
78
  <ul style="color:#ddd; padding-left:20px; line-height:1.8;">
79
  <li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
80
  <li>⏰ 時間:<strong style="color:#fff;">{booking['time']}</strong></li>
 
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"
102
  f"📅 日期:{booking['date']}\n"
103
  f"⏰ 時間:{booking['time']}\n"
104
  f"👥 人數:{booking['pax']} 位\n\n"
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>
112
  <p style="color:#eee; font-size:16px;"><strong>{booking['name']}</strong> 您好,請確認您的訂位:</p>
113
+ <div style="background:#2a2a2a; padding:15px; border-radius:8px; margin:20px 0; border-left:4px solid #2ecc71;">
114
  <ul style="color:#ddd; padding-left:20px; line-height:1.8;">
115
  <li>📅 日期:<strong style="color:#fff;">{booking['date']}</strong></li>
116
  <li>⏰ 時間:<strong style="color:#fff;">{booking['time']}</strong></li>
 
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
 
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
 
 
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"
168
+ elif '已發' in status: status_color = "#f1c40f"; border_color = "#f1c40f"
 
 
 
 
169
 
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);"
 
190
 
191
  card = f"""
192
  <div class="booking-card" style="
193
+ background: #1a1a1a; border-left: 8px solid {border_color}; border-radius: 12px; padding: 24px;
194
+ box-shadow: 0 8px 20px rgba(0,0,0,0.6); font-family: '微軟正黑體', sans-serif; position: relative; margin-bottom: 10px;">
 
 
 
 
 
 
195
 
196
  <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; border-bottom:1px solid #333; padding-bottom:15px;">
197
  <div style="font-size:1.3em; color:#fff; font-weight:bold;">
198
  <span style="font-size:0.8em; color:#aaa; margin-right:8px; font-weight:normal;">📅 日期</span>{row['date']}
199
  </div>
200
+ <div style="color: {status_color}; background: {status_color}22; padding: 8px 16px; border-radius: 30px; font-size: 1em; font-weight: bold; border: 1px solid {status_color}44;">
 
 
 
 
 
 
 
 
201
  {status}
202
  </div>
203
  </div>
 
236
  </div>
237
 
238
  <div style="display:flex; justify-content:space-between; align-items:center; border-top:1px solid #333; padding-top:20px;">
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>
 
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
 
 
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
 
 
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")