feat(cupang): 설정 화면 2열 개편 — itemcode 다중선택 등록 + 활성/삭제
- 수동 등록 카드 제거
- 왼쪽: itemcode_db 전체 목록(낱개+세트) 다중선택, 등록된 항목 진회색+흰글씨
/cupang/api/products/all + /cupang/products/bulk(JSON 일괄 upsert)
- 오른쪽: 등록 제품명 목록 — 제품명/코드/상태 + 활성·비활성 토글 + 완전삭제
/products/{id}/active (토글), /products/{id}/delete (hard delete)
- ItemcodeReader.list_all 추가
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -237,11 +237,11 @@ class CupangDBStore:
|
|||||||
).fetchone()
|
).fetchone()
|
||||||
return self._product_serialize(row)
|
return self._product_serialize(row)
|
||||||
|
|
||||||
def deactivate_product(self, *, product_id: int) -> None:
|
def set_product_active(self, *, product_id: int, active: bool) -> None:
|
||||||
with self._pool.connection() as conn:
|
with self._pool.connection() as conn:
|
||||||
cur = conn.execute(
|
cur = conn.execute(
|
||||||
"UPDATE cupang_products SET active = FALSE WHERE id = %s",
|
"UPDATE cupang_products SET active = %s WHERE id = %s",
|
||||||
(product_id,),
|
(bool(active), product_id),
|
||||||
)
|
)
|
||||||
if cur.rowcount == 0:
|
if cur.rowcount == 0:
|
||||||
raise KeyError(product_id)
|
raise KeyError(product_id)
|
||||||
|
|||||||
@@ -114,9 +114,17 @@ class ItemcodeReader:
|
|||||||
비활성 상태이거나 조회 실패 시 빈 리스트(예외 비전파 — UI 는 수동 입력 폴백).
|
비활성 상태이거나 조회 실패 시 빈 리스트(예외 비전파 — UI 는 수동 입력 폴백).
|
||||||
"""
|
"""
|
||||||
q = (query or "").strip()
|
q = (query or "").strip()
|
||||||
if not self.enabled or not self._pool or not self._sql or not q:
|
if not q:
|
||||||
|
return []
|
||||||
|
return self._run(f"%{q}%", limit)
|
||||||
|
|
||||||
|
def list_all(self, *, limit: int = 2000) -> list[dict[str, Any]]:
|
||||||
|
"""전체 상품 목록(낱개+세트). 설정 화면 왼쪽 리스트용."""
|
||||||
|
return self._run("%", limit)
|
||||||
|
|
||||||
|
def _run(self, like: str, limit: int) -> list[dict[str, Any]]:
|
||||||
|
if not self.enabled or not self._pool or not self._sql:
|
||||||
return []
|
return []
|
||||||
like = f"%{q}%"
|
|
||||||
try:
|
try:
|
||||||
with self._pool.connection() as conn:
|
with self._pool.connection() as conn:
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import json
|
|||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request
|
from fastapi import APIRouter, Body, Depends, Form, HTTPException, Request
|
||||||
from fastapi.responses import (
|
from fastapi.responses import (
|
||||||
HTMLResponse,
|
HTMLResponse,
|
||||||
JSONResponse,
|
JSONResponse,
|
||||||
@@ -609,14 +609,54 @@ async def products_page(request: Request) -> HTMLResponse:
|
|||||||
"is_admin": is_admin(user),
|
"is_admin": is_admin(user),
|
||||||
"nav_items": build_erp_nav(user, active="cupang"),
|
"nav_items": build_erp_nav(user, active="cupang"),
|
||||||
"page_title": "쿠팡 밀크런 — 설정 (제품명)",
|
"page_title": "쿠팡 밀크런 — 설정 (제품명)",
|
||||||
"page_subtitle": "제품명 리스트 관리. itemcode_db 에서 검색해 등록하면 폼 드롭다운에 노출됩니다.",
|
"page_subtitle": "왼쪽 itemcode_db 목록에서 선택해 등록하면 폼 드롭다운에 노출됩니다.",
|
||||||
"products": store.list_products(include_inactive=True),
|
"products": store.list_products(include_inactive=True),
|
||||||
|
"registered_codes": [p["product_code"] for p in store.list_products(include_inactive=True)],
|
||||||
"search_enabled": bool(reader and reader.enabled),
|
"search_enabled": bool(reader and reader.enabled),
|
||||||
"search_reason": (reader.reason if reader else ""),
|
"search_reason": (reader.reason if reader else ""),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/products/all")
|
||||||
|
async def product_all(
|
||||||
|
request: Request, _: dict[str, Any] = Depends(_require_user)
|
||||||
|
) -> JSONResponse:
|
||||||
|
"""itemcode_db 전체 상품 목록(낱개+세트). 설정 화면 왼쪽 리스트 소스."""
|
||||||
|
reader = _itemcode(request)
|
||||||
|
return JSONResponse(
|
||||||
|
{
|
||||||
|
"enabled": bool(reader and reader.enabled),
|
||||||
|
"reason": (reader.reason if reader else "itemcode 리더 미초기화"),
|
||||||
|
"results": reader.list_all() if reader else [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/products/bulk")
|
||||||
|
async def product_bulk(
|
||||||
|
request: Request,
|
||||||
|
items: list[dict[str, Any]] = Body(..., embed=True),
|
||||||
|
user: dict[str, Any] = Depends(_require_user),
|
||||||
|
) -> JSONResponse:
|
||||||
|
"""선택한 상품들을 일괄 등록(upsert). body: {"items":[{"code","name"}, ...]}."""
|
||||||
|
store = _store(request)
|
||||||
|
if store is None:
|
||||||
|
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||||
|
added = 0
|
||||||
|
for it in items:
|
||||||
|
code = str(it.get("code") or "").strip()
|
||||||
|
name = str(it.get("name") or "").strip()
|
||||||
|
if not code or not name:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
store.upsert_product(product_code=code, product_name=name)
|
||||||
|
added += 1
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return JSONResponse({"ok": True, "added": added})
|
||||||
|
|
||||||
|
|
||||||
@router.post("/products")
|
@router.post("/products")
|
||||||
async def product_upsert(
|
async def product_upsert(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -637,17 +677,37 @@ async def product_upsert(
|
|||||||
return RedirectResponse(url="/cupang/products", status_code=303)
|
return RedirectResponse(url="/cupang/products", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/products/{product_id:int}/delete")
|
@router.post("/products/{product_id:int}/active")
|
||||||
async def product_delete(
|
async def product_set_active(
|
||||||
request: Request,
|
request: Request,
|
||||||
product_id: int,
|
product_id: int,
|
||||||
|
active: str = Form(...),
|
||||||
user: dict[str, Any] = Depends(_require_user),
|
user: dict[str, Any] = Depends(_require_user),
|
||||||
) -> RedirectResponse:
|
) -> RedirectResponse:
|
||||||
store = _store(request)
|
store = _store(request)
|
||||||
if store is None:
|
if store is None:
|
||||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||||
try:
|
try:
|
||||||
store.deactivate_product(product_id=product_id)
|
store.set_product_active(
|
||||||
|
product_id=product_id, active=active in ("1", "true", "on", "True")
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
raise HTTPException(status_code=404, detail="제품을 찾을 수 없습니다.")
|
||||||
|
return RedirectResponse(url="/cupang/products", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/products/{product_id:int}/delete")
|
||||||
|
async def product_delete(
|
||||||
|
request: Request,
|
||||||
|
product_id: int,
|
||||||
|
user: dict[str, Any] = Depends(_require_user),
|
||||||
|
) -> RedirectResponse:
|
||||||
|
"""완전 삭제(hard delete)."""
|
||||||
|
store = _store(request)
|
||||||
|
if store is None:
|
||||||
|
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||||
|
try:
|
||||||
|
store.delete_product(product_id=product_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise HTTPException(status_code=404, detail="제품을 찾을 수 없습니다.")
|
raise HTTPException(status_code=404, detail="제품을 찾을 수 없습니다.")
|
||||||
return RedirectResponse(url="/cupang/products", status_code=303)
|
return RedirectResponse(url="/cupang/products", status_code=303)
|
||||||
|
|||||||
@@ -9,102 +9,140 @@
|
|||||||
<a class="erp-btn erp-btn-outline" href="/cupang/">← 달력</a>
|
<a class="erp-btn erp-btn-outline" href="/cupang/">← 달력</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- itemcode_db 검색 → 등록 -->
|
<div class="cpg-prod-layout">
|
||||||
|
|
||||||
|
<!-- ── 왼쪽: itemcode_db 상품 목록 (다중선택 → 등록) ── -->
|
||||||
<div class="erp-card cpg-form-card">
|
<div class="erp-card cpg-form-card">
|
||||||
<div class="cpg-card-head">
|
<div class="cpg-card-head">
|
||||||
<h2>itemcode_db 에서 제품 검색·등록</h2>
|
<h2>itemcode_db 상품</h2>
|
||||||
<span class="erp-muted">
|
<span class="erp-muted">
|
||||||
{% if search_enabled %}제품코드/제품명으로 검색 후 "등록".{% else %}
|
{% if search_enabled %}선택(다중) 후 등록. 이미 등록된 항목은 진한 회색.{% else %}
|
||||||
상품 검색 비활성: {{ search_reason }} — 아래에서 수동 등록하세요.{% endif %}
|
검색 비활성: {{ search_reason }}{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if search_enabled %}
|
{% if search_enabled %}
|
||||||
<div class="cpg-inline-form">
|
<div class="cpg-inline-form" style="margin-bottom:10px;">
|
||||||
<input class="erp-input" type="text" id="cpg-prod-q" placeholder="제품코드 또는 제품명" style="min-width:240px" />
|
<input class="erp-input" type="text" id="cpg-prod-q" placeholder="이름/코드 필터" style="min-width:200px" />
|
||||||
<button type="button" class="erp-btn erp-btn-outline" id="cpg-prod-search">검색</button>
|
<button type="button" class="erp-btn erp-btn-primary" id="cpg-prod-register">선택 등록</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="cpg-prod-results" class="cpg-prod-results"></div>
|
<div id="cpg-src-list" class="cpg-src-list"><p class="erp-muted">불러오는 중…</p></div>
|
||||||
|
{% else %}
|
||||||
|
<p class="erp-muted">ITEMCODE_DB_URL / ITEMCODE_SEARCH_SQL 설정 후 사용 가능합니다.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 수동 등록 -->
|
<!-- ── 오른쪽: 등록된 제품명 ── -->
|
||||||
<div class="erp-card cpg-form-card">
|
|
||||||
<div class="cpg-card-head"><h2>제품명 수동 등록 / 수정</h2>
|
|
||||||
<span class="erp-muted">같은 제품코드는 덮어씁니다.</span></div>
|
|
||||||
<form method="post" action="/cupang/products" class="cpg-rule-grid">
|
|
||||||
<label class="erp-field"><span>제품코드 *</span>
|
|
||||||
<input class="erp-input" type="text" name="product_code" required placeholder="예: MS-1001" /></label>
|
|
||||||
<label class="erp-field"><span>제품명 *</span>
|
|
||||||
<input class="erp-input" type="text" name="product_name" required placeholder="예: 미라네 1호세트" /></label>
|
|
||||||
<label class="erp-field"><span>정렬</span>
|
|
||||||
<input class="erp-input" type="number" name="sort_order" value="0" /></label>
|
|
||||||
<div><button type="submit" class="erp-btn erp-btn-primary">등록</button></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 등록된 제품명 목록 -->
|
|
||||||
<div class="erp-card cpg-form-card">
|
<div class="erp-card cpg-form-card">
|
||||||
<div class="cpg-card-head"><h2>등록된 제품명 ({{ products|length }})</h2>
|
<div class="cpg-card-head"><h2>등록된 제품명 ({{ products|length }})</h2>
|
||||||
<span class="erp-muted">폼의 제품명 드롭다운에 노출됩니다.</span></div>
|
<span class="erp-muted">폼의 제품명 드롭다운에 노출</span></div>
|
||||||
<div class="erp-table-wrap">
|
<div class="erp-table-wrap">
|
||||||
<table class="erp-table">
|
<table class="erp-table">
|
||||||
<thead><tr><th>제품명</th><th>제품코드</th><th>정렬</th><th>상태</th><th></th></tr></thead>
|
<thead><tr><th>제품명</th><th>제품코드</th><th>상태</th><th>동작</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for p in products %}
|
{% for p in products %}
|
||||||
<tr {% if not p.active %}style="opacity:.55"{% endif %}>
|
<tr {% if not p.active %}style="opacity:.55"{% endif %}>
|
||||||
<td>{{ p.product_name }}</td>
|
<td>{{ p.product_name }}</td>
|
||||||
<td>{{ p.product_code }}</td>
|
<td>{{ p.product_code }}</td>
|
||||||
<td>{{ p.sort_order }}</td>
|
|
||||||
<td>{% if p.active %}<span class="erp-badge erp-badge-success">활성</span>{% else %}<span class="erp-badge erp-badge-neutral">비활성</span>{% endif %}</td>
|
<td>{% if p.active %}<span class="erp-badge erp-badge-success">활성</span>{% else %}<span class="erp-badge erp-badge-neutral">비활성</span>{% endif %}</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div class="cpg-row-actions">
|
||||||
{% if p.active %}
|
{% if p.active %}
|
||||||
<form method="post" action="/cupang/products/{{ p.id }}/delete" class="cpg-inline-form"
|
<form method="post" action="/cupang/products/{{ p.id }}/active" class="cpg-inline-form">
|
||||||
onsubmit="return confirm('비활성화합니다. 계속할까요?');">
|
<input type="hidden" name="active" value="0" />
|
||||||
<button type="submit" class="erp-btn erp-btn-danger">비활성화</button>
|
<button type="submit" class="erp-btn erp-btn-outline">비활성</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}—{% endif %}
|
{% else %}
|
||||||
|
<form method="post" action="/cupang/products/{{ p.id }}/active" class="cpg-inline-form">
|
||||||
|
<input type="hidden" name="active" value="1" />
|
||||||
|
<button type="submit" class="erp-btn erp-btn-outline">활성</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<form method="post" action="/cupang/products/{{ p.id }}/delete" class="cpg-inline-form"
|
||||||
|
onsubmit="return confirm('완전 삭제합니다. 계속할까요?');">
|
||||||
|
<button type="submit" class="erp-btn erp-btn-danger">삭제</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if not products %}
|
||||||
|
<tr><td colspan="4" class="erp-muted">등록된 제품명이 없습니다. 왼쪽에서 선택해 등록하세요.</td></tr>
|
||||||
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% if search_enabled %}
|
{% if search_enabled %}
|
||||||
|
<script type="application/json" id="cpg-registered">{{ registered_codes | tojson }}</script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
var q = document.getElementById("cpg-prod-q");
|
var listBox = document.getElementById("cpg-src-list");
|
||||||
var btn = document.getElementById("cpg-prod-search");
|
var filter = document.getElementById("cpg-prod-q");
|
||||||
var box = document.getElementById("cpg-prod-results");
|
var regBtn = document.getElementById("cpg-prod-register");
|
||||||
|
var registered = {};
|
||||||
|
try { (JSON.parse(document.getElementById("cpg-registered").textContent || "[]")).forEach(function(c){ registered[c]=true; }); } catch(e){}
|
||||||
|
var all = [];
|
||||||
|
var selected = {};
|
||||||
|
|
||||||
function esc(s){ var d=document.createElement("div"); d.textContent=s||""; return d.innerHTML; }
|
function esc(s){ var d=document.createElement("div"); d.textContent=s||""; return d.innerHTML; }
|
||||||
function run() {
|
|
||||||
var term = (q.value || "").trim();
|
function render() {
|
||||||
if (!term) { box.innerHTML = ""; return; }
|
var term = (filter.value || "").trim().toLowerCase();
|
||||||
box.innerHTML = '<p class="erp-muted">검색중…</p>';
|
var rows = all.filter(function (it) {
|
||||||
fetch("/cupang/api/products/search?q=" + encodeURIComponent(term))
|
if (!term) return true;
|
||||||
|
return (it.code + " " + it.name).toLowerCase().indexOf(term) >= 0;
|
||||||
|
});
|
||||||
|
if (!rows.length) { listBox.innerHTML = '<p class="erp-muted">결과 없음</p>'; return; }
|
||||||
|
var html = "";
|
||||||
|
rows.forEach(function (it) {
|
||||||
|
var cls = "cpg-src-item";
|
||||||
|
if (registered[it.code]) cls += " cpg-registered";
|
||||||
|
if (selected[it.code]) cls += " is-selected";
|
||||||
|
html += '<div class="' + cls + '" data-code="' + esc(it.code) + '">' +
|
||||||
|
'<span class="cpg-src-name">' + esc(it.name) + '</span>' +
|
||||||
|
'<span class="cpg-src-code">' + esc(it.code) + (it.type ? ' · ' + esc(it.type) : '') + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
});
|
||||||
|
listBox.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
listBox.addEventListener("click", function (e) {
|
||||||
|
var item = e.target.closest(".cpg-src-item");
|
||||||
|
if (!item) return;
|
||||||
|
var code = item.getAttribute("data-code");
|
||||||
|
if (selected[code]) { delete selected[code]; item.classList.remove("is-selected"); }
|
||||||
|
else { selected[code] = true; item.classList.add("is-selected"); }
|
||||||
|
});
|
||||||
|
|
||||||
|
filter.addEventListener("input", render);
|
||||||
|
|
||||||
|
regBtn.addEventListener("click", function () {
|
||||||
|
var items = all.filter(function (it) { return selected[it.code]; })
|
||||||
|
.map(function (it) { return { code: it.code, name: it.name }; });
|
||||||
|
if (!items.length) { alert("등록할 상품을 선택하세요."); return; }
|
||||||
|
regBtn.disabled = true;
|
||||||
|
fetch("/cupang/products/bulk", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ items: items })
|
||||||
|
}).then(function (r) { return r.json(); })
|
||||||
|
.then(function () { location.reload(); })
|
||||||
|
.catch(function () { regBtn.disabled = false; alert("등록 실패"); });
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch("/cupang/api/products/all")
|
||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
var rows = (data && data.results) || [];
|
all = (data && data.results) || [];
|
||||||
if (!rows.length) { box.innerHTML = '<p class="erp-muted">결과 없음</p>'; return; }
|
if (!all.length) { listBox.innerHTML = '<p class="erp-muted">itemcode_db 결과 없음</p>'; return; }
|
||||||
var html = '<table class="erp-table"><thead><tr><th>제품코드</th><th>제품명</th><th>구분</th><th></th></tr></thead><tbody>';
|
render();
|
||||||
rows.forEach(function (it) {
|
|
||||||
html += '<tr><td>' + esc(it.code) + '</td><td>' + esc(it.name) + '</td><td>' + esc(it.type) + '</td>' +
|
|
||||||
'<td><form method="post" action="/cupang/products" class="cpg-inline-form">' +
|
|
||||||
'<input type="hidden" name="product_code" value="' + esc(it.code) + '">' +
|
|
||||||
'<input type="hidden" name="product_name" value="' + esc(it.name) + '">' +
|
|
||||||
'<button type="submit" class="erp-btn erp-btn-outline">등록</button></form></td></tr>';
|
|
||||||
});
|
|
||||||
html += "</tbody></table>";
|
|
||||||
box.innerHTML = html;
|
|
||||||
})
|
})
|
||||||
.catch(function () { box.innerHTML = '<p class="erp-muted">검색 실패</p>'; });
|
.catch(function () { listBox.innerHTML = '<p class="erp-muted">목록 로드 실패</p>'; });
|
||||||
}
|
|
||||||
btn.addEventListener("click", run);
|
|
||||||
q.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); run(); } });
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -118,6 +118,35 @@
|
|||||||
.cpg-detail-grid dt { font-size: 12px; color: var(--color-midtone-gray); }
|
.cpg-detail-grid dt { font-size: 12px; color: var(--color-midtone-gray); }
|
||||||
.cpg-detail-grid dd { margin: 2px 0 0; font-size: 14px; }
|
.cpg-detail-grid dd { margin: 2px 0 0; font-size: 14px; }
|
||||||
|
|
||||||
|
/* ── 설정(제품명) 2열 레이아웃 ── */
|
||||||
|
.cpg-prod-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
gap: 16px; align-items: start;
|
||||||
|
}
|
||||||
|
@media (max-width: 980px) { .cpg-prod-layout { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
.cpg-src-list {
|
||||||
|
max-height: 520px; overflow-y: auto;
|
||||||
|
border: 1px solid var(--color-subtle-ash); border-radius: 10px;
|
||||||
|
}
|
||||||
|
.cpg-src-item {
|
||||||
|
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
|
||||||
|
padding: 8px 12px; cursor: pointer;
|
||||||
|
border-bottom: 1px solid var(--color-subtle-ash);
|
||||||
|
}
|
||||||
|
.cpg-src-item:last-child { border-bottom: 0; }
|
||||||
|
.cpg-src-item:hover { background: var(--color-ghost-gray); }
|
||||||
|
.cpg-src-name { font-size: 14px; font-weight: 500; }
|
||||||
|
.cpg-src-code { font-size: 12px; color: var(--color-midtone-gray); white-space: nowrap; }
|
||||||
|
/* 선택됨: 검정 테두리 강조 */
|
||||||
|
.cpg-src-item.is-selected { box-shadow: inset 0 0 0 2px var(--color-deep-black); }
|
||||||
|
/* 이미 등록됨: 진한 회색 배경 + 흰 글씨 */
|
||||||
|
.cpg-src-item.cpg-registered { background: #4b4b4b; }
|
||||||
|
.cpg-src-item.cpg-registered .cpg-src-name { color: #fff; }
|
||||||
|
.cpg-src-item.cpg-registered .cpg-src-code { color: #d4d4d4; }
|
||||||
|
.cpg-src-item.cpg-registered:hover { background: #3a3a3a; }
|
||||||
|
|
||||||
/* ── 상품 검색 드롭다운 ── */
|
/* ── 상품 검색 드롭다운 ── */
|
||||||
.cpg-search-pop {
|
.cpg-search-pop {
|
||||||
position: absolute; z-index: 50;
|
position: absolute; z-index: 50;
|
||||||
|
|||||||
Reference in New Issue
Block a user