feat(cafe24): 상품관리를 좌우 2분할로 — 목록(좁게) | 상세페이지 편집(넓게)
왼쪽에서 상품을 클릭하면 오른쪽에 편집기가 바로 열린다. 목록을 다시 받지 않고
오른쪽 조각만 교체한다(GET /products/{no}/pane → JS 삽입). 목록까지 다시 그리면
클릭마다 카페24 호출이 2회 더 늘어나기 때문이다. JS 가 실패하거나 없으면 각 행의
링크(/cafe24/?selected=)로 그대로 동작한다.
목록은 페이지를 없애고 전체를 한 번에 받는다(list_all_products, 1회 100개·상한
1000개). 필터·정렬을 한 페이지에만 적용하면 다음 페이지에 있는 상품이 빠져
"진열중만 보기" 가 거짓이 된다. 현재 87개라 1회 호출로 끝난다.
컬럼은 요청대로 번호·상품명·진열·판매·수정 5개다. 좁은 칸에 맞춰 진열/판매는
배지 대신 점, 수정일은 월-일만 표시하고 전체 값은 title 로 둔다. 긴 상품명은
2줄로 제한해 행 높이를 고르게 유지한다(전체 이름은 title·편집기 제목에서 확인).
진열중/판매중 체크박스는 중복 선택이 되며 둘 다 켜면 AND 다. 문서에 없는 API
필터 파라미터에 기대지 않고 받아온 뒤 파이썬에서 걸러낸다. 제목행 클릭은
오름↔내림 토글이며 한글 정렬은 localeCompare(ko) 를 쓴다.
편집 영역을 넓게 쓰려고 이 화면에서만 .erp-page 의 max-width 를 풀었다. 이때
box-sizing:border-box 를 함께 줘야 한다 — width:100% + padding:24px 이라
max-width 만 풀면 문서 전체에 가로 스크롤이 생긴다(측정으로 확인 후 수정).
편집 중 다른 상품을 클릭하거나 페이지를 벗어나면 저장 안 됨 경고를 띄운다.
옛 단독 화면(product.html)은 제거하고 /products/{no} 는 2분할 화면으로
리다이렉트한다. 편집기 조각을 두 곳에서 함께 쓰도록 _editor.html 로 분리했다.
검증: 유닛테스트 33개 통과(신규 3개 — 전체 조회의 페이지 순회·상한 처리·1회
종료). 상한 처리는 테스트가 잡아서 고쳤다(요청한 만큼 받았는지로 판정). 가짜
데이터로 렌더해 브라우저에서 실측: 왼쪽 360px·오른쪽 940px, 각 칸 독립 스크롤,
분할 영역이 화면 높이에 맞고, 가로 스크롤 없음, 정렬 오름/내림 동작 확인.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -333,6 +333,45 @@ def test_normalize_product_flags():
|
||||
assert products.normalize_product({"product_no": "9"})["display"] is True
|
||||
|
||||
|
||||
class _PagingClient:
|
||||
"""페이지를 넘겨가며 응답하는 가짜 클라이언트."""
|
||||
|
||||
def __init__(self, count: int):
|
||||
self.count = count
|
||||
self.calls: list[dict] = []
|
||||
|
||||
def get(self, path, *, params=None, json=None, product_no=None):
|
||||
self.calls.append(dict(params or {}))
|
||||
offset = int((params or {}).get("offset", 0))
|
||||
limit = int((params or {}).get("limit", 100))
|
||||
page = [{"product_no": n} for n in range(offset, min(offset + limit, self.count))]
|
||||
return {"products": page}
|
||||
|
||||
|
||||
def test_list_all_products_walks_pages():
|
||||
client = _PagingClient(230)
|
||||
rows, truncated = products.list_all_products(client)
|
||||
assert len(rows) == 230 and truncated is False
|
||||
# 100 + 100 + 30 → 3회 호출로 끝나야 한다.
|
||||
assert len(client.calls) == 3
|
||||
assert [c["offset"] for c in client.calls] == [0, 100, 200]
|
||||
|
||||
|
||||
def test_list_all_products_stops_at_cap():
|
||||
"""상한을 넘으면 잘렸다고 알린다 — 무한 호출로 API 제한에 걸리지 않게."""
|
||||
client = _PagingClient(10_000)
|
||||
rows, truncated = products.list_all_products(client, max_items=150)
|
||||
assert len(rows) == 150 and truncated is True
|
||||
|
||||
|
||||
def test_list_all_products_single_page():
|
||||
"""현재 쇼핑몰(87개)은 1회 호출로 끝난다."""
|
||||
client = _PagingClient(87)
|
||||
rows, truncated = products.list_all_products(client)
|
||||
assert len(rows) == 87 and truncated is False
|
||||
assert len(client.calls) == 1
|
||||
|
||||
|
||||
def test_list_products_clamps_paging():
|
||||
client = _FakeClient({"products": []})
|
||||
products.list_products(client, limit=999, offset=-5, product_name="락")
|
||||
|
||||
Reference in New Issue
Block a user