Update app.py
Browse files
app.py
CHANGED
|
@@ -305,7 +305,7 @@ def get_menu_items():
|
|
| 305 |
try:
|
| 306 |
res = supabase.table("menu_items").select("*").order("category").order("created_at").execute()
|
| 307 |
if not res.data:
|
| 308 |
-
return pd.DataFrame(columns=['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結'])
|
| 309 |
|
| 310 |
df = pd.DataFrame(res.data)
|
| 311 |
|
|
@@ -314,13 +314,15 @@ def get_menu_items():
|
|
| 314 |
df['require_prepay'] = df['require_prepay'].apply(lambda x: "🔥 需預付" if x else "一般")
|
| 315 |
df['is_active'] = df['is_active'].apply(lambda x: "🟢 販售中" if x else "🔴 已下架")
|
| 316 |
df['has_image'] = df.get('image_url', pd.Series()).apply(lambda x: "🔗 有" if pd.notnull(x) and str(x).strip() else "無")
|
|
|
|
|
|
|
| 317 |
|
| 318 |
-
display_df = df[['name', 'price', 'category', 'available_times', 'allow_takeout', 'require_prepay', 'is_active', 'has_image']]
|
| 319 |
-
display_df.columns = ['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結']
|
| 320 |
return display_df
|
| 321 |
except Exception as e:
|
| 322 |
print("Fetch menu error:", e)
|
| 323 |
-
return pd.DataFrame(columns=['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結'])
|
| 324 |
|
| 325 |
def update_menu_dropdown():
|
| 326 |
try:
|
|
@@ -330,15 +332,15 @@ def update_menu_dropdown():
|
|
| 330 |
return gr.update(choices=choices)
|
| 331 |
except: return gr.update(choices=[])
|
| 332 |
|
| 333 |
-
# ---
|
| 334 |
def load_menu_data(selected_string):
|
| 335 |
if not selected_string:
|
| 336 |
-
return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
|
| 337 |
|
| 338 |
item_id = selected_string.split(" | ")[-1].strip()
|
| 339 |
try:
|
| 340 |
res = supabase.table("menu_items").select("*").eq("id", item_id).execute()
|
| 341 |
-
if not res.data: return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
|
| 342 |
|
| 343 |
item = res.data[0]
|
| 344 |
times = item.get("available_times", [])
|
|
@@ -348,6 +350,7 @@ def load_menu_data(selected_string):
|
|
| 348 |
item.get("name", ""),
|
| 349 |
item.get("description", ""),
|
| 350 |
item.get("price", 0),
|
|
|
|
| 351 |
item.get("category", "main"),
|
| 352 |
times,
|
| 353 |
item.get("require_prepay", False),
|
|
@@ -356,35 +359,35 @@ def load_menu_data(selected_string):
|
|
| 356 |
item_id # 將 ID 存入隱藏狀態中
|
| 357 |
)
|
| 358 |
except:
|
| 359 |
-
return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
|
| 360 |
|
| 361 |
-
def add_menu_item(name, desc, price, category, available_times, require_prepay, allow_takeout, image_url_input):
|
| 362 |
-
# 🌟 修改這裡:把 not price 改成 price is None,這樣 0 元就能順利過關!
|
| 363 |
if not name or price is None: return "⚠️ 名稱與價格為必填", get_menu_items()
|
| 364 |
if not available_times: return "⚠️ 請至少選擇一個供應時段", get_menu_items()
|
| 365 |
final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None
|
| 366 |
|
| 367 |
try:
|
| 368 |
data = {
|
| 369 |
-
"name": name, "description": desc, "price": int(price),
|
| 370 |
-
"
|
|
|
|
| 371 |
"require_prepay": require_prepay, "is_active": True, "image_url": final_image_url
|
| 372 |
}
|
| 373 |
supabase.table("menu_items").insert(data).execute()
|
| 374 |
return f"✅ 成功新增上架:{name}", get_menu_items()
|
| 375 |
except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items()
|
| 376 |
|
| 377 |
-
# ---
|
| 378 |
-
def update_menu_item(item_id, name, desc, price, category, available_times, require_prepay, allow_takeout, image_url_input):
|
| 379 |
if not item_id: return "⚠️ 請先從右側選擇並「載入編輯」一項餐點", get_menu_items()
|
| 380 |
-
# 🌟 修改這裡:同樣把 not price 改成 price is None
|
| 381 |
if not name or price is None: return "⚠️ 名稱與價格為必填", get_menu_items()
|
| 382 |
final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None
|
| 383 |
|
| 384 |
try:
|
| 385 |
data = {
|
| 386 |
-
"name": name, "description": desc, "price": int(price),
|
| 387 |
-
"
|
|
|
|
| 388 |
"require_prepay": require_prepay, "image_url": final_image_url
|
| 389 |
}
|
| 390 |
supabase.table("menu_items").update(data).eq("id", item_id).execute()
|
|
@@ -436,7 +439,6 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
|
|
| 436 |
with gr.Column(elem_id="op-panel"):
|
| 437 |
gr.Markdown("### 🚀 訂單操作控制台")
|
| 438 |
|
| 439 |
-
# 第一排:ID 輸入與狀態變更選單
|
| 440 |
with gr.Row():
|
| 441 |
id_input = gr.Number(label="輸入訂單 ID", precision=0, scale=1)
|
| 442 |
new_status_dropdown = gr.Dropdown(
|
|
@@ -445,11 +447,10 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
|
|
| 445 |
scale=1
|
| 446 |
)
|
| 447 |
|
| 448 |
-
# 第二排:各式操作按鈕
|
| 449 |
with gr.Row():
|
| 450 |
send_btn = gr.Button("🚀 發信/LINE", variant="primary", scale=1)
|
| 451 |
update_status_btn = gr.Button("💾 變更狀態", variant="secondary", scale=1)
|
| 452 |
-
check_lp_btn = gr.Button("🔍 查 LINE Pay", variant="secondary", scale=1)
|
| 453 |
noshow_btn = gr.Button("🚫 標記 No-Show", variant="stop", scale=1)
|
| 454 |
revert_noshow_btn = gr.Button("✅ 撤銷 No-Show", scale=1)
|
| 455 |
refresh_btn = gr.Button("🔄 刷新列表", scale=1)
|
|
@@ -458,16 +459,8 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
|
|
| 458 |
|
| 459 |
booking_display = gr.HTML(elem_id="booking_display")
|
| 460 |
|
| 461 |
-
|
| 462 |
-
update_status_btn.click(
|
| 463 |
-
update_booking_status,
|
| 464 |
-
inputs=[id_input, new_status_dropdown],
|
| 465 |
-
outputs=log_output
|
| 466 |
-
).then(render_booking_cards, outputs=booking_display)
|
| 467 |
-
|
| 468 |
check_lp_btn.click(check_linepay_status, inputs=[id_input], outputs=log_output)
|
| 469 |
-
|
| 470 |
-
# 其餘按鈕事件
|
| 471 |
refresh_btn.click(render_booking_cards, outputs=booking_display)
|
| 472 |
send_btn.click(send_confirmation_hybrid, inputs=id_input, outputs=log_output).then(render_booking_cards, outputs=booking_display)
|
| 473 |
noshow_btn.click(toggle_no_show, inputs=[id_input, gr.State(True)], outputs=log_output).then(render_booking_cards, outputs=booking_display)
|
|
@@ -485,7 +478,12 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
|
|
| 485 |
with gr.Column(scale=1):
|
| 486 |
m_name = gr.Textbox(label="餐點名稱 *")
|
| 487 |
m_desc = gr.Textbox(label="餐點描述 (選填)")
|
| 488 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
m_cat = gr.Dropdown(choices=["main", "snack", "drink", "other"], label="分類", value="main")
|
| 490 |
m_image_url = gr.Textbox(label="餐點照片網址 (選填)", placeholder="例如: https://ciecietaipei.github.io/assets/steak.jpg")
|
| 491 |
m_times = gr.CheckboxGroup(
|
|
@@ -516,27 +514,25 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
|
|
| 516 |
m_set_inactive = gr.Button("🔴 下架", scale=1)
|
| 517 |
m_toggle_log = gr.Textbox(label="操作狀態", interactive=False)
|
| 518 |
|
| 519 |
-
#
|
| 520 |
m_load_btn.click(
|
| 521 |
load_menu_data,
|
| 522 |
inputs=[m_toggle_dropdown],
|
| 523 |
-
outputs=[m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url, m_edit_id]
|
| 524 |
).then(lambda: "✅ 已載入至左側表單,修改後請點擊「儲存修改」", outputs=m_toggle_log)
|
| 525 |
|
| 526 |
-
# 事件綁定:新增 / 儲存修改
|
| 527 |
m_add_btn.click(
|
| 528 |
add_menu_item,
|
| 529 |
-
inputs=[m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url],
|
| 530 |
outputs=[m_form_log, menu_df]
|
| 531 |
).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 532 |
|
| 533 |
m_update_btn.click(
|
| 534 |
update_menu_item,
|
| 535 |
-
inputs=[m_edit_id, m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url],
|
| 536 |
outputs=[m_form_log, menu_df]
|
| 537 |
).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 538 |
|
| 539 |
-
# 事件綁定:刷新與上下架
|
| 540 |
m_refresh_btn.click(get_menu_items, outputs=menu_df).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 541 |
m_set_active.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(True)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 542 |
m_set_inactive.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(False)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
|
|
|
| 305 |
try:
|
| 306 |
res = supabase.table("menu_items").select("*").order("category").order("created_at").execute()
|
| 307 |
if not res.data:
|
| 308 |
+
return pd.DataFrame(columns=['餐點名稱', '價格', '限量', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結'])
|
| 309 |
|
| 310 |
df = pd.DataFrame(res.data)
|
| 311 |
|
|
|
|
| 314 |
df['require_prepay'] = df['require_prepay'].apply(lambda x: "🔥 需預付" if x else "一般")
|
| 315 |
df['is_active'] = df['is_active'].apply(lambda x: "🟢 販售中" if x else "🔴 已下架")
|
| 316 |
df['has_image'] = df.get('image_url', pd.Series()).apply(lambda x: "🔗 有" if pd.notnull(x) and str(x).strip() else "無")
|
| 317 |
+
# 顯示限量 (999 顯示為無限制)
|
| 318 |
+
df['daily_limit_display'] = df.get('daily_limit', pd.Series(999)).apply(lambda x: "無限制" if x >= 999 else f"剩 {x} 份")
|
| 319 |
|
| 320 |
+
display_df = df[['name', 'price', 'daily_limit_display', 'category', 'available_times', 'allow_takeout', 'require_prepay', 'is_active', 'has_image']]
|
| 321 |
+
display_df.columns = ['餐點名稱', '價格', '限量', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結']
|
| 322 |
return display_df
|
| 323 |
except Exception as e:
|
| 324 |
print("Fetch menu error:", e)
|
| 325 |
+
return pd.DataFrame(columns=['餐點名稱', '價格', '限量', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結'])
|
| 326 |
|
| 327 |
def update_menu_dropdown():
|
| 328 |
try:
|
|
|
|
| 332 |
return gr.update(choices=choices)
|
| 333 |
except: return gr.update(choices=[])
|
| 334 |
|
| 335 |
+
# --- 將選擇的餐點資料載入到左側表單 (加入限量讀取) ---
|
| 336 |
def load_menu_data(selected_string):
|
| 337 |
if not selected_string:
|
| 338 |
+
return "", "", 0, 999, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
|
| 339 |
|
| 340 |
item_id = selected_string.split(" | ")[-1].strip()
|
| 341 |
try:
|
| 342 |
res = supabase.table("menu_items").select("*").eq("id", item_id).execute()
|
| 343 |
+
if not res.data: return "", "", 0, 999, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
|
| 344 |
|
| 345 |
item = res.data[0]
|
| 346 |
times = item.get("available_times", [])
|
|
|
|
| 350 |
item.get("name", ""),
|
| 351 |
item.get("description", ""),
|
| 352 |
item.get("price", 0),
|
| 353 |
+
item.get("daily_limit", 999), # 🌟 讀取限量,預設 999
|
| 354 |
item.get("category", "main"),
|
| 355 |
times,
|
| 356 |
item.get("require_prepay", False),
|
|
|
|
| 359 |
item_id # 將 ID 存入隱藏狀態中
|
| 360 |
)
|
| 361 |
except:
|
| 362 |
+
return "", "", 0, 999, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
|
| 363 |
|
| 364 |
+
def add_menu_item(name, desc, price, limit, category, available_times, require_prepay, allow_takeout, image_url_input):
|
|
|
|
| 365 |
if not name or price is None: return "⚠️ 名稱與價格為必填", get_menu_items()
|
| 366 |
if not available_times: return "⚠️ 請至少選擇一個供應時段", get_menu_items()
|
| 367 |
final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None
|
| 368 |
|
| 369 |
try:
|
| 370 |
data = {
|
| 371 |
+
"name": name, "description": desc, "price": int(price),
|
| 372 |
+
"daily_limit": int(limit) if limit else 999, # 🌟 儲存限量
|
| 373 |
+
"category": category, "available_times": available_times, "allow_takeout": allow_takeout,
|
| 374 |
"require_prepay": require_prepay, "is_active": True, "image_url": final_image_url
|
| 375 |
}
|
| 376 |
supabase.table("menu_items").insert(data).execute()
|
| 377 |
return f"✅ 成功新增上架:{name}", get_menu_items()
|
| 378 |
except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items()
|
| 379 |
|
| 380 |
+
# --- 儲存修改邏輯 ---
|
| 381 |
+
def update_menu_item(item_id, name, desc, price, limit, category, available_times, require_prepay, allow_takeout, image_url_input):
|
| 382 |
if not item_id: return "⚠️ 請先從右側選擇並「載入編輯」一項餐點", get_menu_items()
|
|
|
|
| 383 |
if not name or price is None: return "⚠️ 名稱與價格為必填", get_menu_items()
|
| 384 |
final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None
|
| 385 |
|
| 386 |
try:
|
| 387 |
data = {
|
| 388 |
+
"name": name, "description": desc, "price": int(price),
|
| 389 |
+
"daily_limit": int(limit) if limit else 999, # 🌟 儲存限量修改
|
| 390 |
+
"category": category, "available_times": available_times, "allow_takeout": allow_takeout,
|
| 391 |
"require_prepay": require_prepay, "image_url": final_image_url
|
| 392 |
}
|
| 393 |
supabase.table("menu_items").update(data).eq("id", item_id).execute()
|
|
|
|
| 439 |
with gr.Column(elem_id="op-panel"):
|
| 440 |
gr.Markdown("### 🚀 訂單操作控制台")
|
| 441 |
|
|
|
|
| 442 |
with gr.Row():
|
| 443 |
id_input = gr.Number(label="輸入訂單 ID", precision=0, scale=1)
|
| 444 |
new_status_dropdown = gr.Dropdown(
|
|
|
|
| 447 |
scale=1
|
| 448 |
)
|
| 449 |
|
|
|
|
| 450 |
with gr.Row():
|
| 451 |
send_btn = gr.Button("🚀 發信/LINE", variant="primary", scale=1)
|
| 452 |
update_status_btn = gr.Button("💾 變更狀態", variant="secondary", scale=1)
|
| 453 |
+
check_lp_btn = gr.Button("🔍 查 LINE Pay (自動救援)", variant="secondary", scale=1)
|
| 454 |
noshow_btn = gr.Button("🚫 標記 No-Show", variant="stop", scale=1)
|
| 455 |
revert_noshow_btn = gr.Button("✅ 撤銷 No-Show", scale=1)
|
| 456 |
refresh_btn = gr.Button("🔄 刷新列表", scale=1)
|
|
|
|
| 459 |
|
| 460 |
booking_display = gr.HTML(elem_id="booking_display")
|
| 461 |
|
| 462 |
+
update_status_btn.click(update_booking_status, inputs=[id_input, new_status_dropdown], outputs=log_output).then(render_booking_cards, outputs=booking_display)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
check_lp_btn.click(check_linepay_status, inputs=[id_input], outputs=log_output)
|
|
|
|
|
|
|
| 464 |
refresh_btn.click(render_booking_cards, outputs=booking_display)
|
| 465 |
send_btn.click(send_confirmation_hybrid, inputs=id_input, outputs=log_output).then(render_booking_cards, outputs=booking_display)
|
| 466 |
noshow_btn.click(toggle_no_show, inputs=[id_input, gr.State(True)], outputs=log_output).then(render_booking_cards, outputs=booking_display)
|
|
|
|
| 478 |
with gr.Column(scale=1):
|
| 479 |
m_name = gr.Textbox(label="餐點名稱 *")
|
| 480 |
m_desc = gr.Textbox(label="餐點描述 (選填)")
|
| 481 |
+
|
| 482 |
+
with gr.Row():
|
| 483 |
+
m_price = gr.Number(label="價格 (TWD) *", precision=0)
|
| 484 |
+
# 🌟 這裡就是新加入的「每日限量」輸入框 🌟
|
| 485 |
+
m_limit = gr.Number(label="每日限量 (999代表無限制)", value=999, precision=0)
|
| 486 |
+
|
| 487 |
m_cat = gr.Dropdown(choices=["main", "snack", "drink", "other"], label="分類", value="main")
|
| 488 |
m_image_url = gr.Textbox(label="餐點照片網址 (選填)", placeholder="例如: https://ciecietaipei.github.io/assets/steak.jpg")
|
| 489 |
m_times = gr.CheckboxGroup(
|
|
|
|
| 514 |
m_set_inactive = gr.Button("🔴 下架", scale=1)
|
| 515 |
m_toggle_log = gr.Textbox(label="操作狀態", interactive=False)
|
| 516 |
|
| 517 |
+
# 🌟 注意這裡:按鈕綁定的陣列中,已經幫您把 m_limit 都完美補進去了 🌟
|
| 518 |
m_load_btn.click(
|
| 519 |
load_menu_data,
|
| 520 |
inputs=[m_toggle_dropdown],
|
| 521 |
+
outputs=[m_name, m_desc, m_price, m_limit, m_cat, m_times, m_prepay, m_takeout, m_image_url, m_edit_id]
|
| 522 |
).then(lambda: "✅ 已載入至左側表單,修改後請點擊「儲存修改」", outputs=m_toggle_log)
|
| 523 |
|
|
|
|
| 524 |
m_add_btn.click(
|
| 525 |
add_menu_item,
|
| 526 |
+
inputs=[m_name, m_desc, m_price, m_limit, m_cat, m_times, m_prepay, m_takeout, m_image_url],
|
| 527 |
outputs=[m_form_log, menu_df]
|
| 528 |
).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 529 |
|
| 530 |
m_update_btn.click(
|
| 531 |
update_menu_item,
|
| 532 |
+
inputs=[m_edit_id, m_name, m_desc, m_price, m_limit, m_cat, m_times, m_prepay, m_takeout, m_image_url],
|
| 533 |
outputs=[m_form_log, menu_df]
|
| 534 |
).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 535 |
|
|
|
|
| 536 |
m_refresh_btn.click(get_menu_items, outputs=menu_df).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 537 |
m_set_active.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(True)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|
| 538 |
m_set_inactive.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(False)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
|