feat(malaysia): 랙 보기 드래그 재배치·저장·인쇄 미리보기
랙 보기에서 칸을 드래그해 다른 칸으로 구성을 옮긴다(채워진 칸끼리 맞교환, Side A↔B 가능). 칸 번호는 유지되고 수량 합계도 불변(위치만). 저장하면 그 배치로 기록되며, 재고조사 '이전값 불러오기'가 이 최종 위치를 불러온다. - db.reposition_rack: 칸(cell_code) 배치만 교체, daily_stocktake_line 보존, 확정본도 허용(취소만 불가). 위치 이동은 전산재고에 영향 없음 - db.previous_rack_entries: 정렬을 랙 저장시각(MAX created_at) 기준으로 통일 → '이전값 불러오기'가 랙 보기 최종 위치와 일치 - POST /malaysia/rack/save: latest_rack_stocktake 대상 reposition - rack_view.html + malaysia_rack_view.js: HTML5 드래그 이동/swap, 칸 체크 선택, 저장(병렬배열 직렬화), 인쇄 미리보기 모달 - 인쇄: 선택한 칸을 칸마다 A4 가로 1장, 굵은 대형 글씨(@media print), 미리보기 후 인쇄 - i18n 항목 추가, malaysia.js 캐시버스트 v20260615c Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -431,13 +431,67 @@ class MalaysiaStockStore:
|
||||
"JOIN daily_stocktake_rack r ON r.stocktake_id = s.id "
|
||||
"WHERE s.warehouse_code = %s AND s.id <> %s AND s.status <> 'cancelled' "
|
||||
"GROUP BY s.id "
|
||||
"ORDER BY s.stocktake_date DESC, s.id DESC LIMIT 1",
|
||||
"ORDER BY MAX(r.created_at) DESC, s.id DESC LIMIT 1",
|
||||
(warehouse_code, exclude_stocktake_id),
|
||||
).fetchone()
|
||||
if not head:
|
||||
return []
|
||||
return self.list_rack_entries(stocktake_id=head["id"])
|
||||
|
||||
def reposition_rack(
|
||||
self, *, stocktake_id: int, entries: list[dict[str, Any]]
|
||||
) -> dict[str, Any]:
|
||||
"""랙 위치 재배치 전용(랙 보기 드래그 저장).
|
||||
|
||||
daily_stocktake_rack 의 칸(cell_code) 배치만 교체한다. 위치 이동은
|
||||
SKU 총수량을 바꾸지 않으므로 daily_stocktake_line 은 건드리지 않는다
|
||||
(확정본의 집계/전산재고 보존). 확정(finalized) 조사도 허용 — 위치만
|
||||
변경. 취소된 조사는 불가. entries 형식은 replace_rack 과 동일.
|
||||
"""
|
||||
valid_cells = set(store.rack_cell_codes())
|
||||
clean: list[tuple[str, str, int, int]] = []
|
||||
for e in entries:
|
||||
cell = (e.get("cell_code") or "").strip().upper()
|
||||
if cell not in valid_cells:
|
||||
raise ValueError(f"알 수 없는 랙 위치: {cell or '(빈값)'}")
|
||||
code = store.validate_stocktake_code(str(e.get("sku_code") or ""))
|
||||
upb = store.validate_qty(
|
||||
e.get("units_per_box"), allow_zero=False, allow_negative=False
|
||||
)
|
||||
box = store.validate_qty(
|
||||
e.get("box_count"), allow_zero=False, allow_negative=False
|
||||
)
|
||||
clean.append((cell, code, upb, box))
|
||||
|
||||
cell_seq: dict[str, int] = {}
|
||||
with self._pool.connection() as conn:
|
||||
with conn.transaction():
|
||||
head = conn.execute(
|
||||
"SELECT status FROM daily_stocktake WHERE id = %s FOR UPDATE",
|
||||
(stocktake_id,),
|
||||
).fetchone()
|
||||
if not head:
|
||||
raise KeyError(stocktake_id)
|
||||
if head["status"] == "cancelled":
|
||||
raise ValueError("취소된 조사는 수정할 수 없습니다.")
|
||||
conn.execute(
|
||||
"DELETE FROM daily_stocktake_rack WHERE stocktake_id = %s",
|
||||
(stocktake_id,),
|
||||
)
|
||||
for cell, code, upb, box in clean:
|
||||
ln = cell_seq.get(cell, 0)
|
||||
cell_seq[cell] = ln + 1
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO daily_stocktake_rack
|
||||
(stocktake_id, cell_code, line_no, sku_code,
|
||||
units_per_box, box_count)
|
||||
VALUES (%s,%s,%s,%s,%s,%s)
|
||||
""",
|
||||
(stocktake_id, cell, ln, code, upb, box),
|
||||
)
|
||||
return {"entries": len(clean)}
|
||||
|
||||
def delete_stocktake(self, *, stocktake_id: int) -> None:
|
||||
"""재고조사 완전 삭제(헤더+라인 CASCADE). 슈퍼관리자 전용 동작.
|
||||
|
||||
|
||||
@@ -209,6 +209,32 @@ async def rack_view(request: Request) -> HTMLResponse:
|
||||
return render_template(request, "malaysia/rack_view.html", ctx)
|
||||
|
||||
|
||||
@router.post("/rack/save")
|
||||
async def rack_save(
|
||||
request: Request, user: dict[str, Any] = Depends(_require_user)
|
||||
) -> RedirectResponse:
|
||||
"""랙 보기에서 드래그로 재배치한 칸 구성을 저장(위치만 변경).
|
||||
|
||||
대상은 랙 보기가 보여주는 조사(latest_rack_stocktake). 수량 합계는
|
||||
불변이며 확정본도 허용. 폼은 stocktake_detail 과 동일한 병렬배열
|
||||
(rk_cell/rk_sku/rk_upb/rk_box) → _parse_rack_form 재사용.
|
||||
"""
|
||||
st = _store(request)
|
||||
if st is None:
|
||||
raise HTTPException(status_code=503, detail="malaysia_stock_db 미설정")
|
||||
form = await request.form()
|
||||
wh = str(form.get("warehouse_code") or "").strip() or _selected_wh(request, st)
|
||||
latest = st.latest_rack_stocktake(warehouse_code=wh)
|
||||
if not latest:
|
||||
raise HTTPException(status_code=400, detail="저장할 랙 재고조사가 없습니다.")
|
||||
entries = _parse_rack_form(form)
|
||||
try:
|
||||
st.reposition_rack(stocktake_id=latest["id"], entries=entries)
|
||||
except (KeyError, ValueError) as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
return RedirectResponse(url=f"/malaysia/rack?wh={wh}", status_code=303)
|
||||
|
||||
|
||||
@router.post("/reset")
|
||||
async def reset_data(
|
||||
request: Request, user: dict[str, Any] = Depends(_require_user)
|
||||
|
||||
@@ -32,4 +32,4 @@
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:8px;">
|
||||
<button type="button" id="mys-lang-toggle" class="erp-btn erp-btn-outline">ENG</button>
|
||||
</div>
|
||||
<script src="/static/malaysia.js?v=20260615b"></script>
|
||||
<script src="/static/malaysia.js?v=20260615c"></script>
|
||||
|
||||
@@ -9,19 +9,79 @@
|
||||
.rv-cell {
|
||||
border:1px solid #cbd5e1; border-radius:6px; padding:5px;
|
||||
background:#f8fafc; min-height:80px; display:flex; flex-direction:column; min-width:0;
|
||||
cursor:grab; transition:box-shadow .12s, border-color .12s, background .12s;
|
||||
}
|
||||
.rv-cell.is-empty { background:#fff; }
|
||||
.rv-cell.is-selected { border-color:#2563eb; box-shadow:0 0 0 2px #93c5fd inset; background:#eff6ff; }
|
||||
.rv-cell.is-dragover { border-color:#2563eb; background:#dbeafe; }
|
||||
.rv-cell.dragging { opacity:.45; }
|
||||
.rv-cell-head {
|
||||
display:flex; align-items:center; justify-content:space-between;
|
||||
font-size:11px; font-weight:600; color:#475569; margin-bottom:4px;
|
||||
border-bottom:1px solid #e2e8f0; padding-bottom:3px;
|
||||
}
|
||||
.rv-cell-head .rv-pick {
|
||||
flex:0 0 auto; width:16px; height:16px; margin:0; cursor:pointer;
|
||||
}
|
||||
.rv-items { display:flex; flex-direction:column; gap:3px; }
|
||||
.rv-item { font-size:11px; line-height:1.25; }
|
||||
.rv-item .rv-name { font-weight:600; color:#0f172a; }
|
||||
.rv-item .rv-calc { color:#475569; }
|
||||
.rv-item .rv-qty { color:#2563eb; font-weight:600; }
|
||||
.rv-empty-note { font-size:11px; color:#cbd5e1; }
|
||||
|
||||
/* ── 인쇄 미리보기 모달 ── */
|
||||
.rvp-overlay {
|
||||
display:none; position:fixed; inset:0; z-index:2000;
|
||||
background:rgba(15,23,42,.55); overflow:auto; padding:24px;
|
||||
}
|
||||
.rvp-overlay.is-open { display:block; }
|
||||
.rvp-toolbar {
|
||||
position:sticky; top:0; display:flex; gap:8px; justify-content:flex-end;
|
||||
align-items:center; margin-bottom:16px;
|
||||
}
|
||||
.rvp-toolbar .rvp-title { margin-right:auto; color:#fff; font-weight:700; font-size:16px; }
|
||||
.rvp-pages { display:flex; flex-direction:column; gap:18px; align-items:center; }
|
||||
/* 화면 미리보기: A4 가로 비율(297:210) 시트 */
|
||||
.rvp-sheet {
|
||||
background:#fff; width:min(100%, 1000px); aspect-ratio:297/210;
|
||||
box-shadow:0 8px 24px rgba(0,0,0,.3); border-radius:4px;
|
||||
display:flex; flex-direction:column; padding:4% 5%; box-sizing:border-box;
|
||||
}
|
||||
.rvp-cell-code {
|
||||
font-weight:900; line-height:1; letter-spacing:.5px;
|
||||
font-size:9vmin; color:#0f172a; border-bottom:4px solid #0f172a;
|
||||
padding-bottom:2%; margin-bottom:3%; text-align:center;
|
||||
}
|
||||
.rvp-list { flex:1 1 auto; display:flex; flex-direction:column; justify-content:center; gap:3%; }
|
||||
.rvp-line { text-align:center; }
|
||||
.rvp-line .rvp-name { display:block; font-weight:900; font-size:6.5vmin; color:#0f172a; line-height:1.1; }
|
||||
.rvp-line .rvp-calc { display:block; font-weight:800; font-size:5vmin; color:#1e293b; margin-top:1%; }
|
||||
.rvp-empty { text-align:center; font-weight:900; font-size:7vmin; color:#94a3b8; margin:auto 0; }
|
||||
|
||||
/* ── 실제 인쇄 ── */
|
||||
@media print {
|
||||
@page { size: A4 landscape; margin: 8mm; }
|
||||
body * { visibility: hidden !important; }
|
||||
.rvp-overlay, .rvp-overlay * { visibility: visible !important; }
|
||||
.rvp-overlay {
|
||||
display:block !important; position:static !important; padding:0 !important;
|
||||
background:#fff !important; overflow:visible !important;
|
||||
}
|
||||
.rvp-toolbar { display:none !important; }
|
||||
.rvp-pages { gap:0 !important; }
|
||||
.rvp-sheet {
|
||||
width:100% !important; height:100vh !important; aspect-ratio:auto !important;
|
||||
box-shadow:none !important; border-radius:0 !important; page-break-after:always;
|
||||
padding:0 !important;
|
||||
}
|
||||
.rvp-sheet:last-child { page-break-after:auto; }
|
||||
/* 인쇄 시 글씨 더 크게(멀리서도 보이게) — cm/vh 기준 */
|
||||
.rvp-cell-code { font-size:3cm; border-bottom-width:6px; }
|
||||
.rvp-line .rvp-name { font-size:2.4cm; }
|
||||
.rvp-line .rvp-calc { font-size:1.6cm; }
|
||||
.rvp-empty { font-size:2.5cm; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -46,7 +106,20 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="rv-board" style="margin-top:10px;">
|
||||
{% if stocktake %}
|
||||
<div class="erp-page-actions" style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<span class="erp-muted i18n" style="flex:1 1 auto;min-width:0;">칸을 드래그해 다른 칸으로 옮기면 구성이 이동(채워진 칸끼리는 맞교환)됩니다. 칸 번호는 그대로입니다. 인쇄할 칸은 체크 후 인쇄하세요.</span>
|
||||
<button type="button" id="rv-print-btn" class="erp-btn erp-btn-outline">인쇄 미리보기</button>
|
||||
<button type="button" id="rv-save-btn" class="erp-btn erp-btn-primary">저장</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form id="rv-save-form" method="post" action="/malaysia/rack/save" style="display:none;">
|
||||
<input type="hidden" name="warehouse_code" value="{{ selected_wh }}" />
|
||||
<div id="rv-save-fields"></div>
|
||||
</form>
|
||||
|
||||
<div class="rv-board" id="rv-board" style="margin-top:10px;">
|
||||
{% for side in rack_layout %}
|
||||
<div class="rv-side">
|
||||
<h3>Side {{ side.side }}</h3>
|
||||
@@ -55,19 +128,14 @@
|
||||
{% for col in row.cols %}
|
||||
{% for cell in col %}
|
||||
{% set entries = rack_by_cell.get(cell, []) %}
|
||||
<div class="rv-cell {% if not entries %}is-empty{% endif %}">
|
||||
<div class="rv-cell {% if not entries %}is-empty{% endif %}"
|
||||
data-cell="{{ cell }}" draggable="true"
|
||||
data-entries='[{% for e in entries %}{"sku":"{{ e.sku_code }}","ko":{{ (ko_names.get(e.sku_code, e.item_name)) | tojson }},"en":{{ e.item_name | tojson }},"upb":{{ e.units_per_box }},"box":{{ e.box_count }}}{% if not loop.last %},{% endif %}{% endfor %}]'>
|
||||
<div class="rv-cell-head">
|
||||
<span>{{ cell }}</span>
|
||||
</div>
|
||||
<div class="rv-items">
|
||||
{% for e in entries %}
|
||||
<div class="rv-item">
|
||||
<span class="rv-name" data-en="{{ e.item_name }}" data-ko="{{ ko_names.get(e.sku_code, e.item_name) }}">{{ e.item_name }}</span><br>
|
||||
<span class="rv-calc">{{ "{:,}".format(e.units_per_box) }}<span class="i18n">개</span> × {{ e.box_count }}<span class="i18n">박스</span> = <span class="rv-qty">{{ "{:,}".format(e.total) }}</span></span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not entries %}<span class="rv-empty-note">—</span>{% endif %}
|
||||
<span class="rv-code">{{ cell }}</span>
|
||||
<input type="checkbox" class="rv-pick" title="인쇄 선택" draggable="false" />
|
||||
</div>
|
||||
<div class="rv-items"><!-- JS 렌더 --></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
@@ -77,5 +145,16 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 인쇄 미리보기 모달 -->
|
||||
<div class="rvp-overlay" id="rvp-overlay">
|
||||
<div class="rvp-toolbar">
|
||||
<span class="rvp-title i18n">인쇄 미리보기</span>
|
||||
<button type="button" id="rvp-print" class="erp-btn erp-btn-primary">인쇄</button>
|
||||
<button type="button" id="rvp-close" class="erp-btn erp-btn-outline">닫기</button>
|
||||
</div>
|
||||
<div class="rvp-pages" id="rvp-pages"></div>
|
||||
</div>
|
||||
</section>
|
||||
<script src="/static/malaysia_rack_view.js?v=20260615a"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
"창고 랙 보기": "Rack View",
|
||||
"창고 랙": "Warehouse Rack",
|
||||
"랙 보기": "Rack View",
|
||||
"인쇄 미리보기": "Print Preview",
|
||||
"인쇄": "Print",
|
||||
"저장": "Save",
|
||||
"닫기": "Close",
|
||||
"칸을 드래그해 다른 칸으로 옮기면 구성이 이동(채워진 칸끼리는 맞교환)됩니다. 칸 번호는 그대로입니다. 인쇄할 칸은 체크 후 인쇄하세요.":
|
||||
"Drag a cell onto another to move its contents (filled cells swap). Cell numbers stay. Check cells, then print.",
|
||||
"기준 재고조사": "Based on stocktake",
|
||||
"표시할 재고조사가 없습니다.": "No stocktake to display.",
|
||||
"박스": "box",
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/* 말레이시아 창고 랙 보기 — 드래그 재배치 + 저장 + 인쇄 미리보기.
|
||||
*
|
||||
* - 각 .rv-cell 의 data-entries(JSON)가 진실원천. 드래그 드롭으로 두 칸의
|
||||
* 구성(entries)을 맞교환(빈 칸이면 이동). 칸 번호(cell)는 그대로.
|
||||
* - 저장: 모든 칸의 현재 구성을 rk_cell/rk_sku/rk_upb/rk_box 병렬배열 hidden
|
||||
* 으로 만들어 POST /malaysia/rack/save (서버 reposition_rack).
|
||||
* - 인쇄: 체크한 칸을 칸마다 A4 가로 1장으로 미리보기 후 window.print().
|
||||
* - 언어: localStorage('mys_lang'). 셀/미리보기 텍스트를 현재 언어로 렌더.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function lang() { return localStorage.getItem("mys_lang") === "en" ? "en" : "ko"; }
|
||||
function t(ko, en) { return lang() === "en" ? en : ko; }
|
||||
function fmt(n) { return Number(n || 0).toLocaleString(); }
|
||||
function nameOf(e) { return (lang() === "en" ? e.en : e.ko) || e.sku; }
|
||||
|
||||
function getEntries(cell) {
|
||||
try { return JSON.parse(cell.getAttribute("data-entries") || "[]"); }
|
||||
catch (_) { return []; }
|
||||
}
|
||||
function setEntries(cell, arr) {
|
||||
cell.setAttribute("data-entries", JSON.stringify(arr));
|
||||
}
|
||||
|
||||
function renderCell(cell) {
|
||||
var items = cell.querySelector(".rv-items");
|
||||
if (!items) return;
|
||||
var arr = getEntries(cell);
|
||||
items.innerHTML = "";
|
||||
if (!arr.length) {
|
||||
cell.classList.add("is-empty");
|
||||
var s = document.createElement("span");
|
||||
s.className = "rv-empty-note";
|
||||
s.textContent = "—";
|
||||
items.appendChild(s);
|
||||
return;
|
||||
}
|
||||
cell.classList.remove("is-empty");
|
||||
arr.forEach(function (e) {
|
||||
var total = (Number(e.upb) || 0) * (Number(e.box) || 0);
|
||||
var wrap = document.createElement("div");
|
||||
wrap.className = "rv-item";
|
||||
var nm = document.createElement("span");
|
||||
nm.className = "rv-name";
|
||||
nm.textContent = nameOf(e);
|
||||
var calc = document.createElement("span");
|
||||
calc.className = "rv-calc";
|
||||
calc.innerHTML = fmt(e.upb) + t("개", "ea") + " × " + fmt(e.box) + t("박스", "box")
|
||||
+ " = <span class=\"rv-qty\">" + fmt(total) + "</span>";
|
||||
wrap.appendChild(nm);
|
||||
wrap.appendChild(document.createElement("br"));
|
||||
wrap.appendChild(calc);
|
||||
items.appendChild(wrap);
|
||||
});
|
||||
}
|
||||
function renderAll() {
|
||||
document.querySelectorAll(".rv-cell").forEach(renderCell);
|
||||
}
|
||||
|
||||
// ── 드래그 재배치 ──
|
||||
var dragSrc = null;
|
||||
var dirty = false;
|
||||
function markDirty() {
|
||||
dirty = true;
|
||||
var b = document.getElementById("rv-save-btn");
|
||||
if (b) b.classList.add("is-dirty");
|
||||
}
|
||||
function cleanup() {
|
||||
if (dragSrc) dragSrc.classList.remove("dragging");
|
||||
dragSrc = null;
|
||||
document.querySelectorAll(".rv-cell.is-dragover")
|
||||
.forEach(function (c) { c.classList.remove("is-dragover"); });
|
||||
}
|
||||
function onDragStart(e) {
|
||||
var cell = e.target.closest(".rv-cell");
|
||||
if (!cell) return;
|
||||
dragSrc = cell;
|
||||
cell.classList.add("dragging");
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
try { e.dataTransfer.setData("text/plain", cell.getAttribute("data-cell")); } catch (_) {}
|
||||
}
|
||||
function onDragOver(e) {
|
||||
if (!dragSrc) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
var cell = e.target.closest(".rv-cell");
|
||||
if (cell && cell !== dragSrc) cell.classList.add("is-dragover");
|
||||
}
|
||||
function onDragLeave(e) {
|
||||
var cell = e.target.closest(".rv-cell");
|
||||
if (cell) cell.classList.remove("is-dragover");
|
||||
}
|
||||
function onDrop(e) {
|
||||
e.preventDefault();
|
||||
var dst = e.target.closest(".rv-cell");
|
||||
if (!dst || !dragSrc || dst === dragSrc) { cleanup(); return; }
|
||||
var a = getEntries(dragSrc);
|
||||
var b = getEntries(dst);
|
||||
setEntries(dragSrc, b);
|
||||
setEntries(dst, a);
|
||||
renderCell(dragSrc);
|
||||
renderCell(dst);
|
||||
markDirty();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// ── 인쇄 선택(체크박스) → 셀 강조 ──
|
||||
function onChange(e) {
|
||||
var cb = e.target;
|
||||
if (!cb.classList || !cb.classList.contains("rv-pick")) return;
|
||||
var cell = cb.closest(".rv-cell");
|
||||
if (cell) cell.classList.toggle("is-selected", cb.checked);
|
||||
}
|
||||
|
||||
// ── 저장 ──
|
||||
function addHidden(parent, name, val) {
|
||||
var i = document.createElement("input");
|
||||
i.type = "hidden";
|
||||
i.name = name;
|
||||
i.value = val;
|
||||
parent.appendChild(i);
|
||||
}
|
||||
function save() {
|
||||
var fields = document.getElementById("rv-save-fields");
|
||||
var form = document.getElementById("rv-save-form");
|
||||
if (!fields || !form) return;
|
||||
fields.innerHTML = "";
|
||||
document.querySelectorAll(".rv-cell").forEach(function (cell) {
|
||||
var cc = cell.getAttribute("data-cell");
|
||||
getEntries(cell).forEach(function (e) {
|
||||
addHidden(fields, "rk_cell", cc);
|
||||
addHidden(fields, "rk_sku", e.sku);
|
||||
addHidden(fields, "rk_upb", e.upb);
|
||||
addHidden(fields, "rk_box", e.box);
|
||||
});
|
||||
});
|
||||
dirty = false;
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// ── 인쇄 미리보기 ──
|
||||
function openPreview() {
|
||||
var picked = [].slice.call(document.querySelectorAll(".rv-cell")).filter(function (c) {
|
||||
var cb = c.querySelector(".rv-pick");
|
||||
return cb && cb.checked;
|
||||
});
|
||||
if (!picked.length) {
|
||||
alert(t("인쇄할 칸을 먼저 선택하세요.", "Select cells to print first."));
|
||||
return;
|
||||
}
|
||||
var pages = document.getElementById("rvp-pages");
|
||||
pages.innerHTML = "";
|
||||
picked.forEach(function (cell) {
|
||||
var arr = getEntries(cell);
|
||||
var sheet = document.createElement("div");
|
||||
sheet.className = "rvp-sheet";
|
||||
var code = document.createElement("div");
|
||||
code.className = "rvp-cell-code";
|
||||
code.textContent = cell.getAttribute("data-cell");
|
||||
sheet.appendChild(code);
|
||||
var list = document.createElement("div");
|
||||
list.className = "rvp-list";
|
||||
if (!arr.length) {
|
||||
var em = document.createElement("div");
|
||||
em.className = "rvp-empty";
|
||||
em.textContent = t("(비어 있음)", "(empty)");
|
||||
list.appendChild(em);
|
||||
} else {
|
||||
arr.forEach(function (e) {
|
||||
var total = (Number(e.upb) || 0) * (Number(e.box) || 0);
|
||||
var line = document.createElement("div");
|
||||
line.className = "rvp-line";
|
||||
var nm = document.createElement("span");
|
||||
nm.className = "rvp-name";
|
||||
nm.textContent = nameOf(e);
|
||||
var calc = document.createElement("span");
|
||||
calc.className = "rvp-calc";
|
||||
calc.textContent = fmt(e.upb) + t("개", "ea") + " × " + fmt(e.box)
|
||||
+ t("박스", "box") + " = " + fmt(total);
|
||||
line.appendChild(nm);
|
||||
line.appendChild(calc);
|
||||
list.appendChild(line);
|
||||
});
|
||||
}
|
||||
sheet.appendChild(list);
|
||||
pages.appendChild(sheet);
|
||||
});
|
||||
document.getElementById("rvp-overlay").classList.add("is-open");
|
||||
}
|
||||
function closePreview() {
|
||||
document.getElementById("rvp-overlay").classList.remove("is-open");
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
renderAll();
|
||||
var board = document.getElementById("rv-board");
|
||||
if (board) {
|
||||
board.addEventListener("dragstart", onDragStart);
|
||||
board.addEventListener("dragover", onDragOver);
|
||||
board.addEventListener("dragleave", onDragLeave);
|
||||
board.addEventListener("drop", onDrop);
|
||||
board.addEventListener("dragend", cleanup);
|
||||
board.addEventListener("change", onChange);
|
||||
}
|
||||
var sb = document.getElementById("rv-save-btn");
|
||||
if (sb) sb.addEventListener("click", save);
|
||||
var pb = document.getElementById("rv-print-btn");
|
||||
if (pb) pb.addEventListener("click", openPreview);
|
||||
var pc = document.getElementById("rvp-close");
|
||||
if (pc) pc.addEventListener("click", closePreview);
|
||||
var pp = document.getElementById("rvp-print");
|
||||
if (pp) pp.addEventListener("click", function () { window.print(); });
|
||||
var lt = document.getElementById("mys-lang-toggle");
|
||||
if (lt) lt.addEventListener("click", function () { setTimeout(renderAll, 0); });
|
||||
window.addEventListener("beforeunload", function (ev) {
|
||||
if (dirty) { ev.preventDefault(); ev.returnValue = ""; }
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user