Update app.py
Browse files
app.py
CHANGED
|
@@ -22,7 +22,6 @@ REAL_ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") or "2016-11-11"
|
|
| 22 |
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 23 |
|
| 24 |
def get_bookings():
|
| 25 |
-
# 這裡抓取所有資料,沒有設限制
|
| 26 |
try:
|
| 27 |
res = supabase.table("bookings").select("*").order("created_at", desc=True).execute()
|
| 28 |
if not res.data: return pd.DataFrame()
|
|
@@ -90,35 +89,33 @@ def send_confirmation_hybrid(booking_id):
|
|
| 90 |
return log_msg
|
| 91 |
except Exception as e: return f"Error: {e}"
|
| 92 |
|
| 93 |
-
# 🔥🔥🔥 3.
|
| 94 |
def render_booking_cards():
|
| 95 |
df = get_bookings()
|
| 96 |
|
| 97 |
-
#
|
| 98 |
-
count_html = f"<div style='color:#888; margin-bottom:10px; text-align:right;'>共找到 {len(df)} 筆訂位資料</div>"
|
| 99 |
|
| 100 |
if df.empty:
|
| 101 |
-
return f"{count_html}<div style='text-align:center; padding:60px; color:#666; font-size:1.
|
| 102 |
|
| 103 |
-
|
|
|
|
| 104 |
|
| 105 |
for index, row in df.iterrows():
|
| 106 |
-
# 狀態邏輯
|
| 107 |
status = row.get('status', '待處理')
|
| 108 |
status_color = "#ccc"
|
| 109 |
border_color = "#444"
|
| 110 |
|
| 111 |
if '確認' in status:
|
| 112 |
-
status_color = "#2ecc71"
|
| 113 |
border_color = "#2ecc71"
|
| 114 |
elif '取消' in status:
|
| 115 |
-
status_color = "#e74c3c"
|
| 116 |
border_color = "#e74c3c"
|
| 117 |
elif '已發' in status:
|
| 118 |
-
status_color = "#f1c40f"
|
| 119 |
border_color = "#f1c40f"
|
| 120 |
|
| 121 |
-
# 按鈕邏輯
|
| 122 |
is_canceled = '取消' in status
|
| 123 |
btn_onclick = "" if is_canceled else f"cardAction({row['id']})"
|
| 124 |
|
|
@@ -126,89 +123,93 @@ def render_booking_cards():
|
|
| 126 |
btn_style = "background: #333; color: #666; cursor: not-allowed;"
|
| 127 |
btn_text = "🚫 已取消"
|
| 128 |
elif '已發' in status or '確認' in status:
|
| 129 |
-
btn_style = "background: #2c3e50; color: #fff; border: 1px solid #555;
|
| 130 |
btn_text = "🔄 重發確認"
|
| 131 |
else:
|
| 132 |
-
btn_style = "background: #d4af37; color: #000; font-weight:
|
| 133 |
btn_text = "🚀 發送確認"
|
| 134 |
|
| 135 |
card = f"""
|
| 136 |
<div class="booking-card" style="
|
| 137 |
-
background: #
|
| 138 |
-
border-left:
|
| 139 |
border-radius: 12px;
|
| 140 |
-
padding:
|
| 141 |
-
box-shadow: 0
|
| 142 |
-
font-family: '
|
| 143 |
-
position: relative;
|
|
|
|
| 144 |
|
| 145 |
-
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:
|
| 146 |
-
<div style="font-size:1.
|
| 147 |
-
<span style="font-size:0.
|
| 148 |
</div>
|
| 149 |
<div style="
|
| 150 |
color: {status_color};
|
| 151 |
-
background: {status_color}
|
| 152 |
-
padding:
|
| 153 |
-
border-radius:
|
| 154 |
-
font-size:
|
| 155 |
font-weight: bold;
|
| 156 |
-
letter-spacing: 1px;
|
|
|
|
| 157 |
{status}
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
|
| 161 |
-
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:
|
| 162 |
<div>
|
| 163 |
-
<div style="font-size:
|
| 164 |
-
<div style="font-size:
|
| 165 |
</div>
|
| 166 |
<div>
|
| 167 |
-
<div style="font-size:
|
| 168 |
-
<div style="font-size:
|
| 169 |
</div>
|
| 170 |
</div>
|
| 171 |
|
| 172 |
-
<div style="background:#
|
| 173 |
-
<div style="margin-bottom:
|
| 174 |
-
<span style="color:#
|
| 175 |
-
<span style="color:#
|
| 176 |
</div>
|
| 177 |
-
<div style="margin-bottom:
|
| 178 |
-
<span style="color:#
|
| 179 |
-
<a href="tel:{row['tel']}" style="color:#
|
| 180 |
</div>
|
| 181 |
<div>
|
| 182 |
-
<span style="color:#
|
| 183 |
-
<span style="color:#
|
| 184 |
</div>
|
| 185 |
</div>
|
| 186 |
|
| 187 |
-
<div style="margin-bottom:
|
| 188 |
-
<div style="font-size:
|
| 189 |
-
<div style="color:#
|
| 190 |
-
{row.get('remarks') or '無
|
| 191 |
</div>
|
| 192 |
</div>
|
| 193 |
|
| 194 |
-
<div style="display:flex; justify-content:space-between; align-items:center; border-top:1px solid #333; padding-top:
|
| 195 |
-
<
|
| 196 |
-
font-size:
|
| 197 |
-
color: #
|
| 198 |
-
font-
|
| 199 |
-
background: #
|
| 200 |
-
padding:
|
| 201 |
-
border-radius:
|
|
|
|
| 202 |
ID: {row['id']}
|
| 203 |
-
</
|
| 204 |
|
| 205 |
<button onclick="{btn_onclick}" style="
|
| 206 |
border: none;
|
| 207 |
-
padding:
|
| 208 |
border-radius: 8px;
|
| 209 |
-
font-size: 1em;
|
| 210 |
transition: all 0.2s;
|
| 211 |
-
min-width:
|
|
|
|
| 212 |
{btn_style}">
|
| 213 |
{btn_text}
|
| 214 |
</button>
|
|
@@ -250,26 +251,33 @@ function() {
|
|
| 250 |
}
|
| 251 |
"""
|
| 252 |
|
| 253 |
-
# --- CSS (
|
| 254 |
custom_css = """
|
| 255 |
body, .gradio-container { background-color: #0F0F0F; color: #fff; }
|
| 256 |
|
| 257 |
-
/* 捲
|
|
|
|
| 258 |
#booking_display {
|
| 259 |
-
|
| 260 |
-
overflow-y: auto;
|
| 261 |
-
padding-right:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
}
|
| 263 |
-
|
| 264 |
-
|
|
|
|
|
|
|
| 265 |
#booking_display::-webkit-scrollbar-track { background: #222; }
|
| 266 |
|
| 267 |
-
|
|
|
|
| 268 |
|
| 269 |
/* Header Panel 樣式 */
|
| 270 |
#header-panel {
|
| 271 |
background: #1a1a1a;
|
| 272 |
-
border: none;
|
| 273 |
padding: 15px;
|
| 274 |
margin-bottom: 20px;
|
| 275 |
border-radius: 10px;
|
|
@@ -302,7 +310,7 @@ with gr.Blocks(title="Admin") as demo:
|
|
| 302 |
hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
|
| 303 |
hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
|
| 304 |
|
| 305 |
-
log_output = gr.Textbox(label="系統日誌", lines=
|
| 306 |
|
| 307 |
# 綁定事件
|
| 308 |
login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
|
|
|
|
| 22 |
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 23 |
|
| 24 |
def get_bookings():
|
|
|
|
| 25 |
try:
|
| 26 |
res = supabase.table("bookings").select("*").order("created_at", desc=True).execute()
|
| 27 |
if not res.data: return pd.DataFrame()
|
|
|
|
| 89 |
return log_msg
|
| 90 |
except Exception as e: return f"Error: {e}"
|
| 91 |
|
| 92 |
+
# 🔥🔥🔥 3.2 版卡片:解決截斷問題 + 字體特大號 🔥🔥🔥
|
| 93 |
def render_booking_cards():
|
| 94 |
df = get_bookings()
|
| 95 |
|
| 96 |
+
count_html = f"<div style='color:#bbb; margin-bottom:15px; text-align:right; font-size:16px;'>📊 共找到 <span style='color:#fff; font-weight:bold;'>{len(df)}</span> 筆資料</div>"
|
|
|
|
| 97 |
|
| 98 |
if df.empty:
|
| 99 |
+
return f"{count_html}<div style='text-align:center; padding:60px; color:#666; font-size:1.5em;'>📭 目前沒有訂位資料</div>"
|
| 100 |
|
| 101 |
+
# 這裡 padding-bottom 設為 120px,確保最後一張卡片不會被捲軸切掉
|
| 102 |
+
cards_html = f"{count_html}<div style='display: flex; flex-direction: column; gap: 24px; padding-bottom: 150px;'>"
|
| 103 |
|
| 104 |
for index, row in df.iterrows():
|
|
|
|
| 105 |
status = row.get('status', '待處理')
|
| 106 |
status_color = "#ccc"
|
| 107 |
border_color = "#444"
|
| 108 |
|
| 109 |
if '確認' in status:
|
| 110 |
+
status_color = "#2ecc71"
|
| 111 |
border_color = "#2ecc71"
|
| 112 |
elif '取消' in status:
|
| 113 |
+
status_color = "#e74c3c"
|
| 114 |
border_color = "#e74c3c"
|
| 115 |
elif '已發' in status:
|
| 116 |
+
status_color = "#f1c40f"
|
| 117 |
border_color = "#f1c40f"
|
| 118 |
|
|
|
|
| 119 |
is_canceled = '取消' in status
|
| 120 |
btn_onclick = "" if is_canceled else f"cardAction({row['id']})"
|
| 121 |
|
|
|
|
| 123 |
btn_style = "background: #333; color: #666; cursor: not-allowed;"
|
| 124 |
btn_text = "🚫 已取消"
|
| 125 |
elif '已發' in status or '確認' in status:
|
| 126 |
+
btn_style = "background: #2c3e50; color: #fff; border: 1px solid #555;"
|
| 127 |
btn_text = "🔄 重發確認"
|
| 128 |
else:
|
| 129 |
+
btn_style = "background: #d4af37; color: #000; font-weight:800; box-shadow: 0 4px 12px rgba(212, 175, 55, 0.5);"
|
| 130 |
btn_text = "🚀 發送確認"
|
| 131 |
|
| 132 |
card = f"""
|
| 133 |
<div class="booking-card" style="
|
| 134 |
+
background: #1a1a1a;
|
| 135 |
+
border-left: 8px solid {border_color};
|
| 136 |
border-radius: 12px;
|
| 137 |
+
padding: 24px;
|
| 138 |
+
box-shadow: 0 8px 20px rgba(0,0,0,0.6);
|
| 139 |
+
font-family: '微軟正黑體', sans-serif;
|
| 140 |
+
position: relative;
|
| 141 |
+
margin-bottom: 5px;">
|
| 142 |
|
| 143 |
+
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; border-bottom:1px solid #333; padding-bottom:15px;">
|
| 144 |
+
<div style="font-size:1.3em; color:#fff; font-weight:bold;">
|
| 145 |
+
<span style="font-size:0.8em; color:#aaa; margin-right:8px; font-weight:normal;">📅 日期</span>{row['date']}
|
| 146 |
</div>
|
| 147 |
<div style="
|
| 148 |
color: {status_color};
|
| 149 |
+
background: {status_color}22;
|
| 150 |
+
padding: 8px 16px;
|
| 151 |
+
border-radius: 30px;
|
| 152 |
+
font-size: 1em;
|
| 153 |
font-weight: bold;
|
| 154 |
+
letter-spacing: 1px;
|
| 155 |
+
border: 1px solid {status_color}44;">
|
| 156 |
{status}
|
| 157 |
</div>
|
| 158 |
</div>
|
| 159 |
|
| 160 |
+
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:20px; margin-bottom:25px;">
|
| 161 |
<div>
|
| 162 |
+
<div style="font-size:1em; color:#aaa; margin-bottom:5px;">⏰ 訂位時間</div>
|
| 163 |
+
<div style="font-size:2.2em; color:#d4af37; font-weight:900; letter-spacing:-1px; line-height:1;">{row['time']}</div>
|
| 164 |
</div>
|
| 165 |
<div>
|
| 166 |
+
<div style="font-size:1em; color:#aaa; margin-bottom:5px;">👥 人數</div>
|
| 167 |
+
<div style="font-size:2.2em; color:#fff; font-weight:900; line-height:1;">{row['pax']} <span style="font-size:0.5em; font-weight:400; color:#888;">位</span></div>
|
| 168 |
</div>
|
| 169 |
</div>
|
| 170 |
|
| 171 |
+
<div style="background:#222; padding:20px; border-radius:10px; margin-bottom:20px;">
|
| 172 |
+
<div style="margin-bottom:15px;">
|
| 173 |
+
<span style="color:#bbb; font-size:1em; display:block; margin-bottom:4px; font-weight:bold;">👤 姓名 Name</span>
|
| 174 |
+
<span style="color:#fff; font-size:1.5em; font-weight:bold;">{row['name']}</span>
|
| 175 |
</div>
|
| 176 |
+
<div style="margin-bottom:15px;">
|
| 177 |
+
<span style="color:#bbb; font-size:1em; display:block; margin-bottom:4px; font-weight:bold;">📞 電話 Phone</span>
|
| 178 |
+
<a href="tel:{row['tel']}" style="color:#69c0ff; text-decoration:none; font-size:1.4em; letter-spacing:1px; font-weight:600; border-bottom: 1px dotted #69c0ff;">{row['tel']}</a>
|
| 179 |
</div>
|
| 180 |
<div>
|
| 181 |
+
<span style="color:#bbb; font-size:1em; display:block; margin-bottom:4px; font-weight:bold;">✉️ 信箱 Email</span>
|
| 182 |
+
<span style="color:#ddd; font-size:1.1em; word-break:break-all;">{row['email'] or '未提供'}</span>
|
| 183 |
</div>
|
| 184 |
</div>
|
| 185 |
|
| 186 |
+
<div style="margin-bottom:25px;">
|
| 187 |
+
<div style="font-size:1em; color:#aaa; margin-bottom:8px;">📝 備註 Note</div>
|
| 188 |
+
<div style="color:#f1c40f; background:#f1c40f11; padding:15px; border-radius:8px; font-size:1.1em; line-height:1.6; border: 1px solid #f1c40f33;">
|
| 189 |
+
{row.get('remarks') or '無'}
|
| 190 |
</div>
|
| 191 |
</div>
|
| 192 |
|
| 193 |
+
<div style="display:flex; justify-content:space-between; align-items:center; border-top:1px solid #333; padding-top:20px;">
|
| 194 |
+
<div style="
|
| 195 |
+
font-size: 1.1em;
|
| 196 |
+
color: #000;
|
| 197 |
+
font-weight: 900;
|
| 198 |
+
background: #e0e0e0;
|
| 199 |
+
padding: 6px 12px;
|
| 200 |
+
border-radius: 6px;
|
| 201 |
+
font-family: monospace;">
|
| 202 |
ID: {row['id']}
|
| 203 |
+
</div>
|
| 204 |
|
| 205 |
<button onclick="{btn_onclick}" style="
|
| 206 |
border: none;
|
| 207 |
+
padding: 14px 30px;
|
| 208 |
border-radius: 8px;
|
| 209 |
+
font-size: 1.1em;
|
| 210 |
transition: all 0.2s;
|
| 211 |
+
min-width: 150px;
|
| 212 |
+
cursor: pointer;
|
| 213 |
{btn_style}">
|
| 214 |
{btn_text}
|
| 215 |
</button>
|
|
|
|
| 251 |
}
|
| 252 |
"""
|
| 253 |
|
| 254 |
+
# --- CSS (修復捲軸與高度問題) ---
|
| 255 |
custom_css = """
|
| 256 |
body, .gradio-container { background-color: #0F0F0F; color: #fff; }
|
| 257 |
|
| 258 |
+
/* 🔥 關鍵修正:給容器一個固定高度 (例如 65vh),確保它在頁面內捲動 */
|
| 259 |
+
/* 這樣就不會被下方的 Log 區塊擋住,也不會撐開整個網頁 */
|
| 260 |
#booking_display {
|
| 261 |
+
height: 65vh; /* 固定高度,約佔螢幕 65% */
|
| 262 |
+
overflow-y: auto; /* 內容太多時顯示垂直捲軸 */
|
| 263 |
+
padding-right: 8px; /* 避免文字貼著捲軸 */
|
| 264 |
+
margin-bottom: 20px;
|
| 265 |
+
border: 1px solid #333;
|
| 266 |
+
border-radius: 8px;
|
| 267 |
+
background-color: #111;
|
| 268 |
}
|
| 269 |
+
|
| 270 |
+
/* 捲軸美化 */
|
| 271 |
+
#booking_display::-webkit-scrollbar { width: 8px; }
|
| 272 |
+
#booking_display::-webkit-scrollbar-thumb { background: #666; border-radius: 4px; }
|
| 273 |
#booking_display::-webkit-scrollbar-track { background: #222; }
|
| 274 |
|
| 275 |
+
/* 讓按鈕有點擊感 */
|
| 276 |
+
button:active { transform: scale(0.96); }
|
| 277 |
|
| 278 |
/* Header Panel 樣式 */
|
| 279 |
#header-panel {
|
| 280 |
background: #1a1a1a;
|
|
|
|
| 281 |
padding: 15px;
|
| 282 |
margin-bottom: 20px;
|
| 283 |
border-radius: 10px;
|
|
|
|
| 310 |
hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
|
| 311 |
hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
|
| 312 |
|
| 313 |
+
log_output = gr.Textbox(label="系統日誌 (System Log)", lines=2, interactive=False)
|
| 314 |
|
| 315 |
# 綁定事件
|
| 316 |
login_btn.click(check_login, inputs=[username_input, password_input], outputs=[login_row, admin_row, error_msg]).then(
|