DeepLearning101 commited on
Commit
311693d
·
verified ·
1 Parent(s): 1147dda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -99
app.py CHANGED
@@ -4,10 +4,10 @@ import requests
4
  from supabase import create_client, Client
5
  from datetime import datetime, timedelta, timezone
6
 
7
- # 設定台北時區
 
8
  TAIPEI_TZ = timezone(timedelta(hours=8))
9
 
10
- # --- 1. 連線設定 ---
11
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
12
  LINE_ADMIN_ID = os.getenv("LINE_ADMIN_ID")
13
  SUPABASE_URL = os.getenv("SUPABASE_URL")
@@ -15,12 +15,18 @@ SUPABASE_KEY = os.getenv("SUPABASE_KEY")
15
 
16
  if SUPABASE_URL and SUPABASE_KEY:
17
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
18
- else:
19
- print("⚠️ 警告:SUPABASE 環境變數未設定")
20
 
21
- # --- 2. 輔助函式 (日期與時間) ---
 
 
 
 
 
 
 
22
  def get_date_options():
23
  options = []
 
24
  today = datetime.now(TAIPEI_TZ)
25
  weekdays = ["(一)", "(二)", "(三)", "(四)", "(五)", "(六)", "(日)"]
26
  for i in range(30):
@@ -38,120 +44,94 @@ def update_time_slots(date_str):
38
  except: return gr.update(choices=[]), "日期格式錯誤"
39
 
40
  slots = ["18:00", "18:30", "19:00", "19:30", "20:00", "20:30",
41
- "21:00", "21:30", "22:00", "22:30", "23:00", "23:30",
42
- "00:00", "00:30", "01:00"]
43
 
44
- if weekday == 4 or weekday == 5:
45
- slots.extend(["01:30", "02:00", "02:30"])
46
- status_msg = f"✨ 已選擇 {date_str} (週末營業至 03:00)"
47
- else:
48
- slots.extend(["01:30"])
49
- status_msg = f"🌙 已選擇 {date_str} (平日營業至 02:00)"
50
-
 
 
 
 
51
  return gr.update(choices=slots, value=slots[0] if slots else None), status_msg
52
 
53
- # --- [新功能] 抓取網址中的 LINE ID ---
54
  def get_line_id_from_url(request: gr.Request):
 
55
  if request:
56
  return request.query_params.get("line_id", "")
57
  return ""
58
 
59
- # --- 3. 核心邏輯:處理訂位 ---
60
- def handle_booking(name, tel, email, date_str, time, pax, remarks, line_id): # 新增 line_id
61
  if not name or not tel or not date_str or not time:
62
- return "⚠️ 請完整填寫必填欄位 (姓名、電話、日期、時間)"
63
-
64
- # 防呆機制:檢查是否重複提交
 
 
 
 
 
 
 
 
 
 
65
  try:
66
- existing = supabase.table("bookings").select("id")\
67
- .eq("tel", tel).eq("date", date_str).eq("time", time)\
68
- .neq("status", "顧客已取消").execute()
69
- if existing.data and len(existing.data) > 0:
70
- return "⚠️ 系統偵測到您已預約過此時段,請勿重複提交。"
71
  except: pass
72
 
73
- # 準備資料 (新增 user_id)
74
  data = {
75
  "name": name, "tel": tel, "email": email, "date": date_str, "time": time,
76
- "pax": pax, "remarks": remarks, "status": "待處理",
77
- "user_id": line_id # 存入 LINE ID
78
  }
79
 
80
  try:
81
- # 寫入資料庫
82
  supabase.table("bookings").insert(data).execute()
83
 
84
- # 發送 LINE Notify 給老闆 (顯示來源)
85
  if LINE_ACCESS_TOKEN and LINE_ADMIN_ID:
86
- source = "🟢 LINE用戶" if line_id else "⚪ 一般訪客"
87
- msg = f"🔥 新訂位 ({source})\n姓名:{name}\n電話:{tel}\n時間:{date_str} {time}\n人數:{pax}"
88
- requests.post("https://api.line.me/v2/bot/message/push",
89
- headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}", "Content-Type": "application/json"},
90
- json={"to": LINE_ADMIN_ID, "messages": [{"type": "text", "text": msg}]})
91
 
92
- return """
93
- <div style='text-align: center; color: #fff; padding: 20px; border: 1px solid #d4af37; border-radius: 8px; background: #222;'>
94
- <h2 style='color: #d4af37; margin: 0;'>Request Received</h2>
95
- <p style='margin: 10px 0;'>🥂 預約申請已提交</p>
96
- <p style='font-size: 0.9em; color: #aaa;'>系統將發送確認信至您的 Email。</p>
97
- <p style='font-size: 0.85em; color: #ff5252; margin-top: 5px;'>(若未收到,請檢查您的<b>垃圾信件匣</b>)</p>
98
- </div>
99
- """
100
- except Exception as e:
101
- print(f"Error: {e}")
102
- return f"❌ 系統錯誤: {str(e)}"
103
-
104
- # --- 4. 核心邏輯:處理確認連結 (Webhook) ---
105
  def check_confirmation(request: gr.Request):
106
  if not request: return ""
107
- params = request.query_params
108
- action = params.get('action')
109
- bid = params.get('id')
110
 
111
  if action == 'confirm' and bid:
112
  try:
113
  supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute()
114
- return f"""<script>document.addEventListener('DOMContentLoaded', function() {{alert('✅ 感謝您!訂位已確認 (編號 {bid})');}});</script><div style='padding:20px; background:#d4af37; color:black; text-align:center; margin-bottom:20px; border-radius:8px; font-weight:bold;'>🎉 感謝您的確認!我們期待您的光臨。 (訂單編號: {bid})</div>"""
115
  except: return ""
116
-
117
  elif action == 'cancel' and bid:
118
  try:
119
  supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute()
120
- return f"""<script>document.addEventListener('DOMContentLoaded', function() {{alert('已為您取消訂位 (編號 {bid})');}});</script><div style='padding:20px; background:#333; border: 1px solid #ff5252; color:#ff5252; text-align:center; margin-bottom:20px; border-radius:8px;'>🚫 訂位已取消。<br>期待您下次有機會再來訪。</div>"""
121
  except: return ""
122
  return ""
123
 
124
- # --- 5. 介面設定 (保留您的樣式) ---
125
- theme = gr.themes.Soft(
126
- primary_hue="amber", neutral_hue="zinc",
127
- font=[gr.themes.GoogleFont("Playfair Display"), "ui-sans-serif", "sans-serif"],
128
- ).set(
129
- body_background_fill="#0F0F0F", block_background_fill="#1a1a1a", block_border_width="1px", block_border_color="#333",
130
- input_background_fill="#262626", input_border_color="#444", body_text_color="#E0E0E0", block_title_text_color="#d4af37",
131
- button_primary_background_fill="#d4af37", button_primary_text_color="#000000",
132
- )
133
-
134
- custom_css = """
135
- footer {display: none !important;}
136
- .gradio-container, .block, .row, .column { overflow: visible !important; }
137
- .options, .wrap .options { background-color: #262626 !important; border: 1px solid #d4af37 !important; z-index: 10000 !important; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
138
- .item, .options .item { color: #E0E0E0 !important; padding: 8px 12px !important; }
139
- .item:hover, .item.selected, .options .item:hover { background-color: #d4af37 !important; color: black !important; }
140
- input:focus, .dropdown-trigger:focus-within { border-color: #d4af37 !important; box-shadow: 0 0 8px rgba(212, 175, 55, 0.4) !important; }
141
- h3 { border-bottom: 1px solid #444; padding-bottom: 5px; margin-bottom: 10px; }
142
- .legal-footer { text-align: center; margin-top: 15px; padding-top: 15px; border-top: 1px solid #333; color: #666; font-size: 0.75rem; line-height: 1.5; font-family: sans-serif; }
143
- .legal-footer strong { color: #888; }
144
- """
145
-
146
- # --- 6. 介面佈局 ---
147
  with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
148
-
149
- # [新功能] 隱藏欄位接收 LINE ID
150
- line_id_state = gr.Textbox(visible=False)
151
  confirm_msg_box = gr.HTML()
152
-
153
- # 載入時執行 JS 抓取參數
154
- demo.load(get_line_id_from_url, None, line_id_state)
155
  demo.load(check_confirmation, None, confirm_msg_box)
156
 
157
  with gr.Row():
@@ -163,7 +143,7 @@ with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
163
  gr.Markdown("### 🕰️ 選擇時段 Time Slot")
164
  status_box = gr.Markdown("請先選擇日期...", visible=True)
165
  time_slot = gr.Dropdown(choices=[], label="可用時段 Available Time", interactive=True)
166
-
167
  gr.HTML("<div style='height: 10px'></div>")
168
  gr.Markdown("### 👤 聯絡人資料 Contact,收到確認 E-Mail 並點擊 確認出席 才算訂位成功")
169
  with gr.Group():
@@ -178,23 +158,10 @@ with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
178
  gr.HTML("<div style='height: 15px'></div>")
179
  submit_btn = gr.Button("確認預約 Request Booking (系統會記錄是否曾 No Show)", size="lg", variant="primary")
180
  output_msg = gr.HTML()
181
-
182
- gr.HTML("""
183
- <div class="legal-footer">
184
- <p style="margin-bottom: 5px;">© 2026 CIE CIE TAIPEI. All Rights Reserved.</p>
185
- <p>內容涉及酒類產品訊息,請勿轉發分享給未達法定購買年齡者;未滿十八歲請勿飲酒。<br>
186
- <strong>喝酒不開車,開車不喝酒。</strong></p>
187
- </div>
188
- """)
189
 
190
  booking_date.change(update_time_slots, inputs=booking_date, outputs=[time_slot, status_box])
191
-
192
- # [關鍵] 送出時把 line_id_state 也傳進去
193
- submit_btn.click(
194
- handle_booking,
195
- inputs=[cust_name, cust_tel, cust_email, booking_date, time_slot, pax_count, cust_remarks, line_id_state],
196
- outputs=output_msg
197
- )
198
 
199
  if __name__ == "__main__":
200
  demo.launch()
 
4
  from supabase import create_client, Client
5
  from datetime import datetime, timedelta, timezone
6
 
7
+ # --- 1. 設定與初始化 ---
8
+ # ✅ 補回:設定台北時區 (UTC+8)
9
  TAIPEI_TZ = timezone(timedelta(hours=8))
10
 
 
11
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
12
  LINE_ADMIN_ID = os.getenv("LINE_ADMIN_ID")
13
  SUPABASE_URL = os.getenv("SUPABASE_URL")
 
15
 
16
  if SUPABASE_URL and SUPABASE_KEY:
17
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
 
 
18
 
19
+ # --- 2. 座位控管設定 (老闆可在此修改) ---
20
+ DEFAULT_LIMIT = 30
21
+ SPECIAL_DAYS = {
22
+ "2026-12-31": 10, # 跨年夜
23
+ "2026-02-14": 15 # 情人節
24
+ }
25
+
26
+ # --- 3. 輔助函式 ---
27
  def get_date_options():
28
  options = []
29
+ # ✅ 修正:使用台北時間
30
  today = datetime.now(TAIPEI_TZ)
31
  weekdays = ["(一)", "(二)", "(三)", "(四)", "(五)", "(六)", "(日)"]
32
  for i in range(30):
 
44
  except: return gr.update(choices=[]), "日期格式錯誤"
45
 
46
  slots = ["18:00", "18:30", "19:00", "19:30", "20:00", "20:30",
47
+ "21:00", "21:30", "22:00", "22:30", "23:00", "23:30", "00:00", "00:30", "01:00"]
48
+ if weekday == 4 or weekday == 5: slots.extend(["01:30", "02:00", "02:30"])
49
 
50
+ # 檢查剩餘座位
51
+ clean_date = date_str.split(" ")[0]
52
+ daily_limit = SPECIAL_DAYS.get(clean_date, DEFAULT_LIMIT)
53
+
54
+ try:
55
+ res = supabase.table("bookings").select("pax").eq("date", date_str).neq("status", "顧客已取消").execute()
56
+ current_total = sum([item['pax'] for item in res.data])
57
+ remaining = daily_limit - current_total
58
+ status_msg = f"✨ {date_str} (剩餘座位: {remaining} 位)"
59
+ except: status_msg = f"✨ {date_str}"
60
+
61
  return gr.update(choices=slots, value=slots[0] if slots else None), status_msg
62
 
63
+ # --- 4. 核心邏輯 ---
64
  def get_line_id_from_url(request: gr.Request):
65
+ """從網址參數讀取 line_id"""
66
  if request:
67
  return request.query_params.get("line_id", "")
68
  return ""
69
 
70
+ def handle_booking(name, tel, email, date_str, time, pax, remarks, line_id):
 
71
  if not name or not tel or not date_str or not time:
72
+ return "⚠️ 請完整填寫必填欄位"
73
+
74
+ # A. 檢查座位上限
75
+ clean_date = date_str.split(" ")[0]
76
+ daily_limit = SPECIAL_DAYS.get(clean_date, DEFAULT_LIMIT)
77
+ try:
78
+ res = supabase.table("bookings").select("pax").eq("date", date_str).neq("status", "顧客已取消").execute()
79
+ current_total = sum([item['pax'] for item in res.data])
80
+ if current_total + pax > daily_limit:
81
+ return "⚠️ 抱歉,該時段剩餘座位不足,請調整人數或日期。"
82
+ except: pass
83
+
84
+ # B. 防重複提交
85
  try:
86
+ existing = supabase.table("bookings").select("id").eq("tel", tel).eq("date", date_str).eq("time", time).neq("status", "顧客已取消").execute()
87
+ if existing.data: return "⚠️ 您已預約過此時段,請勿重複提交。"
 
 
 
88
  except: pass
89
 
90
+ # C. 寫入資料庫
91
  data = {
92
  "name": name, "tel": tel, "email": email, "date": date_str, "time": time,
93
+ "pax": pax, "remarks": remarks, "status": "待處理",
94
+ "user_id": line_id # 存入 LINE ID
95
  }
96
 
97
  try:
 
98
  supabase.table("bookings").insert(data).execute()
99
 
100
+ # D. 發送 LINE Notify 給老闆
101
  if LINE_ACCESS_TOKEN and LINE_ADMIN_ID:
102
+ src = "🟢 LINE用戶" if line_id else "⚪ 訪客"
103
+ msg = f"🔥 新訂位 ({src})\n{name} / {tel}\n{date_str} {time} ({pax}人)"
104
+ requests.post("https://api.line.me/v2/bot/message/push", headers={"Authorization": f"Bearer {LINE_ACCESS_TOKEN}", "Content-Type": "application/json"}, json={"to": LINE_ADMIN_ID, "messages": [{"type": "text", "text": msg}]})
 
 
105
 
106
+ return """<div style='text-align: center; color: #fff; padding: 20px; border: 1px solid #d4af37; border-radius: 8px; background: #222;'><h2 style='color: #d4af37; margin: 0;'>Request Received</h2><p style='margin: 10px 0;'>🥂 預約申請已提交</p><p style='font-size: 0.9em; color: #aaa;'>請留意 Email 確認信。</p></div>"""
107
+ except Exception as e: return f"❌ 系統錯誤: {str(e)}"
108
+
109
+ # --- 5. Webhook (確認/取消) ---
 
 
 
 
 
 
 
 
 
110
  def check_confirmation(request: gr.Request):
111
  if not request: return ""
112
+ action = request.query_params.get('action')
113
+ bid = request.query_params.get('id')
 
114
 
115
  if action == 'confirm' and bid:
116
  try:
117
  supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute()
118
+ return f"""<script>alert('✅ 訂位已確認 (編號 {bid})');</script><div style='padding:20px; background:#d4af37; color:black; text-align:center; border-radius:8px;'>🎉 感謝確認!</div>"""
119
  except: return ""
 
120
  elif action == 'cancel' and bid:
121
  try:
122
  supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute()
123
+ return f"""<script>alert('已取消訂位 (編號 {bid})');</script><div style='padding:20px; background:#333; color:#ff5252; text-align:center; border:1px solid #ff5252; border-radius:8px;'>🚫 訂位已取消。</div>"""
124
  except: return ""
125
  return ""
126
 
127
+ # --- 6. 介面 ---
128
+ theme = gr.themes.Soft(primary_hue="amber", neutral_hue="zinc").set(body_background_fill="#0F0F0F", block_background_fill="#1a1a1a", block_border_width="1px", block_border_color="#333", input_background_fill="#262626", input_border_color="#444", body_text_color="#E0E0E0", block_title_text_color="#d4af37", button_primary_background_fill="#d4af37", button_primary_text_color="#000000")
129
+ custom_css = "footer {display: none !important;} .gradio-container, .block, .row, .column { overflow: visible !important; } .options, .wrap .options { background-color: #262626 !important; border: 1px solid #d4af37 !important; z-index: 10000 !important; box-shadow: 0 5px 15px rgba(0,0,0,0.5); } .item:hover, .options .item:hover { background-color: #d4af37 !important; color: black !important; } .legal-footer { text-align: center; margin-top: 15px; padding-top: 15px; border-top: 1px solid #333; color: #666; font-size: 0.75rem; }"
130
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
132
+ line_id_box = gr.Textbox(visible=False)
 
 
133
  confirm_msg_box = gr.HTML()
134
+ demo.load(get_line_id_from_url, None, line_id_box)
 
 
135
  demo.load(check_confirmation, None, confirm_msg_box)
136
 
137
  with gr.Row():
 
143
  gr.Markdown("### 🕰️ 選擇時段 Time Slot")
144
  status_box = gr.Markdown("請先選擇日期...", visible=True)
145
  time_slot = gr.Dropdown(choices=[], label="可用時段 Available Time", interactive=True)
146
+
147
  gr.HTML("<div style='height: 10px'></div>")
148
  gr.Markdown("### 👤 聯絡人資料 Contact,收到確認 E-Mail 並點擊 確認出席 才算訂位成功")
149
  with gr.Group():
 
158
  gr.HTML("<div style='height: 15px'></div>")
159
  submit_btn = gr.Button("確認預約 Request Booking (系統會記錄是否曾 No Show)", size="lg", variant="primary")
160
  output_msg = gr.HTML()
161
+ gr.HTML("""<div class="legal-footer"><p style="margin-bottom: 5px;">© 2026 CIE CIE TAIPEI. All Rights Reserved.</p><p>內容涉及酒類產品訊息,請勿轉發分享給未達法定購買年齡者;未滿十八歲請勿飲酒。<br><strong>喝酒不開車,開車不喝酒。</strong></p></div>""")
 
 
 
 
 
 
 
162
 
163
  booking_date.change(update_time_slots, inputs=booking_date, outputs=[time_slot, status_box])
164
+ submit_btn.click(handle_booking, inputs=[cust_name, cust_tel, cust_email, booking_date, time_slot, pax_count, cust_remarks, line_id_box], outputs=output_msg)
 
 
 
 
 
 
165
 
166
  if __name__ == "__main__":
167
  demo.launch()