From 92297145a8fe8719dc319528ec1e5f7cbab1c807 Mon Sep 17 00:00:00 2001 From: chpark Date: Wed, 27 May 2026 12:07:05 +0900 Subject: [PATCH] =?UTF-8?q?feat(payments):=20=EC=9E=85=EA=B8=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D/=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EB=93=9C=EB=9E=98=EA=B7=B8=20=EC=9D=B4=EB=8F=99=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SweetAlert2 팝업 제목 바를 잡고 마우스/터치로 옮길 수 있게 makeSwalDraggable 추가. 출고정산 입금관리 + 매입 입금관리 모달에 적용. --- src/app/(main)/m/admin/payments/page.tsx | 2 + src/app/(main)/m/admin/proc-payments/page.tsx | 3 + src/lib/swal-draggable.ts | 57 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/lib/swal-draggable.ts diff --git a/src/app/(main)/m/admin/payments/page.tsx b/src/app/(main)/m/admin/payments/page.tsx index d65ff0a..9d53f01 100644 --- a/src/app/(main)/m/admin/payments/page.tsx +++ b/src/app/(main)/m/admin/payments/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useState, useCallback } from "react"; import { RefreshCcw } from "lucide-react"; import Swal from "sweetalert2"; +import { makeSwalDraggable } from "@/lib/swal-draggable"; interface Order { OBJID: string; ORDER_NO: string; ORDER_DATE: string; COMPANY_NAME: string; STATUS: string; TOTAL_AMOUNT: number; PAID_AMOUNT: number } const fmt = (n: number) => Number(n || 0).toLocaleString("ko-KR"); @@ -58,6 +59,7 @@ export default function PaymentsPage() { title: `${o.COMPANY_NAME} 입금 등록`, html: `미입금 ₩${fmt(remain)}`, input: "number", inputValue: remain, showCancelButton: true, confirmButtonText: "입금 처리", confirmButtonColor: "#0f766e", + didOpen: () => makeSwalDraggable(), }); if (!r.isConfirmed) return; const amt = Number(r.value); diff --git a/src/app/(main)/m/admin/proc-payments/page.tsx b/src/app/(main)/m/admin/proc-payments/page.tsx index cf6d9cf..f85728a 100644 --- a/src/app/(main)/m/admin/proc-payments/page.tsx +++ b/src/app/(main)/m/admin/proc-payments/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useState, useMemo, useCallback } from "react"; import Swal from "sweetalert2"; import { SearchableSelect } from "@/components/ui/searchable-select"; import { Loading } from "@/components/ui/loading"; +import { makeSwalDraggable } from "@/lib/swal-draggable"; interface Proc { OBJID: string; @@ -105,6 +106,7 @@ export default function ProcPaymentsPage() { confirmButtonColor: "#0f766e", cancelButtonText: "취소", focusConfirm: false, + didOpen: () => makeSwalDraggable(), preConfirm: () => { const a = (document.getElementById("sw-amount") as HTMLInputElement)?.value; const m = (document.getElementById("sw-method") as HTMLInputElement)?.value; @@ -160,6 +162,7 @@ export default function ProcPaymentsPage() { confirmButtonColor: "#0f766e", denyButtonColor: "#dc2626", focusConfirm: false, + didOpen: () => makeSwalDraggable(), preConfirm: () => ({ paidDate: (document.getElementById("sw-date") as HTMLInputElement)?.value || undefined, amount: Number((document.getElementById("sw-amount") as HTMLInputElement)?.value) || Number(p.TOTAL_AMOUNT), diff --git a/src/lib/swal-draggable.ts b/src/lib/swal-draggable.ts new file mode 100644 index 0000000..569b899 --- /dev/null +++ b/src/lib/swal-draggable.ts @@ -0,0 +1,57 @@ +// SweetAlert2 팝업을 제목 바를 잡고 마우스/터치로 이동 가능하게 만든다. +// Swal.fire({ didOpen: () => makeSwalDraggable() }) 형태로 사용. +export function makeSwalDraggable() { + if (typeof document === "undefined") return; + const popup = document.querySelector(".swal2-popup"); + if (!popup) return; + const handle = popup.querySelector(".swal2-title") ?? popup; + + let committedX = 0, committedY = 0; // 지금까지 이동 누적 + let startX = 0, startY = 0; + let dragging = false; + + handle.style.cursor = "move"; + handle.style.userSelect = "none"; + handle.style.touchAction = "none"; + + const point = (e: MouseEvent | TouchEvent) => + "touches" in e ? e.touches[0] ?? (e as TouchEvent).changedTouches[0] : (e as MouseEvent); + + const onMove = (e: MouseEvent | TouchEvent) => { + if (!dragging) return; + const p = point(e); + const nx = committedX + (p.clientX - startX); + const ny = committedY + (p.clientY - startY); + popup.style.transform = `translate(${nx}px, ${ny}px)`; + if ("touches" in e) e.preventDefault(); + }; + + const onUp = (e: MouseEvent | TouchEvent) => { + if (!dragging) return; + dragging = false; + const p = point(e); + committedX += p.clientX - startX; + committedY += p.clientY - startY; + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + document.removeEventListener("touchmove", onMove); + document.removeEventListener("touchend", onUp); + }; + + const onDown = (e: MouseEvent | TouchEvent) => { + // 입력/버튼 위에서 시작하면 드래그 안 함 + const target = e.target as HTMLElement; + if (target.closest("input, textarea, select, button, a")) return; + const p = point(e); + startX = p.clientX; + startY = p.clientY; + dragging = true; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + document.addEventListener("touchmove", onMove, { passive: false }); + document.addEventListener("touchend", onUp); + }; + + handle.addEventListener("mousedown", onDown); + handle.addEventListener("touchstart", onDown, { passive: true }); +}