From 441bf00b95ff2f49c93fdfdff9fb23f0f073aaed Mon Sep 17 00:00:00 2001 From: gahusb Date: Sat, 11 Apr 2026 09:14:41 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20PDF=20=EC=A0=84=EC=B2=B4=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=B6=9C=EB=A0=A5=20+=20=ED=95=84?= =?UTF-8?q?=EC=88=98=20=ED=95=AD=EB=AA=A9=2040%=20=ED=95=A0=EC=9D=B8=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @media print CSS 보강: html/body height auto, overflow visible, fixed 요소 숨김 - 하단 고정바에 no-print 클래스 추가 - afterprint 이벤트 리스너로 isPrinting 상태 안정적 해제 - 필수 항목 헤더에 40% 할인 배지, 소계에 정가 취소선/할인액 표시 - 합계 박스에 정가→할인가 비교 표시 Co-Authored-By: Claude Opus 4.6 --- app/quote/[token]/page.tsx | 46 +++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/app/quote/[token]/page.tsx b/app/quote/[token]/page.tsx index a5c114a..1b38d01 100644 --- a/app/quote/[token]/page.tsx +++ b/app/quote/[token]/page.tsx @@ -39,6 +39,12 @@ export default function QuotePage() { const [submitted, setSubmitted] = useState(false); const [isPrinting, setIsPrinting] = useState(false); + useEffect(() => { + const handleAfterPrint = () => setIsPrinting(false); + window.addEventListener('afterprint', handleAfterPrint); + return () => window.removeEventListener('afterprint', handleAfterPrint); + }, []); + useEffect(() => { fetch(`/api/quote/${token}`) .then((r) => r.ok ? r.json() : Promise.reject()) @@ -70,6 +76,8 @@ export default function QuotePage() { const optionalItems = quote?.items.filter((i) => i.optional) ?? []; const requiredTotal = requiredItems.reduce((s, i) => s + i.unitPrice * i.quantity, 0); + const DISCOUNT_RATE = 0.4; // 40% 할인 + const requiredOriginal = Math.round(requiredTotal / (1 - DISCOUNT_RATE)); const optionalTotal = optionalItems .filter((i) => checkedOptional[i.id]) .reduce((s, i) => s + i.unitPrice * i.quantity, 0); @@ -149,16 +157,17 @@ export default function QuotePage() { input[type=checkbox] { accent-color: #6366f1; width: 18px; height: 18px; cursor: pointer; } input[type=radio] { accent-color: #6366f1; width: 18px; height: 18px; cursor: pointer; } @media print { - body { background: white !important; color: #1e293b !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; } + html, body { height: auto !important; min-height: 0 !important; overflow: visible !important; background: white !important; color: #1e293b !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; } + * { overflow: visible !important; } .no-print { display: none !important; } .print-break { page-break-before: always; } - .print-section-title { display: block !important; } - [style*="background: #0a0f1e"], [style*="background: #0f172a"] { - background: white !important; - color: #1e293b !important; - } - table { page-break-inside: auto; } + .print-section-title { display: block !important; color: #1e293b !important; } + div[style] { background: white !important; color: #1e293b !important; min-height: 0 !important; height: auto !important; } + [style*="position: fixed"] { display: none !important; } + table { page-break-inside: auto; background: white !important; } tr { page-break-inside: avoid; } + th, td { color: #1e293b !important; border-color: #e2e8f0 !important; } + span, p, h2, h3, div { color: #1e293b !important; } } .print-section-title { display: none; } `} @@ -184,7 +193,7 @@ export default function QuotePage() { setTimeout(() => { window.print(); setIsPrinting(false); - }, 300); + }, 500); }} style={{ display: 'flex', alignItems: 'center', gap: 6, @@ -266,7 +275,7 @@ export default function QuotePage() {
- + 필수 항목 + 40% 할인 적용
@@ -368,6 +378,12 @@ export default function QuotePage() {
+ {/* 필수 항목 할인 소계 */} +
+ 정가 {requiredOriginal.toLocaleString()}원 + −{(requiredOriginal - requiredTotal).toLocaleString()}원 할인 + {requiredTotal.toLocaleString()}원 +
)} @@ -428,10 +444,14 @@ export default function QuotePage() { {/* 합계 */}
-
+
+
+ 필수 항목 정가 + {requiredOriginal.toLocaleString()}원 +
- 필수 항목 - {requiredTotal.toLocaleString()}원 + 40% 할인 적용 + {requiredTotal.toLocaleString()}원
{optionalTotal > 0 && (
@@ -502,7 +522,7 @@ export default function QuotePage() { {/* 하단 고정 바 — 견적 수락 */} {quote.status !== 'accepted' && quote.status !== 'rejected' && !isExpired && ( -
+
현재 선택된 견적 합계