diff --git a/UI_개선사항_문서.md b/UI_개선사항_문서.md index da991296..b6bb785f 100644 --- a/UI_개선사항_문서.md +++ b/UI_개선사항_문서.md @@ -601,4 +601,200 @@ export default function EmptyStatePage() { --- +## 📧 메일 관리 시스템 UI 개선사항 + +### 최근 업데이트 (2025-01-02) + +#### 1. 메일 발송 페이지 헤더 개선 +**변경 전:** +```tsx +
+
+
+ +
+
+

메일 발송

+

설명

+
+
+
+``` + +**변경 후 (표준 헤더 카드 적용):** +```tsx + + + 메일 발송 +

+ 템플릿을 선택하거나 직접 작성하여 메일을 발송하세요 +

+
+
+``` + +**개선 사항:** +- ✅ 불필요한 아이콘 제거 (종이비행기) +- ✅ 표준 Card 컴포넌트 사용으로 통일감 향상 +- ✅ 다른 페이지와 동일한 헤더 스타일 적용 + +#### 2. 메일 내용 입력 개선 +**변경 전:** +```tsx +
'; + + container.appendChild(card); + + // 쿼리 텍스트가 변경될 때마다 파라미터를 감지합니다 + const textarea = card.querySelector(".query-textarea"); + textarea.addEventListener("input", function () { + detectQueryParameters(queryId); + }); + + // 초기 SQL이 있으면 파라미터를 감지합니다 + if (queries[queryId].sql) { + detectQueryParameters(queryId); + } + } + + // 쿼리 이름을 업데이트하는 함수 + function updateQueryName(queryId, name) { + queries[queryId].name = name; + const nameSpan = document.querySelector("#" + queryId + " .query-name"); + nameSpan.textContent = name; + } + + // 쿼리 타입을 업데이트하는 함수 + function updateQueryType(queryId, type) { + queries[queryId].type = type; + const card = document.getElementById(queryId); + card.className = "query-card " + type; + const badge = card.querySelector(".query-type-badge"); + badge.className = "query-type-badge " + type; + badge.textContent = type === "master" ? "MASTER" : "DETAIL"; + } + + // 쿼리 SQL을 업데이트하는 함수 + function updateQuerySql(queryId, sql) { + queries[queryId].sql = sql; + } + + // 쿼리를 삭제하는 함수 + function deleteQuery(queryId) { + if (confirm("이 쿼리를 삭제하시겠습니까?")) { + document.getElementById(queryId).remove(); + delete queries[queryId]; + } + } + + // 쿼리에서 파라미터를 감지하는 함수 + function detectQueryParameters(queryId) { + const card = document.getElementById(queryId); + const textarea = card.querySelector(".query-textarea"); + const sql = textarea.value; + const paramSection = document.getElementById("params-" + queryId); + + const regex = /\$\d+/g; + const matches = sql.match(regex); + + if (matches && matches.length > 0) { + const uniqueParams = Array.from(new Set(matches)); + uniqueParams.sort(function (a, b) { + return parseInt(a.substring(1)) - parseInt(b.substring(1)); + }); + + paramSection.classList.add("show"); + paramSection.innerHTML = + '📎 파라미터'; + + uniqueParams.forEach(function (param) { + const paramNum = param.substring(1); + const fieldDiv = document.createElement("div"); + fieldDiv.className = "parameter-field"; + fieldDiv.style.display = "flex"; + fieldDiv.style.gap = "5px"; + fieldDiv.style.alignItems = "center"; + + const label = document.createElement("div"); + label.textContent = param; + label.style.minWidth = "35px"; + label.style.fontWeight = "bold"; + label.style.fontSize = "11px"; + + const select = document.createElement("select"); + select.className = "param-type-select"; + select.innerHTML = + ''; + + const input = document.createElement("input"); + input.type = "text"; + input.className = "param-value-input"; + input.placeholder = "값"; + input.dataset.param = param; + input.dataset.queryId = queryId; + + // URL 파라미터가 있으면 자동으로 채웁니다 + const urlParams = loadUrlParameters(); + if (urlParams[param]) { + input.value = urlParams[param]; + } + + select.addEventListener("change", function () { + input.type = select.value; + }); + + fieldDiv.appendChild(label); + fieldDiv.appendChild(select); + fieldDiv.appendChild(input); + paramSection.appendChild(fieldDiv); + }); + + updateUrlExample(); + } else { + paramSection.classList.remove("show"); + } + } + + // URL 예시를 업데이트하는 함수 + function updateUrlExample() { + const allParams = []; + + // 모든 쿼리의 파라미터를 수집합니다 + Object.keys(queries).forEach(function (queryId) { + const paramSection = document.getElementById("params-" + queryId); + if (paramSection) { + const inputs = paramSection.querySelectorAll("input[data-param]"); + inputs.forEach(function (input) { + const param = input.dataset.param; + if (!allParams.includes(param)) { + allParams.push(param); + } + }); + } + }); + + // URL 예시를 생성합니다 + if (allParams.length > 0) { + allParams.sort(function (a, b) { + return parseInt(a.substring(1)) - parseInt(b.substring(1)); + }); + + const paramString = allParams + .map(function (p) { + return p + "=값"; + }) + .join("&"); + + document.getElementById("url-sample").textContent = + window.location.pathname + "?" + paramString; + } + } + + // 쿼리를 실행하는 함수 (시뮬레이션) + function executeQuery(queryId) { + const query = queries[queryId]; + let finalSql = query.sql; + + // 파라미터를 실제 값으로 치환합니다 + const paramSection = document.getElementById("params-" + queryId); + const inputs = paramSection.querySelectorAll("input[data-param]"); + let allFilled = true; + + inputs.forEach(function (input) { + const param = input.dataset.param; + const value = input.value; + const type = input.parentElement.querySelector("select").value; + + if (value) { + let formattedValue = value; + if (type === "text" || type === "date") { + formattedValue = "'" + value + "'"; + } + finalSql = finalSql.split(param).join(formattedValue); + } else { + allFilled = false; + } + }); + + if (!allFilled) { + alert("모든 파라미터 값을 입력해주세요!"); + return; + } + + console.log("[" + query.name + "] 실행 쿼리:", finalSql); + + // 결과 영역에 샘플 필드를 표시합니다 + const resultArea = document.getElementById("result-" + queryId); + resultArea.style.display = "block"; + const fieldsDiv = resultArea.querySelector(".result-fields"); + + // 샘플 필드를 생성합니다 + if (query.type === "master") { + fieldsDiv.innerHTML = + '
order_no
order_date
supplier
'; + } else { + fieldsDiv.innerHTML = + '
item_name
quantity
price
'; + } + + alert("쿼리가 실행되었습니다!\n콘솔에서 확인하세요."); + } + + // 템플릿을 적용하는 함수 + function applyTemplate(templateType) { + clearCanvas(); + + switch (templateType) { + case "order": + // 발주서 템플릿: 마스터 쿼리와 디테일 쿼리를 모두 생성합니다 + addQuery({ + name: "발주 마스터", + type: "master", + sql: "SELECT order_no, order_date, supplier FROM purchase_order WHERE order_no = $1", + }); + + addQuery({ + name: "발주 상세", + type: "detail", + sql: "SELECT item_name, quantity, price FROM purchase_order_detail WHERE order_no = $1", + }); + + // 캔버스에 컴포넌트를 배치합니다 + createElement("label", 50, 80, { width: "150px", height: "40px" }); + createElement("text", 210, 80, { + width: "300px", + height: "40px", + queryId: "query_1", + }); + + createElement("label", 50, 140, { width: "150px", height: "40px" }); + createElement("text", 210, 140, { + width: "300px", + height: "40px", + queryId: "query_1", + }); + + createElement("table", 50, 220, { + width: "650px", + height: "300px", + queryId: "query_2", + }); + break; + + case "invoice": + // 청구서 템플릿 + addQuery({ + name: "청구 마스터", + type: "master", + sql: "SELECT invoice_no, invoice_date, customer FROM invoice WHERE invoice_no = $1", + }); + + addQuery({ + name: "청구 항목", + type: "detail", + sql: "SELECT description, amount FROM invoice_items WHERE invoice_no = $1", + }); + + createElement("text", 100, 100); + createElement("table", 100, 250, { + width: "600px", + height: "250px", + }); + break; + + case "basic": + addQuery({ + name: "기본 쿼리", + type: "master", + sql: "SELECT * FROM table WHERE id = $1", + }); + createElement("text", 100, 100); + break; + } + } + + // 파라미터 테스트 함수 - 실제 URL 파라미터를 시뮬레이션합니다 + function testWithParams() { + const testUrl = window.location.pathname + "?$1=PO-2025-001&$2=2025-01"; + alert( + "테스트 URL:\n" + + testUrl + + "\n\n이 URL로 페이지를 열면 파라미터가 자동으로 입력됩니다." + ); + + // 실제로 URL을 변경합니다 + if (confirm("테스트 파라미터로 페이지를 새로고침하시겠습니까?")) { + window.location.href = testUrl; + } + } + + function clearCanvas() { + const components = canvas.querySelectorAll(".placed-component"); + components.forEach(function (comp) { + comp.remove(); + }); + } + + function saveReport() { + const reportData = { + title: document.getElementById("report-title").value, + queries: queries, + components: [], + }; + + document.querySelectorAll(".placed-component").forEach(function (comp) { + reportData.components.push({ + type: comp.dataset.type, + queryId: comp.dataset.queryId, + left: comp.style.left, + top: comp.style.top, + width: comp.style.width, + height: comp.style.height, + }); + }); + + console.log("저장된 리포트:", JSON.stringify(reportData, null, 2)); + alert("리포트가 저장되었습니다!\n콘솔에서 확인하세요."); + } + + function showPreview() { + const modal = document.getElementById("previewModal"); + const previewContent = document.getElementById("previewContent"); + previewContent.innerHTML = canvas.innerHTML; + modal.classList.add("active"); + } + + function closePreview() { + document.getElementById("previewModal").classList.remove("active"); + } + + function exportPDF() { + alert("PDF 다운로드 기능이 실행됩니다."); + } + + function exportWord() { + alert("WORD 다운로드 기능이 실행됩니다."); + } + + document + .getElementById("previewModal") + .addEventListener("click", function (e) { + if (e.target === this) { + closePreview(); + } + }); + + +