119a128ee0
왼쪽에서 상품을 클릭하면 오른쪽에 편집기가 바로 열린다. 목록을 다시 받지 않고
오른쪽 조각만 교체한다(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>
446 lines
9.9 KiB
CSS
446 lines
9.9 KiB
CSS
/* 카페24 상품관리 모듈 전용 스타일.
|
|
전역(erp.css) 은 건드리지 않는다. 클래스 접두사: cf24-
|
|
색상은 erp.css 의 :root 토큰을 재사용한다. */
|
|
|
|
/* .erp-page 는 flex column + overflow-y:auto 다. 자식의 flex-shrink 기본값이
|
|
1 이라 내용이 화면보다 길면 카드가 눌려 잘리고(스크롤도 안 생긴다),
|
|
시스템 화면의 [카페24 연결] 버튼처럼 카드 아래쪽이 사라진다.
|
|
축소를 막아 넘친 만큼 .erp-page 가 스크롤하게 한다.
|
|
이 파일은 카페24 템플릿에서만 로드되므로 다른 모듈에는 영향이 없다. */
|
|
.erp-page > * {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* 2분할 화면은 편집 영역을 최대한 넓게 쓴다 — 이 페이지에서만 폭 제한을 푼다.
|
|
box-sizing 을 함께 바꿔야 한다: .erp-page 는 width:100% + padding:24px 라서
|
|
max-width 를 풀면 padding 이 폭에 더해져 문서 전체에 가로 스크롤이 생긴다. */
|
|
.erp-page {
|
|
max-width: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.cf24-card {
|
|
margin-bottom: var(--sp-16, 16px);
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════════════════
|
|
좌우 2분할: 왼쪽 목록(좁게) | 오른쪽 상세페이지 편집(넓게)
|
|
각 칸이 따로 스크롤되고, 전체 높이는 화면에 맞춘다.
|
|
════════════════════════════════════════════════════════════ */
|
|
.cf24-split {
|
|
display: grid;
|
|
grid-template-columns: 360px minmax(0, 1fr);
|
|
gap: var(--sp-12, 12px);
|
|
align-items: stretch;
|
|
}
|
|
|
|
/* .erp-page 는 flex column 이다. 위 flex-shrink:0 규칙의 예외로, 분할 영역만은
|
|
남은 높이를 모두 차지하고 안에서 스크롤되게 한다(휴가/쿠팡 모듈과 같은 방식). */
|
|
.erp-page > .cf24-split {
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
}
|
|
|
|
.cf24-pane {
|
|
min-height: 0;
|
|
overflow: auto;
|
|
margin-bottom: 0;
|
|
padding: var(--sp-12, 12px);
|
|
}
|
|
|
|
.cf24-pane-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--sp-8, 8px);
|
|
overflow: hidden; /* 표만 스크롤 — 검색/필터는 고정 */
|
|
}
|
|
|
|
.cf24-pane-editor {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--sp-8, 8px);
|
|
padding: var(--sp-16, 16px);
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
.cf24-split {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
.cf24-pane {
|
|
overflow: visible;
|
|
}
|
|
.cf24-pane-list {
|
|
max-height: 45vh;
|
|
}
|
|
}
|
|
|
|
/* ── 왼쪽: 검색·필터 ── */
|
|
.cf24-filters {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--sp-8, 8px);
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.cf24-checks {
|
|
display: flex;
|
|
gap: var(--sp-12, 12px);
|
|
font-size: var(--text-caption, 12px);
|
|
}
|
|
|
|
.cf24-checks label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cf24-list-count {
|
|
font-size: var(--text-caption, 12px);
|
|
color: var(--color-midtone-gray, #737373);
|
|
}
|
|
|
|
/* ── 왼쪽: 목록 표 (좁게) ── */
|
|
.cf24-list-scroll {
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.cf24-list-table {
|
|
font-size: 12px;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
}
|
|
|
|
.cf24-list-table thead th {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
padding: 6px 4px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.cf24-list-table tbody td {
|
|
padding: 6px 4px;
|
|
vertical-align: top;
|
|
}
|
|
|
|
/* 고정폭 합계를 최소로 잡아 상품명에 남은 폭을 준다.
|
|
진열/판매 칸은 제목(2글자) + 정렬 화살표가 들어갈 만큼만. */
|
|
.cf24-col-no { width: 36px; text-align: right; color: var(--color-midtone-gray, #737373); }
|
|
.cf24-col-flag { width: 40px; text-align: center; }
|
|
.cf24-col-date { width: 46px; white-space: nowrap; color: var(--color-midtone-gray, #737373); }
|
|
.cf24-col-name { word-break: break-word; }
|
|
|
|
/* 긴 상품명은 2줄까지만 — 행 높이를 고르게 유지해 목록을 훑기 쉽게 한다.
|
|
전체 이름은 title 툴팁과 오른쪽 편집기 제목에서 확인한다. */
|
|
.cf24-col-name a {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.cf24-row {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cf24-row:hover {
|
|
background: var(--color-ghost-gray, #f2f2f2);
|
|
}
|
|
|
|
.cf24-row.is-active {
|
|
background: var(--color-rich-black, #0a0a0a);
|
|
}
|
|
|
|
.cf24-row.is-active td,
|
|
.cf24-row.is-active .cf24-col-name a {
|
|
color: #fff;
|
|
}
|
|
|
|
/* 진열/판매 표시는 좁은 칸이라 배지 대신 점으로 */
|
|
.cf24-dot {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--color-subtle-ash, #e5e5e5);
|
|
}
|
|
|
|
.cf24-dot-on {
|
|
background: var(--color-success-green, #10c22b);
|
|
}
|
|
|
|
/* 제목행 클릭 정렬 */
|
|
.cf24-sortable {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.cf24-sortable::after {
|
|
content: "↕";
|
|
opacity: 0.35;
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.cf24-sortable.is-asc::after { content: "▲"; opacity: 1; }
|
|
.cf24-sortable.is-desc::after { content: "▼"; opacity: 1; }
|
|
|
|
/* ── 오른쪽: 편집기 ── */
|
|
.cf24-editor-head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: var(--sp-12, 12px);
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.cf24-editor-title {
|
|
margin: 0;
|
|
font-size: var(--text-heading, 18px);
|
|
letter-spacing: -0.45px;
|
|
}
|
|
|
|
.cf24-editor-sub {
|
|
margin: 4px 0 0;
|
|
font-size: var(--text-caption, 12px);
|
|
color: var(--color-midtone-gray, #737373);
|
|
}
|
|
|
|
.cf24-editor-badges {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.cf24-editor-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
gap: var(--sp-8, 8px);
|
|
}
|
|
|
|
.cf24-editor-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: var(--sp-8, 8px);
|
|
flex-wrap: wrap;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.cf24-editor-bar-right {
|
|
display: flex;
|
|
gap: var(--sp-8, 8px);
|
|
align-items: center;
|
|
}
|
|
|
|
.cf24-memo {
|
|
width: 260px;
|
|
padding: 6px var(--sp-10, 10px);
|
|
border: 1px solid var(--color-subtle-ash, #e5e5e5);
|
|
border-radius: var(--r-lg, 10px);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.cf24-html-main {
|
|
flex: 1 1 auto;
|
|
min-height: 340px;
|
|
}
|
|
|
|
.cf24-details {
|
|
flex: 0 0 auto;
|
|
border-top: 1px solid var(--color-subtle-ash, #e5e5e5);
|
|
padding-top: var(--sp-8, 8px);
|
|
}
|
|
|
|
.cf24-details > summary {
|
|
cursor: pointer;
|
|
font-size: var(--text-caption, 12px);
|
|
color: var(--color-midtone-gray, #737373);
|
|
}
|
|
|
|
.cf24-compact {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.cf24-compact tbody td,
|
|
.cf24-compact thead th {
|
|
padding: 6px 8px;
|
|
}
|
|
|
|
.cf24-empty-pane {
|
|
margin: auto;
|
|
text-align: center;
|
|
padding: var(--sp-24, 24px);
|
|
}
|
|
|
|
.cf24-empty-pane h3 {
|
|
margin: 0 0 var(--sp-8, 8px);
|
|
}
|
|
|
|
.cf24-card-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--sp-8, 8px);
|
|
margin-bottom: var(--sp-12, 12px);
|
|
}
|
|
|
|
.cf24-card-head h3 {
|
|
margin: 0;
|
|
font-size: var(--text-heading, 18px);
|
|
letter-spacing: -0.45px;
|
|
}
|
|
|
|
.cf24-muted {
|
|
color: var(--color-midtone-gray, #737373);
|
|
font-size: var(--text-caption, 12px);
|
|
}
|
|
|
|
.cf24-err {
|
|
color: var(--color-callout-red, #c22b10);
|
|
}
|
|
|
|
.cf24-nowrap {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* 상태 배지 */
|
|
.cf24-badge-ok {
|
|
background: var(--color-success-green, #10c22b);
|
|
color: #fff;
|
|
}
|
|
|
|
.cf24-badge-off {
|
|
background: var(--color-ghost-gray, #f2f2f2);
|
|
color: var(--color-rich-black, #0a0a0a);
|
|
}
|
|
|
|
/* 안내/오류 배너 */
|
|
.cf24-flash {
|
|
padding: var(--sp-10, 10px) var(--sp-12, 12px);
|
|
border-radius: var(--r-lg, 10px);
|
|
margin-bottom: var(--sp-12, 12px);
|
|
font-size: var(--text-body, 14px);
|
|
}
|
|
|
|
.cf24-flash-ok {
|
|
background: #eefaf0;
|
|
border: 1px solid var(--color-success-green, #10c22b);
|
|
}
|
|
|
|
.cf24-flash-err {
|
|
background: #fdefec;
|
|
border: 1px solid var(--color-callout-red, #c22b10);
|
|
}
|
|
|
|
/* 키-값 표 */
|
|
.cf24-kv th {
|
|
width: 180px;
|
|
text-align: left;
|
|
color: var(--color-midtone-gray, #737373);
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.cf24-actions {
|
|
display: flex;
|
|
gap: var(--sp-8, 8px);
|
|
align-items: center;
|
|
margin-top: var(--sp-12, 12px);
|
|
}
|
|
|
|
.cf24-warn {
|
|
color: var(--color-callout-red, #c22b10);
|
|
font-size: var(--text-caption, 12px);
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* 검색 도구모음 / 페이지 이동 */
|
|
.cf24-toolbar {
|
|
display: flex;
|
|
gap: var(--sp-8, 8px);
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
margin-bottom: var(--sp-12, 12px);
|
|
}
|
|
|
|
.cf24-search {
|
|
flex: 0 1 320px;
|
|
padding: var(--sp-8, 8px) var(--sp-12, 12px);
|
|
border: 1px solid var(--color-subtle-ash, #e5e5e5);
|
|
border-radius: var(--r-lg, 10px);
|
|
font-size: var(--text-body, 14px);
|
|
}
|
|
|
|
.cf24-pager {
|
|
display: flex;
|
|
gap: var(--sp-12, 12px);
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-top: var(--sp-16, 16px);
|
|
}
|
|
|
|
/* 상세설명 HTML 원문 */
|
|
.cf24-label {
|
|
display: block;
|
|
margin: var(--sp-16, 16px) 0 var(--sp-8, 8px);
|
|
font-size: var(--text-caption, 12px);
|
|
font-weight: 500;
|
|
color: var(--color-midtone-gray, #737373);
|
|
}
|
|
|
|
.cf24-html {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
padding: var(--sp-12, 12px);
|
|
border: 1px solid var(--color-subtle-ash, #e5e5e5);
|
|
border-radius: var(--r-lg, 10px);
|
|
background: var(--color-canvas-white, #fff);
|
|
font-family: var(--font-geist-mono, ui-monospace, monospace);
|
|
font-size: 12px;
|
|
line-height: 1.6;
|
|
white-space: pre;
|
|
overflow: auto;
|
|
resize: vertical;
|
|
}
|
|
|
|
/* 읽기 전용은 배경으로 구분 — 편집 가능한 칸과 헷갈리지 않게 */
|
|
.cf24-html[readonly] {
|
|
background: var(--color-ghost-gray, #f2f2f2);
|
|
}
|
|
|
|
.cf24-note {
|
|
margin: var(--sp-12, 12px) 0 0;
|
|
padding: var(--sp-10, 10px) var(--sp-12, 12px);
|
|
border-left: 3px solid var(--color-subtle-ash, #e5e5e5);
|
|
background: var(--color-ghost-gray, #f2f2f2);
|
|
border-radius: var(--r-sm, 6px);
|
|
color: var(--color-midtone-gray, #737373);
|
|
font-size: var(--text-caption, 12px);
|
|
line-height: 1.7;
|
|
}
|
|
|
|
/* 넓은 로그 표는 카드 안에서만 가로 스크롤 */
|
|
.cf24-scroll {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.cf24-scroll code {
|
|
font-family: var(--font-geist-mono, ui-monospace, monospace);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.cf24-empty {
|
|
padding: var(--sp-24, 24px);
|
|
}
|
|
|
|
.cf24-empty h3 {
|
|
margin: 0 0 var(--sp-8, 8px);
|
|
}
|