+ )
+}
+
+
+const e: Record = {
+ logBox: { marginTop: 14, borderTop: '0.5px solid var(--line)', paddingTop: 10, display: 'flex', flexDirection: 'column', gap: 5 },
+ logTitle: { fontSize: 'var(--fs-caption)', fontWeight: 700, color: 'var(--muted)' },
+ logEmpty: { marginTop: 14, borderTop: '0.5px solid var(--line)', paddingTop: 10, fontSize: 11, color: 'var(--muted)' },
+ logRow: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: 'var(--muted)' },
+ /* 무엇을 보냈는지가 한눈에 갈려야 한다 — 계약서까지 보낸 건은 더 무겁게 */
+ tagBoth: { fontWeight: 700, color: 'var(--dark)', background: 'var(--lime-bg)', border: '0.5px solid var(--lime)', borderRadius: 3, padding: '0 5px', whiteSpace: 'nowrap' },
+ tagQuote: { fontWeight: 700, color: 'var(--muted)', background: 'var(--card)', border: '0.5px solid var(--line)', borderRadius: 3, padding: '0 5px', whiteSpace: 'nowrap' },
+ logNo: { fontVariantNumeric: 'tabular-nums', color: 'var(--dark)', whiteSpace: 'nowrap' },
+ logDate: { fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' },
+ logTo: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
+}
+
+/** 스스로 불러오는 판 — 관리자 「고객정보」처럼 이력만 필요한 곳에서 쓴다. */
+export function EmailLogFor({ quoteId }: { quoteId: number }) {
+ const [rows, setRows] = useState(null)
+ useEffect(() => { fetchEmailLog(quoteId).then(setRows).catch(() => setRows([])) }, [quoteId])
+ return
+}
diff --git a/frontend/src/components/EmailSendModal.tsx b/frontend/src/components/EmailSendModal.tsx
index 1788d54..f68d5a7 100644
--- a/frontend/src/components/EmailSendModal.tsx
+++ b/frontend/src/components/EmailSendModal.tsx
@@ -1,6 +1,7 @@
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
import { BTN } from '../styles/buttons'
-import { sendQuoteEmail } from '../api/email'
+import { sendQuoteEmail, fetchEmailLog, type EmailLogRow } from '../api/email'
+import { EmailLog } from './EmailLog'
/** 견적서(+계약서) 이메일 발송 모달. to 비우면 등록된 고객 이메일로 발송. */
export function EmailSendModal({ quoteId, customerName, defaultTo, onClose, noContract}: {
@@ -26,6 +27,15 @@ export function EmailSendModal({ quoteId, customerName, defaultTo, onClose, noCo
const [sending, setSending] = useState(false)
const [done, setDone] = useState(null)
const [err, setErr] = useState('')
+ /*
+ * 지금까지 무엇을 보냈나 — **여기서만 보여 준다.**
+ * 목록 화면에는 두지 않는다. 「보냈나 안 보냈나」는 보낼 때 궁금한 것이지
+ * 목록을 훑을 때 궁금한 것이 아니다 — 열마다 배지를 더하면 표만 복잡해진다.
+ */
+ const [log, setLog] = useState(null)
+
+ function loadLog() { fetchEmailLog(quoteId).then(setLog).catch(() => setLog([])) }
+ useEffect(loadLog, [quoteId]) // eslint-disable-line react-hooks/exhaustive-deps
async function handleSend() {
setSending(true); setErr('')
@@ -36,6 +46,7 @@ export function EmailSendModal({ quoteId, customerName, defaultTo, onClose, noCo
include_contract: attachContract,
})
setDone(`${r.to} 로 발송됨 (${r.attachments.join(', ')})`)
+ loadLog() // 방금 보낸 것이 이력에 바로 보이게
} catch (e) {
setErr(e instanceof Error ? e.message : '발송 실패')
} finally {
@@ -55,12 +66,23 @@ export function EmailSendModal({ quoteId, customerName, defaultTo, onClose, noCo
{done ? (
✓ {done}
+
) : (
+ {/*
+ 고객정보에 메일이 있으면 채워서 연다 — 그대로 보내도 되고 고쳐도 된다.
+ 없으면 빈칸으로 열려 **여기서 바로 적어** 보낼 수 있다.
+ 견적서는 메일 없이도 만들어지므로, 메일이 없다고 발송 자체를 막지 않는다.
+ */}
- setTo(e.target.value)} />
+ setTo(e.target.value)}
+ />
+ {!defaultTo &&