Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -36,16 +36,38 @@ def get_date_options():
|
|
| 36 |
|
| 37 |
def update_time_slots(date_str):
|
| 38 |
if not date_str: return gr.update(choices=[]), "請先選擇日期"
|
|
|
|
| 39 |
try:
|
| 40 |
clean_date_str = date_str.split(" ")[0]
|
| 41 |
date_obj = datetime.strptime(clean_date_str, "%Y-%m-%d")
|
| 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 |
clean_date = date_str.split(" ")[0]
|
| 50 |
daily_limit = SPECIAL_DAYS.get(clean_date, DEFAULT_LIMIT)
|
| 51 |
|
|
@@ -56,6 +78,7 @@ def update_time_slots(date_str):
|
|
| 56 |
status_msg = f"✨ {date_str} (剩餘座位: {remaining} 位)"
|
| 57 |
except: status_msg = f"✨ {date_str}"
|
| 58 |
|
|
|
|
| 59 |
return gr.update(choices=slots, value=slots[0] if slots else None), status_msg
|
| 60 |
|
| 61 |
# --- [新增] 初始化 UI 的函式 (解決日期過期問題) ---
|
|
@@ -64,20 +87,16 @@ def init_ui():
|
|
| 64 |
當網頁載入時執行:
|
| 65 |
1. 重新計算日期列表 (確保今天是真的今天)
|
| 66 |
2. 自動選擇第一天 (今天)
|
| 67 |
-
3. 根據今天,自動更新時段和剩餘座位
|
| 68 |
"""
|
| 69 |
-
# 1. 取得最新的日期列表
|
| 70 |
fresh_dates = get_date_options()
|
| 71 |
default_date = fresh_dates[0]
|
| 72 |
-
|
| 73 |
-
# 2. 根據「今天」去計算時段 (呼叫既有的邏輯)
|
| 74 |
time_update, status_msg = update_time_slots(default_date)
|
| 75 |
|
| 76 |
-
# 3. 回傳三個元件的更新
|
| 77 |
return (
|
| 78 |
-
gr.update(choices=fresh_dates, value=default_date),
|
| 79 |
-
time_update,
|
| 80 |
-
status_msg
|
| 81 |
)
|
| 82 |
|
| 83 |
# --- 4. 核心邏輯 (抓 ID 與 處理訂位) ---
|
|
@@ -136,20 +155,17 @@ def check_confirmation(request: gr.Request):
|
|
| 136 |
action = request.query_params.get('action')
|
| 137 |
bid = request.query_params.get('id')
|
| 138 |
|
| 139 |
-
# 目標官網首頁
|
| 140 |
OFFICIAL_SITE = "https://ciecietaipei.github.io/index.html"
|
| 141 |
|
| 142 |
if action == 'confirm' and bid:
|
| 143 |
try:
|
| 144 |
supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute()
|
| 145 |
-
# 回傳目標網址
|
| 146 |
return f"{OFFICIAL_SITE}?status=confirmed"
|
| 147 |
except: pass
|
| 148 |
|
| 149 |
elif action == 'cancel' and bid:
|
| 150 |
try:
|
| 151 |
supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute()
|
| 152 |
-
# 回傳目標網址
|
| 153 |
return f"{OFFICIAL_SITE}?status=canceled"
|
| 154 |
except: pass
|
| 155 |
|
|
@@ -158,21 +174,15 @@ def check_confirmation(request: gr.Request):
|
|
| 158 |
# --- 6. 介面 ---
|
| 159 |
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")
|
| 160 |
|
| 161 |
-
# CSS 隱藏技巧
|
| 162 |
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; } #hidden_box { display: none !important; }"
|
| 163 |
|
| 164 |
with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
|
| 165 |
-
# 接收 LINE ID 的盒子 (CSS 隱藏)
|
| 166 |
line_id_box = gr.Textbox(visible=True, elem_id="hidden_box", label="LINE ID")
|
| 167 |
-
|
| 168 |
-
# 接收轉址 URL 的盒子 (隱藏)
|
| 169 |
redirect_url_box = gr.Textbox(visible=False)
|
| 170 |
|
| 171 |
-
# 載入時觸發
|
| 172 |
demo.load(get_line_id_from_url, None, line_id_box)
|
| 173 |
demo.load(check_confirmation, None, redirect_url_box)
|
| 174 |
|
| 175 |
-
# 關鍵:當 redirect_url_box 有值時,執行 JS 轉址
|
| 176 |
redirect_url_box.change(
|
| 177 |
fn=None,
|
| 178 |
inputs=redirect_url_box,
|
|
@@ -183,7 +193,6 @@ with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
|
|
| 183 |
with gr.Row():
|
| 184 |
with gr.Column():
|
| 185 |
gr.Markdown("### 📅 預約資訊 Booking Info")
|
| 186 |
-
# 這裡的 choices 初始值會被下方的 load 覆蓋,所以留空也沒關係,但為了邏輯正確我先拿掉 get_date_options() 的調用
|
| 187 |
booking_date = gr.Dropdown(label="選擇日期 Select Date", interactive=True)
|
| 188 |
pax_count = gr.Slider(minimum=1, maximum=10, value=2, step=1, label="用餐人數 Guest Count")
|
| 189 |
with gr.Column():
|
|
@@ -191,8 +200,7 @@ with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
|
|
| 191 |
status_box = gr.Markdown("請先選擇日期...", visible=True)
|
| 192 |
time_slot = gr.Dropdown(choices=[], label="可用時段 Available Time", interactive=True)
|
| 193 |
|
| 194 |
-
#
|
| 195 |
-
# 當頁面載入時,執行 init_ui,這會重新計算日期,並更新 1.日期選單 2.時段選單 3.狀態文字
|
| 196 |
demo.load(init_ui, None, [booking_date, time_slot, status_box])
|
| 197 |
|
| 198 |
gr.HTML("<div style='height: 10px'></div>")
|
|
|
|
| 36 |
|
| 37 |
def update_time_slots(date_str):
|
| 38 |
if not date_str: return gr.update(choices=[]), "請先選擇日期"
|
| 39 |
+
|
| 40 |
try:
|
| 41 |
clean_date_str = date_str.split(" ")[0]
|
| 42 |
date_obj = datetime.strptime(clean_date_str, "%Y-%m-%d")
|
| 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 |
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 的函式 (解決日期過期問題) ---
|
|
|
|
| 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 與 處理訂位) ---
|
|
|
|
| 155 |
action = request.query_params.get('action')
|
| 156 |
bid = request.query_params.get('id')
|
| 157 |
|
|
|
|
| 158 |
OFFICIAL_SITE = "https://ciecietaipei.github.io/index.html"
|
| 159 |
|
| 160 |
if action == 'confirm' and bid:
|
| 161 |
try:
|
| 162 |
supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute()
|
|
|
|
| 163 |
return f"{OFFICIAL_SITE}?status=confirmed"
|
| 164 |
except: pass
|
| 165 |
|
| 166 |
elif action == 'cancel' and bid:
|
| 167 |
try:
|
| 168 |
supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute()
|
|
|
|
| 169 |
return f"{OFFICIAL_SITE}?status=canceled"
|
| 170 |
except: pass
|
| 171 |
|
|
|
|
| 174 |
# --- 6. 介面 ---
|
| 175 |
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")
|
| 176 |
|
|
|
|
| 177 |
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; } #hidden_box { display: none !important; }"
|
| 178 |
|
| 179 |
with gr.Blocks(theme=theme, css=custom_css, title="Booking") as demo:
|
|
|
|
| 180 |
line_id_box = gr.Textbox(visible=True, elem_id="hidden_box", label="LINE ID")
|
|
|
|
|
|
|
| 181 |
redirect_url_box = gr.Textbox(visible=False)
|
| 182 |
|
|
|
|
| 183 |
demo.load(get_line_id_from_url, None, line_id_box)
|
| 184 |
demo.load(check_confirmation, None, redirect_url_box)
|
| 185 |
|
|
|
|
| 186 |
redirect_url_box.change(
|
| 187 |
fn=None,
|
| 188 |
inputs=redirect_url_box,
|
|
|
|
| 193 |
with gr.Row():
|
| 194 |
with gr.Column():
|
| 195 |
gr.Markdown("### 📅 預約資訊 Booking Info")
|
|
|
|
| 196 |
booking_date = gr.Dropdown(label="選擇日期 Select Date", interactive=True)
|
| 197 |
pax_count = gr.Slider(minimum=1, maximum=10, value=2, step=1, label="用餐人數 Guest Count")
|
| 198 |
with gr.Column():
|
|
|
|
| 200 |
status_box = gr.Markdown("請先選擇日期...", visible=True)
|
| 201 |
time_slot = gr.Dropdown(choices=[], label="可用時段 Available Time", interactive=True)
|
| 202 |
|
| 203 |
+
# 載入時初始化 UI (包含日期與時間過濾)
|
|
|
|
| 204 |
demo.load(init_ui, None, [booking_date, time_slot, status_box])
|
| 205 |
|
| 206 |
gr.HTML("<div style='height: 10px'></div>")
|