DeepLearning101 commited on
Commit
053a647
·
verified ·
1 Parent(s): ef50b1a

電話 (Phone):「寬鬆驗證」+ Email 檢查

Browse files
Files changed (1) hide show
  1. app.py +33 -39
app.py CHANGED
@@ -25,7 +25,6 @@ SPECIAL_DAYS = {
25
  # --- 3. 輔助函式 ---
26
  def get_date_options():
27
  options = []
28
- # 這裡的 TAIPEI_TZ 會在函式被呼叫時重新取得當下時間
29
  today = datetime.now(TAIPEI_TZ)
30
  weekdays = ["(一)", "(二)", "(三)", "(四)", "(五)", "(六)", "(日)"]
31
  for i in range(30):
@@ -43,31 +42,22 @@ def update_time_slots(date_str):
43
  weekday = date_obj.weekday()
44
  except: return gr.update(choices=[]), "日期格式錯誤"
45
 
46
- # 預設的所有時段
47
  slots = ["18:30", "19:00", "19:30", "20:00", "20:30",
48
  "21:00", "21:30", "22:00", "22:30", "23:00", "23:30", "00:00", "00:30", "01:00", "01:30"]
49
  if weekday == 4 or weekday == 5: slots.extend(["02:00", "02:30"])
50
 
51
- # 🔥🔥🔥 新增:時間過濾邏輯 🔥🔥🔥
52
  now = datetime.now(TAIPEI_TZ)
53
- # 只有當選擇的日期是「今天」時,才需要過濾過去的時間
54
  if date_obj.date() == now.date():
55
- current_time_str = now.strftime("%H:%M") # 取得現在時間字串 (例如 "22:15")
56
  valid_slots = []
57
  for s in slots:
58
- h = int(s.split(":")[0]) # 取得時段的小時數
59
-
60
- # 判斷邏輯:
61
- # 1. 凌晨時段 (00:00 - 05:00):這屬於「跨日」,相對於今天的晚餐時段來說是未來,所以保留。
62
- # 2. 晚間時段 (18:00+):必須比「現在時間」晚,才保留。
63
  if h < 5:
64
  valid_slots.append(s)
65
  elif s > current_time_str:
66
  valid_slots.append(s)
67
-
68
- slots = valid_slots # 更新清單
69
 
70
- # 計算剩餘座位 (維持原樣)
71
  clean_date = date_str.split(" ")[0]
72
  daily_limit = SPECIAL_DAYS.get(clean_date, DEFAULT_LIMIT)
73
 
@@ -78,26 +68,13 @@ def update_time_slots(date_str):
78
  status_msg = f"✨ {date_str} (剩餘座位: {remaining} 位)"
79
  except: status_msg = f"✨ {date_str}"
80
 
81
- # 如果所有時段都過期了 (slots 為空),value 給 None
82
  return gr.update(choices=slots, value=slots[0] if slots else None), status_msg
83
 
84
- # --- [新增] 初始化 UI 的函式 (解決日期過期問題) ---
85
  def init_ui():
86
- """
87
- 當網頁載入時執行:
88
- 1. 重新計算日期列表 (確保今天是真的今天)
89
- 2. 自動選擇第一天 (今天)
90
- 3. 根據今天,自動更新時段和剩餘座位 (此時會自動觸發上方的時間過濾)
91
- """
92
  fresh_dates = get_date_options()
93
  default_date = fresh_dates[0]
94
  time_update, status_msg = update_time_slots(default_date)
95
-
96
- return (
97
- gr.update(choices=fresh_dates, value=default_date),
98
- time_update,
99
- status_msg
100
- )
101
 
102
  # --- 4. 核心邏輯 (抓 ID 與 處理訂位) ---
103
  def get_line_id_from_url(request: gr.Request):
@@ -106,8 +83,25 @@ def get_line_id_from_url(request: gr.Request):
106
  return ""
107
 
108
  def handle_booking(name, tel, email, date_str, time, pax, remarks, line_id):
 
109
  if not name or not tel or not date_str or not time:
110
- return "⚠️ 請完整填寫必填欄位"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  clean_date = date_str.split(" ")[0]
113
  daily_limit = SPECIAL_DAYS.get(clean_date, DEFAULT_LIMIT)
@@ -132,6 +126,7 @@ def handle_booking(name, tel, email, date_str, time, pax, remarks, line_id):
132
  try:
133
  supabase.table("bookings").insert(data).execute()
134
 
 
135
  if LINE_ACCESS_TOKEN and LINE_ADMIN_ID:
136
  src = "🟢 LINE用戶" if line_id else "⚪ 訪客"
137
  note = remarks if remarks else "無"
@@ -157,18 +152,18 @@ def check_confirmation(request: gr.Request):
157
 
158
  OFFICIAL_SITE = "https://ciecietaipei.github.io/index.html"
159
 
160
- notify_msg = "" # 用來存要發給老闆的訊息
161
 
162
  if action == 'confirm' and bid:
163
  try:
164
- # 更新狀態
165
  supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute()
166
 
167
- # 🔥 抓資料,準備通知
168
  res = supabase.table("bookings").select("name, date, time, pax").eq("id", bid).execute()
169
  if res.data:
170
  b = res.data[0]
171
- notify_msg = f"🎉 客人已按確認!\n姓名:{b['name']}\n日期:{b['date']}\n時間:{b['time']}\n人數:{b['pax']}人"
172
 
173
  final_url = f"{OFFICIAL_SITE}?status=confirmed"
174
  except:
@@ -176,14 +171,14 @@ def check_confirmation(request: gr.Request):
176
 
177
  elif action == 'cancel' and bid:
178
  try:
179
- # 更新狀態
180
  supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute()
181
 
182
- # 🔥 抓資料,準備通知
183
  res = supabase.table("bookings").select("name, date, time").eq("id", bid).execute()
184
  if res.data:
185
  b = res.data[0]
186
- notify_msg = f"⚠️ 客人已取消...\n姓名:{b['name']}\n日期:{b['date']}\n時間:{b['time']}"
187
 
188
  final_url = f"{OFFICIAL_SITE}?status=canceled"
189
  except:
@@ -191,19 +186,19 @@ def check_confirmation(request: gr.Request):
191
  else:
192
  final_url = ""
193
 
194
- # 🔥🔥🔥 統一發送通知給老闆 (如果 notify_msg 有內容) 🔥🔥🔥
195
  if notify_msg and LINE_ACCESS_TOKEN and LINE_ADMIN_ID:
196
  try:
197
  requests.post(
198
  "https://api.line.me/v2/bot/message/push",
199
  headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}", "Content-Type": "application/json"},
200
  json={
201
- "to": LINE_ADMIN_ID,
202
  "messages": [{"type": "text", "text": notify_msg}]
203
  }
204
  )
205
  except Exception as e:
206
- print(f"LINE 通知發送失敗: {e}")
207
 
208
  return final_url
209
 
@@ -236,7 +231,6 @@ with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
236
  status_box = gr.Markdown("請先選擇日期...", visible=True)
237
  time_slot = gr.Dropdown(choices=[], label="可用時段 Available Time", interactive=True)
238
 
239
- # 載入時初始化 UI (包含日期與時間過濾)
240
  demo.load(init_ui, None, [booking_date, time_slot, status_box])
241
 
242
  gr.HTML("<div style='height: 10px'></div>")
 
25
  # --- 3. 輔助函式 ---
26
  def get_date_options():
27
  options = []
 
28
  today = datetime.now(TAIPEI_TZ)
29
  weekdays = ["(一)", "(二)", "(三)", "(四)", "(五)", "(六)", "(日)"]
30
  for i in range(30):
 
42
  weekday = date_obj.weekday()
43
  except: return gr.update(choices=[]), "日期格式錯誤"
44
 
 
45
  slots = ["18:30", "19:00", "19:30", "20:00", "20:30",
46
  "21:00", "21:30", "22:00", "22:30", "23:00", "23:30", "00:00", "00:30", "01:00", "01:30"]
47
  if weekday == 4 or weekday == 5: slots.extend(["02:00", "02:30"])
48
 
 
49
  now = datetime.now(TAIPEI_TZ)
 
50
  if date_obj.date() == now.date():
51
+ current_time_str = now.strftime("%H:%M")
52
  valid_slots = []
53
  for s in slots:
54
+ h = int(s.split(":")[0])
 
 
 
 
55
  if h < 5:
56
  valid_slots.append(s)
57
  elif s > current_time_str:
58
  valid_slots.append(s)
59
+ slots = valid_slots
 
60
 
 
61
  clean_date = date_str.split(" ")[0]
62
  daily_limit = SPECIAL_DAYS.get(clean_date, DEFAULT_LIMIT)
63
 
 
68
  status_msg = f"✨ {date_str} (剩餘座位: {remaining} 位)"
69
  except: status_msg = f"✨ {date_str}"
70
 
 
71
  return gr.update(choices=slots, value=slots[0] if slots else None), status_msg
72
 
 
73
  def init_ui():
 
 
 
 
 
 
74
  fresh_dates = get_date_options()
75
  default_date = fresh_dates[0]
76
  time_update, status_msg = update_time_slots(default_date)
77
+ return (gr.update(choices=fresh_dates, value=default_date), time_update, status_msg)
 
 
 
 
 
78
 
79
  # --- 4. 核心邏輯 (抓 ID 與 處理訂位) ---
80
  def get_line_id_from_url(request: gr.Request):
 
83
  return ""
84
 
85
  def handle_booking(name, tel, email, date_str, time, pax, remarks, line_id):
86
+ # 1. 基本必填檢查
87
  if not name or not tel or not date_str or not time:
88
+ return "⚠️ 請完整填寫必填欄位 (姓名、電話、日期、時間)"
89
+
90
+ # 🔥🔥🔥 [新增] 格式驗證邏輯 🔥🔥🔥
91
+
92
+ # 2. Email 驗證 (如果用戶有填寫 Email,就必須包含 @)
93
+ if email and "@" not in email:
94
+ return "⚠️ Email 格式錯誤 (請確認包含 @ 符號)"
95
+
96
+ # 3. 電話驗證 (寬鬆模式:相容外國人)
97
+ # 邏輯:不限制符號 (+, -, 空白, 括號都可),但「數字」的總數至少要有 6 碼
98
+ # 例如: "+886 912-345-678" (數字12碼) -> 通過
99
+ # 例如: "Test Phone" (數字0碼) -> 失敗
100
+ digit_count = sum(c.isdigit() for c in tel)
101
+ if digit_count < 6:
102
+ return "⚠️ 電話號碼格式錯誤 (請填寫有效的電話號碼)"
103
+
104
+ # 🔥🔥🔥 驗證結束 🔥🔥🔥
105
 
106
  clean_date = date_str.split(" ")[0]
107
  daily_limit = SPECIAL_DAYS.get(clean_date, DEFAULT_LIMIT)
 
126
  try:
127
  supabase.table("bookings").insert(data).execute()
128
 
129
+ # [LINE 通知老闆 - 新訂位]
130
  if LINE_ACCESS_TOKEN and LINE_ADMIN_ID:
131
  src = "🟢 LINE用戶" if line_id else "⚪ 訪客"
132
  note = remarks if remarks else "無"
 
152
 
153
  OFFICIAL_SITE = "https://ciecietaipei.github.io/index.html"
154
 
155
+ notify_msg = "" # 準備要發給老闆的訊息
156
 
157
  if action == 'confirm' and bid:
158
  try:
159
+ # 1. 更新資料庫
160
  supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute()
161
 
162
+ # 2. 🔥 抓訂位資料 (為了通知老闆是誰按了確認)
163
  res = supabase.table("bookings").select("name, date, time, pax").eq("id", bid).execute()
164
  if res.data:
165
  b = res.data[0]
166
+ notify_msg = f"🎉 顧客已確認出席!\n{b['date']} {b['time']}\n{b['name']} ({b['pax']}人)"
167
 
168
  final_url = f"{OFFICIAL_SITE}?status=confirmed"
169
  except:
 
171
 
172
  elif action == 'cancel' and bid:
173
  try:
174
+ # 1. 更新資料庫
175
  supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute()
176
 
177
+ # 2. 🔥 抓訂位資料
178
  res = supabase.table("bookings").select("name, date, time").eq("id", bid).execute()
179
  if res.data:
180
  b = res.data[0]
181
+ notify_msg = f"⚠️ 顧客已取消...\n{b['date']} {b['time']}\n{b['name']}"
182
 
183
  final_url = f"{OFFICIAL_SITE}?status=canceled"
184
  except:
 
186
  else:
187
  final_url = ""
188
 
189
+ # 🔥🔥🔥 3. 這裡執行發送通知給老闆 (使用 Messaging API) 🔥🔥🔥
190
  if notify_msg and LINE_ACCESS_TOKEN and LINE_ADMIN_ID:
191
  try:
192
  requests.post(
193
  "https://api.line.me/v2/bot/message/push",
194
  headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}", "Content-Type": "application/json"},
195
  json={
196
+ "to": LINE_ADMIN_ID, # 發給老闆
197
  "messages": [{"type": "text", "text": notify_msg}]
198
  }
199
  )
200
  except Exception as e:
201
+ print(f"通知老闆失敗: {e}")
202
 
203
  return final_url
204
 
 
231
  status_box = gr.Markdown("請先選擇日期...", visible=True)
232
  time_slot = gr.Dropdown(choices=[], label="可用時段 Available Time", interactive=True)
233
 
 
234
  demo.load(init_ui, None, [booking_date, time_slot, status_box])
235
 
236
  gr.HTML("<div style='height: 10px'></div>")