Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -33,7 +33,7 @@ CARD_CSS = """
|
|
| 33 |
gap: 12px;
|
| 34 |
}
|
| 35 |
|
| 36 |
-
/*
|
| 37 |
#job_cards label {
|
| 38 |
border: 1px solid #e5e7eb !important;
|
| 39 |
border-radius: 16px !important;
|
|
@@ -41,8 +41,9 @@ CARD_CSS = """
|
|
| 41 |
background: #ffffff !important;
|
| 42 |
cursor: pointer !important;
|
| 43 |
line-height: 1.45 !important;
|
| 44 |
-
white-space: pre-wrap !important;
|
| 45 |
transition: all .12s ease;
|
|
|
|
| 46 |
}
|
| 47 |
|
| 48 |
/* hover */
|
|
@@ -51,13 +52,14 @@ CARD_CSS = """
|
|
| 51 |
box-shadow: 0 6px 18px rgba(17, 24, 39, 0.08);
|
| 52 |
}
|
| 53 |
|
| 54 |
-
/* 選択時 */
|
| 55 |
#job_cards input:checked + label {
|
| 56 |
border-color: #4f46e5 !important;
|
| 57 |
background: #eef2ff !important;
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
-
/* ラジオ丸
|
| 61 |
#job_cards input[type="radio"] {
|
| 62 |
transform: scale(0.95);
|
| 63 |
margin-top: 6px;
|
|
@@ -201,13 +203,12 @@ def bids_table(job_id=None):
|
|
| 201 |
return pd.DataFrame(rows)
|
| 202 |
|
| 203 |
# =========================
|
| 204 |
-
# Contractor card
|
| 205 |
# =========================
|
| 206 |
def open_job_cards(date_mode: str):
|
| 207 |
"""
|
| 208 |
-
職人タブの「募集中案件カード(Radio)」 choices
|
| 209 |
-
|
| 210 |
-
期限が近い順(残り日数が小さい順)に並べる。
|
| 211 |
"""
|
| 212 |
items = []
|
| 213 |
for j in STATE["jobs"]:
|
|
@@ -218,18 +219,12 @@ def open_job_cards(date_mode: str):
|
|
| 218 |
rem_days = days_remaining(j["deadline_raw"])
|
| 219 |
rem = remaining_label(j["deadline_raw"])
|
| 220 |
|
| 221 |
-
details = (j.get("details") or "").strip()
|
| 222 |
-
details_short = details[:60] + ("…" if len(details) > 60 else "")
|
| 223 |
-
if not details_short:
|
| 224 |
-
details_short = "(詳細なし)"
|
| 225 |
-
|
| 226 |
label = (
|
| 227 |
f"{j['title']}\n"
|
| 228 |
-
f"・案件ID:{j['job_id']}\n"
|
| 229 |
f"・エリア:{j['area']}\n"
|
| 230 |
f"・予算:{yen(j['budget_min'])}〜{yen(j['budget_max'])}\n"
|
| 231 |
-
f"・納期:{dl}
|
| 232 |
-
f"・
|
| 233 |
)
|
| 234 |
|
| 235 |
sort_key = rem_days if rem_days is not None else 10**9
|
|
@@ -239,24 +234,39 @@ def open_job_cards(date_mode: str):
|
|
| 239 |
return [x[1] for x in items]
|
| 240 |
|
| 241 |
def job_card_md(job_id: str, date_mode: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
j = next((x for x in STATE["jobs"] if x["job_id"] == job_id), None)
|
| 243 |
if not j:
|
| 244 |
return "(案件が見つかりません)"
|
| 245 |
-
|
|
|
|
| 246 |
dl = format_deadline(j["deadline_raw"], date_mode)
|
| 247 |
rem = remaining_label(j["deadline_raw"])
|
| 248 |
|
|
|
|
| 249 |
return f"""
|
| 250 |
-
<div style="border:1px solid #e5e7eb;border-radius:
|
| 251 |
-
<div style="
|
| 252 |
-
|
| 253 |
-
<div style="padding:6px 10px;border-radius:999px;background:#f3f4f6;">
|
| 254 |
-
|
| 255 |
-
<
|
| 256 |
-
|
| 257 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
</div>
|
| 259 |
-
<div style="margin-top:10px;color:#374151;white-space:pre-wrap;">{details}</div>
|
| 260 |
</div>
|
| 261 |
"""
|
| 262 |
|
|
@@ -345,7 +355,7 @@ def refresh_contractor_view(date_mode):
|
|
| 345 |
|
| 346 |
def on_card_select(job_id, date_mode):
|
| 347 |
"""
|
| 348 |
-
職人:カード
|
| 349 |
"""
|
| 350 |
if not job_id:
|
| 351 |
return (
|
|
@@ -517,7 +527,6 @@ with gr.Blocks(title="ギルドビルド|プロトタイプ", theme=gr.themes.
|
|
| 517 |
)
|
| 518 |
refresh_jobs_btn = gr.Button("案件リスト更新")
|
| 519 |
|
| 520 |
-
# ★カード化されたRadio(CSSでカードUI)
|
| 521 |
job_cards = gr.Radio(
|
| 522 |
label="募集中案件(カード)",
|
| 523 |
choices=open_job_cards(date_mode.value),
|
|
|
|
| 33 |
gap: 12px;
|
| 34 |
}
|
| 35 |
|
| 36 |
+
/* label をカード化(常に文字色を濃色に固定) */
|
| 37 |
#job_cards label {
|
| 38 |
border: 1px solid #e5e7eb !important;
|
| 39 |
border-radius: 16px !important;
|
|
|
|
| 41 |
background: #ffffff !important;
|
| 42 |
cursor: pointer !important;
|
| 43 |
line-height: 1.45 !important;
|
| 44 |
+
white-space: pre-wrap !important;
|
| 45 |
transition: all .12s ease;
|
| 46 |
+
color: #111827 !important; /* ★選択時に文字が消えるのを防ぐ */
|
| 47 |
}
|
| 48 |
|
| 49 |
/* hover */
|
|
|
|
| 52 |
box-shadow: 0 6px 18px rgba(17, 24, 39, 0.08);
|
| 53 |
}
|
| 54 |
|
| 55 |
+
/* 選択時:背景 + 枠 + 文字色を明示 */
|
| 56 |
#job_cards input:checked + label {
|
| 57 |
border-color: #4f46e5 !important;
|
| 58 |
background: #eef2ff !important;
|
| 59 |
+
color: #111827 !important; /* ★checkedで白文字化されても上書き */
|
| 60 |
}
|
| 61 |
|
| 62 |
+
/* ラジオ丸 */
|
| 63 |
#job_cards input[type="radio"] {
|
| 64 |
transform: scale(0.95);
|
| 65 |
margin-top: 6px;
|
|
|
|
| 203 |
return pd.DataFrame(rows)
|
| 204 |
|
| 205 |
# =========================
|
| 206 |
+
# Contractor card list (simple)
|
| 207 |
# =========================
|
| 208 |
def open_job_cards(date_mode: str):
|
| 209 |
"""
|
| 210 |
+
職人タブの「募集中案件カード(Radio)」 choices
|
| 211 |
+
カード内はシンプルに:タイトル/エリア/予算/納期/残り
|
|
|
|
| 212 |
"""
|
| 213 |
items = []
|
| 214 |
for j in STATE["jobs"]:
|
|
|
|
| 219 |
rem_days = days_remaining(j["deadline_raw"])
|
| 220 |
rem = remaining_label(j["deadline_raw"])
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
label = (
|
| 223 |
f"{j['title']}\n"
|
|
|
|
| 224 |
f"・エリア:{j['area']}\n"
|
| 225 |
f"・予算:{yen(j['budget_min'])}〜{yen(j['budget_max'])}\n"
|
| 226 |
+
f"・納期:{dl}\n"
|
| 227 |
+
f"・{rem}"
|
| 228 |
)
|
| 229 |
|
| 230 |
sort_key = rem_days if rem_days is not None else 10**9
|
|
|
|
| 234 |
return [x[1] for x in items]
|
| 235 |
|
| 236 |
def job_card_md(job_id: str, date_mode: str):
|
| 237 |
+
"""
|
| 238 |
+
選択後の詳細表示(見栄え重視)
|
| 239 |
+
※案件IDは表示しない(要望)
|
| 240 |
+
"""
|
| 241 |
j = next((x for x in STATE["jobs"] if x["job_id"] == job_id), None)
|
| 242 |
if not j:
|
| 243 |
return "(案件が見つかりません)"
|
| 244 |
+
|
| 245 |
+
details = (j.get("details") or "").strip() or "(詳細なし)"
|
| 246 |
dl = format_deadline(j["deadline_raw"], date_mode)
|
| 247 |
rem = remaining_label(j["deadline_raw"])
|
| 248 |
|
| 249 |
+
# status表示(職人は基本OPENを見る想定なので「募集中」を固定表示)
|
| 250 |
return f"""
|
| 251 |
+
<div style="border:1px solid #e5e7eb;border-radius:18px;padding:16px;background:#ffffff;">
|
| 252 |
+
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;">
|
| 253 |
+
<div style="font-size:20px;font-weight:900;line-height:1.2;">{j['title']}</div>
|
| 254 |
+
<div style="padding:6px 10px;border-radius:999px;background:#f3f4f6;color:#111827;font-weight:700;">
|
| 255 |
+
募集中
|
| 256 |
+
</div>
|
| 257 |
+
</div>
|
| 258 |
+
|
| 259 |
+
<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap;">
|
| 260 |
+
<div style="padding:7px 10px;border-radius:999px;background:#f3f4f6;">エリア:<b>{j['area']}</b></div>
|
| 261 |
+
<div style="padding:7px 10px;border-radius:999px;background:#f3f4f6;">予算:<b>{yen(j['budget_min'])}〜{yen(j['budget_max'])}</b></div>
|
| 262 |
+
<div style="padding:7px 10px;border-radius:999px;background:#f3f4f6;">納期:<b>{dl}</b></div>
|
| 263 |
+
<div style="padding:7px 10px;border-radius:999px;background:#eef2ff;color:#111827;"><b>{rem}</b></div>
|
| 264 |
+
</div>
|
| 265 |
+
|
| 266 |
+
<div style="margin-top:14px;padding:12px;border-radius:14px;background:#fafafa;border:1px solid #f3f4f6;">
|
| 267 |
+
<div style="font-weight:800;margin-bottom:6px;color:#111827;">詳細</div>
|
| 268 |
+
<div style="color:#374151;white-space:pre-wrap;line-height:1.6;">{details}</div>
|
| 269 |
</div>
|
|
|
|
| 270 |
</div>
|
| 271 |
"""
|
| 272 |
|
|
|
|
| 355 |
|
| 356 |
def on_card_select(job_id, date_mode):
|
| 357 |
"""
|
| 358 |
+
職人:カード選択 → 入札先Dropdownへ自動セット + 詳細表示 + 入札一覧更新
|
| 359 |
"""
|
| 360 |
if not job_id:
|
| 361 |
return (
|
|
|
|
| 527 |
)
|
| 528 |
refresh_jobs_btn = gr.Button("案件リスト更新")
|
| 529 |
|
|
|
|
| 530 |
job_cards = gr.Radio(
|
| 531 |
label="募集中案件(カード)",
|
| 532 |
choices=open_job_cards(date_mode.value),
|