Refine voucher compare UI/layout and fix suggestion behavior
This commit is contained in:
@@ -3,6 +3,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
@@ -18,6 +19,23 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from openpyxl import load_workbook
|
from openpyxl import load_workbook
|
||||||
from sqlalchemy import create_engine, event, text
|
from sqlalchemy import create_engine, event, text
|
||||||
|
from wehago_compare import (
|
||||||
|
get_erp_filtered_rows,
|
||||||
|
get_individual_pair_recommendations,
|
||||||
|
get_last_action_summary,
|
||||||
|
get_status_field_suggestions,
|
||||||
|
get_status_detail_rows,
|
||||||
|
get_wehago_compare_dashboard,
|
||||||
|
get_wehago_filtered_rows,
|
||||||
|
import_uploaded_erp_voucher_file,
|
||||||
|
init_wehago_compare_db,
|
||||||
|
recommend_pair_matches,
|
||||||
|
refresh_wehago_compare_data,
|
||||||
|
save_recommended_pair_matches,
|
||||||
|
save_manual_pair_matches,
|
||||||
|
save_recheck_review_rows,
|
||||||
|
undo_last_action,
|
||||||
|
)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -1078,6 +1096,7 @@ def init_db() -> None:
|
|||||||
migrate_project_basic_info(conn)
|
migrate_project_basic_info(conn)
|
||||||
ensure_default_app_config(conn)
|
ensure_default_app_config(conn)
|
||||||
conn.execute(text("ANALYZE"))
|
conn.execute(text("ANALYZE"))
|
||||||
|
init_wehago_compare_db(engine)
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
@@ -6135,6 +6154,20 @@ def render_annual_summary_page(request: Request, message: str = "") -> HTMLRespo
|
|||||||
return templates.TemplateResponse(request, "annual_summary.html", context)
|
return templates.TemplateResponse(request, "annual_summary.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def render_wehago_compare_page(
|
||||||
|
request: Request,
|
||||||
|
start_year: int | None = None,
|
||||||
|
end_year: int | None = None,
|
||||||
|
message: str = "",
|
||||||
|
) -> HTMLResponse:
|
||||||
|
init_db()
|
||||||
|
context = {
|
||||||
|
**base_context(request, message),
|
||||||
|
"wehago_compare": get_wehago_compare_dashboard(engine, start_year=start_year, end_year=end_year),
|
||||||
|
}
|
||||||
|
return templates.TemplateResponse(request, "wehago_compare.html", context)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health() -> dict[str, str]:
|
async def health() -> dict[str, str]:
|
||||||
return build_health_payload()
|
return build_health_payload()
|
||||||
@@ -6620,6 +6653,329 @@ async def annual_summary(request: Request):
|
|||||||
return HTMLResponse("<h1>서버 오류</h1><p>로그를 확인해주세요.</p>", status_code=500)
|
return HTMLResponse("<h1>서버 오류</h1><p>로그를 확인해주세요.</p>", status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare")
|
||||||
|
async def wehago_compare(request: Request, start_year: str | None = None, end_year: str | None = None):
|
||||||
|
try:
|
||||||
|
return render_wehago_compare_page(
|
||||||
|
request,
|
||||||
|
start_year=parse_optional_year(start_year),
|
||||||
|
end_year=parse_optional_year(end_year),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 페이지 에러: %s", exc)
|
||||||
|
return HTMLResponse("<h1>서버 오류</h1><p>로그를 확인해주세요.</p>", status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/wehago-compare/refresh")
|
||||||
|
async def refresh_wehago_compare(
|
||||||
|
request: Request,
|
||||||
|
start_year: str | None = None,
|
||||||
|
end_year: str | None = None,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
summary = refresh_wehago_compare_data(engine)
|
||||||
|
message = "WEHAGO DB 갱신 완료: " + ", ".join(f"{key}={value}" for key, value in summary.items())
|
||||||
|
return render_wehago_compare_page(
|
||||||
|
request,
|
||||||
|
start_year=parse_optional_year(start_year),
|
||||||
|
end_year=parse_optional_year(end_year),
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 DB 갱신 에러: %s", exc)
|
||||||
|
return render_wehago_compare_page(
|
||||||
|
request,
|
||||||
|
start_year=parse_optional_year(start_year),
|
||||||
|
end_year=parse_optional_year(end_year),
|
||||||
|
message=f"WEHAGO DB 갱신 중 오류가 발생했습니다: {exc}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/wehago-compare/upload-erp")
|
||||||
|
async def upload_wehago_erp_file(
|
||||||
|
request: Request,
|
||||||
|
start_year: str | None = None,
|
||||||
|
end_year: str | None = None,
|
||||||
|
erp_file: UploadFile = File(...),
|
||||||
|
):
|
||||||
|
temp_path: Path | None = None
|
||||||
|
try:
|
||||||
|
if not erp_file.filename:
|
||||||
|
raise ValueError("업로드할 ERP 파일을 선택해주세요.")
|
||||||
|
suffix = Path(erp_file.filename).suffix or ".xlsx"
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as handle:
|
||||||
|
temp_path = Path(handle.name)
|
||||||
|
while True:
|
||||||
|
chunk = await erp_file.read(1024 * 1024)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
handle.write(chunk)
|
||||||
|
|
||||||
|
summary = import_uploaded_erp_voucher_file(engine, temp_path, erp_file.filename)
|
||||||
|
message = (
|
||||||
|
f"ERP 파일 반영 완료: {summary['file_name']} "
|
||||||
|
f"(추가 {summary['inserted_rows']}건, 중복 제외 {summary['duplicate_rows']}건)"
|
||||||
|
)
|
||||||
|
return render_wehago_compare_page(
|
||||||
|
request,
|
||||||
|
start_year=parse_optional_year(start_year),
|
||||||
|
end_year=parse_optional_year(end_year),
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 ERP 업로드 에러: %s", exc)
|
||||||
|
return render_wehago_compare_page(
|
||||||
|
request,
|
||||||
|
start_year=parse_optional_year(start_year),
|
||||||
|
end_year=parse_optional_year(end_year),
|
||||||
|
message=f"ERP 파일 업로드 중 오류가 발생했습니다: {exc}",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
await erp_file.close()
|
||||||
|
if temp_path and temp_path.exists():
|
||||||
|
temp_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare/api/status-rows")
|
||||||
|
async def wehago_compare_status_rows(
|
||||||
|
start_year: int | None = None,
|
||||||
|
end_year: int | None = None,
|
||||||
|
status: str = "",
|
||||||
|
voucher_no: str = "",
|
||||||
|
draft_no: str = "",
|
||||||
|
wehago_account: str = "",
|
||||||
|
erp_account: str = "",
|
||||||
|
wehago_amount: str = "",
|
||||||
|
erp_amount: str = "",
|
||||||
|
wehago_vendor: str = "",
|
||||||
|
erp_vendor: str = "",
|
||||||
|
desc_keyword: str = "",
|
||||||
|
review_reason: str = "",
|
||||||
|
offset: int = 0,
|
||||||
|
limit: int = 200,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
payload = get_status_detail_rows(
|
||||||
|
engine,
|
||||||
|
start_year=start_year,
|
||||||
|
end_year=end_year,
|
||||||
|
status=status,
|
||||||
|
voucher_no=voucher_no,
|
||||||
|
draft_no=draft_no,
|
||||||
|
wehago_account=wehago_account,
|
||||||
|
erp_account=erp_account,
|
||||||
|
wehago_amount=wehago_amount,
|
||||||
|
erp_amount=erp_amount,
|
||||||
|
wehago_vendor=wehago_vendor,
|
||||||
|
erp_vendor=erp_vendor,
|
||||||
|
desc_keyword=desc_keyword,
|
||||||
|
review_reason=review_reason,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 상태 상세 조회 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare/api/status-suggestions")
|
||||||
|
async def wehago_compare_status_suggestions(
|
||||||
|
start_year: int | None = None,
|
||||||
|
end_year: int | None = None,
|
||||||
|
status: str = "",
|
||||||
|
field: str = "voucher_no",
|
||||||
|
keyword: str = "",
|
||||||
|
offset: int = 0,
|
||||||
|
limit: int = 10,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
payload = get_status_field_suggestions(
|
||||||
|
engine,
|
||||||
|
start_year=start_year,
|
||||||
|
end_year=end_year,
|
||||||
|
status=status,
|
||||||
|
field=field,
|
||||||
|
keyword=keyword,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 자동완성 조회 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/wehago-compare/api/recheck-review-save")
|
||||||
|
async def wehago_compare_recheck_review_save(request: Request):
|
||||||
|
try:
|
||||||
|
payload = await request.json()
|
||||||
|
rows = payload.get("rows") if isinstance(payload, dict) else None
|
||||||
|
if not isinstance(rows, list):
|
||||||
|
raise ValueError("저장할 검토 항목 형식이 올바르지 않습니다.")
|
||||||
|
saved = save_recheck_review_rows(engine, rows)
|
||||||
|
return JSONResponse(content={"saved_count": saved})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 검토 저장 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/wehago-compare/api/pair-match-save")
|
||||||
|
async def wehago_compare_pair_match_save(request: Request):
|
||||||
|
try:
|
||||||
|
payload = await request.json()
|
||||||
|
ledger_rows = payload.get("ledger_rows") if isinstance(payload, dict) else None
|
||||||
|
voucher_rows = payload.get("voucher_rows") if isinstance(payload, dict) else None
|
||||||
|
if not isinstance(ledger_rows, list) or not isinstance(voucher_rows, list):
|
||||||
|
raise ValueError("저장할 쌍비교 항목 형식이 올바르지 않습니다.")
|
||||||
|
saved = save_manual_pair_matches(engine, ledger_rows, voucher_rows)
|
||||||
|
return JSONResponse(content={"saved_count": saved})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 쌍매칭 저장 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare/api/pair-recommendations")
|
||||||
|
async def wehago_compare_pair_recommendations(
|
||||||
|
start_year: int | None = None,
|
||||||
|
end_year: int | None = None,
|
||||||
|
ledger_voucher_no: str = "",
|
||||||
|
ledger_review_reason: str = "",
|
||||||
|
voucher_voucher_no: str = "",
|
||||||
|
voucher_review_reason: str = "",
|
||||||
|
limit: int = 300,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
payload = recommend_pair_matches(
|
||||||
|
engine,
|
||||||
|
start_year=start_year,
|
||||||
|
end_year=end_year,
|
||||||
|
ledger_voucher_no=ledger_voucher_no,
|
||||||
|
ledger_review_reason=ledger_review_reason,
|
||||||
|
voucher_voucher_no=voucher_voucher_no,
|
||||||
|
voucher_review_reason=voucher_review_reason,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 추천 매칭 조회 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/wehago-compare/api/pair-recommendations/apply")
|
||||||
|
async def wehago_compare_pair_recommendations_apply(request: Request):
|
||||||
|
try:
|
||||||
|
payload = await request.json()
|
||||||
|
pair_keys = payload.get("pair_keys") if isinstance(payload, dict) else None
|
||||||
|
auto_only = bool(payload.get("auto_only")) if isinstance(payload, dict) else False
|
||||||
|
saved = save_recommended_pair_matches(
|
||||||
|
engine,
|
||||||
|
start_year=parse_optional_year(payload.get("start_year")) if isinstance(payload, dict) else None,
|
||||||
|
end_year=parse_optional_year(payload.get("end_year")) if isinstance(payload, dict) else None,
|
||||||
|
pair_keys=pair_keys if isinstance(pair_keys, list) else None,
|
||||||
|
auto_only=auto_only,
|
||||||
|
ledger_voucher_no=payload.get("ledger_voucher_no", "") if isinstance(payload, dict) else "",
|
||||||
|
ledger_review_reason=payload.get("ledger_review_reason", "") if isinstance(payload, dict) else "",
|
||||||
|
voucher_voucher_no=payload.get("voucher_voucher_no", "") if isinstance(payload, dict) else "",
|
||||||
|
voucher_review_reason=payload.get("voucher_review_reason", "") if isinstance(payload, dict) else "",
|
||||||
|
)
|
||||||
|
return JSONResponse(content=jsonable_encoder(saved))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 추천 매칭 저장 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare/api/last-action")
|
||||||
|
async def wehago_compare_last_action():
|
||||||
|
try:
|
||||||
|
payload = get_last_action_summary(engine=engine)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload or {}))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 최근 작업 조회 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/wehago-compare/api/undo-last-action")
|
||||||
|
async def wehago_compare_undo_last_action():
|
||||||
|
try:
|
||||||
|
payload = undo_last_action(engine)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 되돌리기 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare/api/pair-individual-recommendations")
|
||||||
|
async def wehago_compare_pair_individual_recommendations(
|
||||||
|
start_year: int | None = None,
|
||||||
|
end_year: int | None = None,
|
||||||
|
source_status: str = "",
|
||||||
|
source_row_key: str = "",
|
||||||
|
offset: int = 0,
|
||||||
|
limit: int = 10,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
payload = get_individual_pair_recommendations(
|
||||||
|
engine,
|
||||||
|
start_year=start_year,
|
||||||
|
end_year=end_year,
|
||||||
|
source_status=source_status,
|
||||||
|
source_row_key=source_row_key,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 개별 추천 조회 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare/api/wehago-rows")
|
||||||
|
async def wehago_compare_wehago_rows(
|
||||||
|
start_year: int | None = None,
|
||||||
|
end_year: int | None = None,
|
||||||
|
voucher_no: str = "",
|
||||||
|
account_code: str = "",
|
||||||
|
vendor_name: str = "",
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
payload = get_wehago_filtered_rows(
|
||||||
|
engine,
|
||||||
|
start_year=start_year,
|
||||||
|
end_year=end_year,
|
||||||
|
voucher_no=voucher_no,
|
||||||
|
account_code=account_code,
|
||||||
|
vendor_name=vendor_name,
|
||||||
|
)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 WEHAGO 상세 조회 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wehago-compare/api/erp-rows")
|
||||||
|
async def wehago_compare_erp_rows(
|
||||||
|
start_year: int | None = None,
|
||||||
|
end_year: int | None = None,
|
||||||
|
voucher_no: str = "",
|
||||||
|
account_code: str = "",
|
||||||
|
vendor_name: str = "",
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
payload = get_erp_filtered_rows(
|
||||||
|
engine,
|
||||||
|
start_year=start_year,
|
||||||
|
end_year=end_year,
|
||||||
|
voucher_no=voucher_no,
|
||||||
|
account_code=account_code,
|
||||||
|
vendor_name=vendor_name,
|
||||||
|
)
|
||||||
|
return JSONResponse(content=jsonable_encoder(payload))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("전표비교 ERP 상세 조회 에러: %s", exc)
|
||||||
|
return JSONResponse(content={"error": str(exc)}, status_code=500)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/upload")
|
@app.post("/upload")
|
||||||
async def upload_excel(request: Request, excel_file: UploadFile = File(...)):
|
async def upload_excel(request: Request, excel_file: UploadFile = File(...)):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -41,10 +41,10 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
background: rgba(255,255,255,0.92);
|
background: transparent;
|
||||||
border: 1px solid var(--line);
|
border: 0;
|
||||||
border-radius: 12px;
|
border-radius: 0;
|
||||||
padding: 8px 12px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-swatch {
|
.legend-swatch {
|
||||||
|
|||||||
@@ -249,6 +249,17 @@
|
|||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
box-shadow: inset 0 1px 2px rgba(16, 24, 40, 0.03);
|
box-shadow: inset 0 1px 2px rgba(16, 24, 40, 0.03);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="date"],
|
||||||
|
select {
|
||||||
|
min-height: 38px;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select option {
|
select option {
|
||||||
@@ -301,6 +312,10 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: transform 0.16s ease, box-shadow 0.16s ease, background 0.16s ease, border-color 0.16s ease;
|
transition: transform 0.16s ease, box-shadow 0.16s ease, background 0.16s ease, border-color 0.16s ease;
|
||||||
box-shadow: 0 8px 18px rgba(17, 17, 17, 0.14);
|
box-shadow: 0 8px 18px rgba(17, 17, 17, 0.14);
|
||||||
|
min-height: 38px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover,
|
button:hover,
|
||||||
@@ -513,6 +528,7 @@
|
|||||||
<a href="/" class="{% if request.url.path == '/' %}active{% endif %}">대시보드</a>
|
<a href="/" class="{% if request.url.path == '/' %}active{% endif %}">대시보드</a>
|
||||||
<a href="/projects" class="{% if request.url.path == '/projects' %}active{% endif %}">프로젝트 정보</a>
|
<a href="/projects" class="{% if request.url.path == '/projects' %}active{% endif %}">프로젝트 정보</a>
|
||||||
<a href="/annual-summary" class="{% if request.url.path == '/annual-summary' %}active{% endif %}">연도별 수익/비용</a>
|
<a href="/annual-summary" class="{% if request.url.path == '/annual-summary' %}active{% endif %}">연도별 수익/비용</a>
|
||||||
|
<a href="/wehago-compare" class="{% if request.url.path == '/wehago-compare' %}active{% endif %}">전표비교</a>
|
||||||
<div class="nav-spacer"></div>
|
<div class="nav-spacer"></div>
|
||||||
<aside
|
<aside
|
||||||
class="sync-status"
|
class="sync-status"
|
||||||
|
|||||||
@@ -158,12 +158,12 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
padding: 6px 10px;
|
padding: 0;
|
||||||
background: rgba(255,255,255,0.92);
|
background: transparent;
|
||||||
border: 1px solid var(--line);
|
border: 0;
|
||||||
color: #363b44;
|
color: #363b44;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+37
-9
@@ -227,14 +227,15 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 190px;
|
max-width: 190px;
|
||||||
padding: 8px 12px;
|
min-height: 27px;
|
||||||
|
padding: 3px 12px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(255, 255, 255, 0.96);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.3;
|
line-height: 1.05;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
|
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
|
||||||
}
|
}
|
||||||
@@ -263,6 +264,22 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
min-width: 14px;
|
||||||
|
min-height: 14px;
|
||||||
|
max-width: 14px;
|
||||||
|
max-height: 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background: rgba(15, 23, 42, 0.08);
|
||||||
|
box-shadow: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-quick-link-remove:hover {
|
||||||
|
background: rgba(15, 23, 42, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-quick-link-remove svg {
|
.project-quick-link-remove svg {
|
||||||
@@ -506,10 +523,11 @@
|
|||||||
background: #f7fbfe;
|
background: #f7fbfe;
|
||||||
color: #29485f;
|
color: #29485f;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 5px 10px;
|
min-height: 18px;
|
||||||
|
padding: 1px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.2;
|
line-height: 1.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-project-tag.is-muted {
|
.related-project-tag.is-muted {
|
||||||
@@ -517,15 +535,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.related-project-tag button {
|
.related-project-tag button {
|
||||||
width: 18px;
|
width: 14px;
|
||||||
height: 18px;
|
height: 14px;
|
||||||
min-width: 18px;
|
min-width: 14px;
|
||||||
|
min-height: 14px;
|
||||||
|
max-width: 14px;
|
||||||
|
max-height: 14px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 999px;
|
border-radius: 50%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: rgba(41, 72, 95, 0.08);
|
background: rgba(41, 72, 95, 0.08);
|
||||||
color: #29485f;
|
color: #29485f;
|
||||||
box-shadow: none;
|
box-shadow: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-project-tag button:hover {
|
.related-project-tag button:hover {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+3475
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user