feat(expense): 첨부 뷰어 모달 — 이미지 미리보기 + 펜 주석

승인 대기 페이지의 "첨부" 텍스트 링크를 클립 아이콘 버튼으로 교체.
클릭 시 모달 팝업으로 이미지 미리보기 + 마우스 펜 주석 + 다운로드.

뷰어 컴포넌트 (전역, 다른 페이지에서도 재사용 가능):
- 좌측: 첨부 목록 (썸네일/메타)
- 우측: 캔버스 스택 (img + 투명 canvas)
- 도구: 펜/지우개, 색상 4종, 굵기 3종, 전체 지우기, 합성 PNG 저장, 원본 열기
- 이미지 외 파일은 다운로드 링크만 표시
- ESC/배경 클릭으로 닫기, pointer events 지원

전역 노출: window.ErpAttachViewer.{open, openFor, close}
This commit is contained in:
2026-05-29 03:40:03 +09:00
parent d4af2dc064
commit 151de492ad
4 changed files with 450 additions and 1 deletions
@@ -40,7 +40,13 @@
{{ "{:,}".format(it.amount) }} 원
</td>
<td style="text-align: right;">
<a class="erp-btn erp-btn-ghost erp-btn-sm" href="/expense/api/items/{{ it.id }}/attachments" target="_blank">첨부</a>
<button class="erp-btn erp-btn-outline erp-btn-sm js-attach" title="첨부 보기"
aria-label="첨부 보기">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.44 11.05 12.25 20.24a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66L9.41 17.41a2 2 0 0 1-2.83-2.83l8.49-8.48"/>
</svg>
</button>
<button class="erp-btn erp-btn-outline erp-btn-sm js-reject">반려</button>
<button class="erp-btn erp-btn-primary erp-btn-sm js-approve">승인</button>
</td>
@@ -68,6 +74,11 @@ document.getElementById("pend-tbody").addEventListener("click", async (e) => {
const tr = btn.closest("tr[data-id]");
const id = tr.dataset.id;
if (btn.classList.contains("js-attach")) {
const owner = tr.children[1]?.textContent?.trim() || "";
window.ErpAttachViewer.openFor(id, { title: `첨부 — ${owner}` });
return;
}
if (btn.classList.contains("js-approve")) {
if (!confirm("승인할까요?")) return;
const res = await fetch(`/expense/api/items/${id}/approve`, { method: "POST" });