From 1e3136cb0bb22c4ecec355c8c051f8833c12b16c Mon Sep 17 00:00:00 2001 From: leeheejin Date: Mon, 20 Oct 2025 18:04:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?todo=20=EC=98=A4=EB=A5=98=20=EC=9E=88?= =?UTF-8?q?=EB=8D=98=EA=B1=B0=20=EC=88=98=EC=A0=95=20=EC=9D=B4=EC=A0=A0=20?= =?UTF-8?q?=EB=94=94=EB=B9=84=EA=BA=BC=EB=8F=84=20=EC=A7=80=EC=9B=8C?= =?UTF-8?q?=EC=A7=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-node/data/todos/todos.json | 44 +------------------ backend-node/src/services/todoService.ts | 23 +++++++--- .../dashboard/widgets/TodoWidget.tsx | 13 ++++-- 3 files changed, 29 insertions(+), 51 deletions(-) diff --git a/backend-node/data/todos/todos.json b/backend-node/data/todos/todos.json index e10d42af..766a08ed 100644 --- a/backend-node/data/todos/todos.json +++ b/backend-node/data/todos/todos.json @@ -1,54 +1,14 @@ [ - { - "id": "e5bb334c-d58a-4068-ad77-2607a41f4675", - "title": "ㅁㄴㅇㄹ", - "description": "ㅁㄴㅇㄹ", - "priority": "normal", - "status": "completed", - "assignedTo": "", - "dueDate": "2025-10-20T18:17", - "createdAt": "2025-10-20T06:15:49.610Z", - "updatedAt": "2025-10-20T07:36:06.370Z", - "isUrgent": false, - "order": 0, - "completedAt": "2025-10-20T07:36:06.370Z" - }, - { - "id": "334be17c-7776-47e8-89ec-4b57c4a34bcd", - "title": "연동되어주겠니?", - "description": "", - "priority": "normal", - "status": "pending", - "assignedTo": "", - "dueDate": "", - "createdAt": "2025-10-20T06:20:06.343Z", - "updatedAt": "2025-10-20T06:20:06.343Z", - "isUrgent": false, - "order": 1 - }, - { - "id": "f85b81de-fcbd-4858-8973-247d9d6e70ed", - "title": "연동되어주겠니?11", - "description": "ㄴㅇㄹ", - "priority": "normal", - "status": "pending", - "assignedTo": "", - "dueDate": "2025-10-20T17:22", - "createdAt": "2025-10-20T06:20:53.818Z", - "updatedAt": "2025-10-20T06:20:53.818Z", - "isUrgent": false, - "order": 2 - }, { "id": "58d2b26f-5197-4df1-b5d4-724a72ee1d05", "title": "연동되어주려무니", "description": "ㅁㄴㅇㄹ", "priority": "normal", - "status": "pending", + "status": "in_progress", "assignedTo": "", "dueDate": "2025-10-21T15:21", "createdAt": "2025-10-20T06:21:19.817Z", - "updatedAt": "2025-10-20T06:21:19.817Z", + "updatedAt": "2025-10-20T09:00:26.948Z", "isUrgent": false, "order": 3 } diff --git a/backend-node/src/services/todoService.ts b/backend-node/src/services/todoService.ts index 33becbb9..c7f12dee 100644 --- a/backend-node/src/services/todoService.ts +++ b/backend-node/src/services/todoService.ts @@ -155,10 +155,15 @@ export class TodoService { updates: Partial ): Promise { try { - if (DATA_SOURCE === "database") { + // 먼저 데이터베이스에서 찾아보고, 없으면 파일에서 찾기 + try { return await this.updateTodoDB(id, updates); - } else { - return this.updateTodoFile(id, updates); + } catch (dbError: any) { + // 데이터베이스에서 찾지 못했으면 파일에서 찾기 + if (dbError.message && dbError.message.includes("찾을 수 없습니다")) { + return this.updateTodoFile(id, updates); + } + throw dbError; } } catch (error) { logger.error("❌ To-Do 수정 오류:", error); @@ -171,10 +176,16 @@ export class TodoService { */ public async deleteTodo(id: string): Promise { try { - if (DATA_SOURCE === "database") { + // 먼저 데이터베이스에서 찾아보고, 없으면 파일에서 찾기 + try { await this.deleteTodoDB(id); - } else { - this.deleteTodoFile(id); + } catch (dbError: any) { + // 데이터베이스에서 찾지 못했으면 파일에서 찾기 + if (dbError.message && dbError.message.includes("찾을 수 없습니다")) { + this.deleteTodoFile(id); + } else { + throw dbError; + } } logger.info(`✅ To-Do 삭제: ${id}`); } catch (error) { diff --git a/frontend/components/dashboard/widgets/TodoWidget.tsx b/frontend/components/dashboard/widgets/TodoWidget.tsx index 6ab85294..30e221fb 100644 --- a/frontend/components/dashboard/widgets/TodoWidget.tsx +++ b/frontend/components/dashboard/widgets/TodoWidget.tsx @@ -249,11 +249,18 @@ export default function TodoWidget({ element }: TodoWidgetProps) { }, }); - if (response.ok) { - fetchTodos(); + // 응답이 성공이거나, 500 에러여도 실제로는 삭제되었을 수 있으므로 목록 새로고침 + if (response.ok || response.status === 500) { + // 약간의 딜레이 후 새로고침 (백엔드 처리 완료 대기) + setTimeout(() => { + fetchTodos(); + }, 300); } } catch (error) { - // console.error("To-Do 삭제 오류:", error); + // 네트워크 에러여도 삭제되었을 수 있으므로 새로고침 + setTimeout(() => { + fetchTodos(); + }, 300); } }; From 5fb9e19e5aa1e8bf27fd0b61f44ec376e13bafcd Mon Sep 17 00:00:00 2001 From: leeheejin Date: Tue, 21 Oct 2025 10:05:39 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EC=B0=A8=ED=8A=B8=20=EB=B2=94=EB=A1=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dashboard/charts/AreaChart.tsx | 20 +++++++++------ .../admin/dashboard/charts/BarChart.tsx | 20 +++++++++------ .../admin/dashboard/charts/ComboChart.tsx | 25 +++++++++++++------ .../dashboard/charts/ComboChartComponent.tsx | 4 +-- .../dashboard/charts/HorizontalBarChart.tsx | 23 +++++++++++------ .../admin/dashboard/charts/LineChart.tsx | 22 ++++++++++------ .../admin/dashboard/charts/PieChart.tsx | 19 ++++++++------ .../dashboard/charts/StackedBarChart.tsx | 18 ++++++++----- .../charts/StackedBarChartComponent.tsx | 4 +-- 9 files changed, 101 insertions(+), 54 deletions(-) diff --git a/frontend/components/admin/dashboard/charts/AreaChart.tsx b/frontend/components/admin/dashboard/charts/AreaChart.tsx index 9304d406..cabc8f10 100644 --- a/frontend/components/admin/dashboard/charts/AreaChart.tsx +++ b/frontend/components/admin/dashboard/charts/AreaChart.tsx @@ -23,7 +23,7 @@ export function AreaChart({ data, config, width = 600, height = 400 }: AreaChart const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -221,17 +221,23 @@ export function AreaChart({ data, config, width = 600, height = 400 }: AreaChart .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) @@ -239,7 +245,7 @@ export function AreaChart({ data, config, width = 600, height = 400 }: AreaChart .attr("opacity", config.areaOpacity !== undefined ? config.areaOpacity : 0.3) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/BarChart.tsx b/frontend/components/admin/dashboard/charts/BarChart.tsx index 3f619d05..4026802d 100644 --- a/frontend/components/admin/dashboard/charts/BarChart.tsx +++ b/frontend/components/admin/dashboard/charts/BarChart.tsx @@ -23,7 +23,7 @@ export function BarChart({ data, config, width = 600, height = 400 }: BarChartPr const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -196,24 +196,30 @@ export function BarChart({ data, config, width = 600, height = 400 }: BarChartPr .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; // 각 범례 항목의 너비 + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; // 중앙 정렬 + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/ComboChart.tsx b/frontend/components/admin/dashboard/charts/ComboChart.tsx index ce373d71..e3763784 100644 --- a/frontend/components/admin/dashboard/charts/ComboChart.tsx +++ b/frontend/components/admin/dashboard/charts/ComboChart.tsx @@ -25,7 +25,7 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -275,23 +275,32 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha .text(config.yAxisLabel); } - // 범례 + // 범례 (차트 하단 중앙) if (config.showLegend !== false && data.datasets.length > 0) { - const legend = svg.append("g").attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + + const legend = svg + .append("g") + .attr("class", "legend") + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); // 범례 아이콘 (첫 번째는 사각형, 나머지는 라인) if (i === 0) { - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); } else { - legendRow + legendItem .append("line") .attr("x1", 0) .attr("y1", 7) @@ -300,7 +309,7 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha .attr("stroke", dataset.color || colors[i % colors.length]) .attr("stroke-width", 2); - legendRow + legendItem .append("circle") .attr("cx", 7.5) .attr("cy", 7) @@ -308,7 +317,7 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha .attr("fill", dataset.color || colors[i % colors.length]); } - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx b/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx index 6f11d0fe..c9c40bb2 100644 --- a/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx +++ b/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx @@ -59,7 +59,7 @@ export function ComboChartComponent({ data, config, width = 250, height = 200 }: top: 5, right: 30, left: 20, - bottom: 5, + bottom: 25, }} > @@ -84,7 +84,7 @@ export function ComboChartComponent({ data, config, width = 250, height = 200 }: name ]} /> - {showLegend && yKeys.length > 1 && ( + {showLegend && ( diff --git a/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx b/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx index 60fcb666..955a02c9 100644 --- a/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx +++ b/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx @@ -23,7 +23,7 @@ export function HorizontalBarChart({ data, config, width = 600, height = 400 }: const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 120 }; + const margin = { top: 40, right: 80, bottom: 80, left: 120 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -192,21 +192,30 @@ export function HorizontalBarChart({ data, config, width = 600, height = 400 }: .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { - const legend = svg.append("g").attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + + const legend = svg + .append("g") + .attr("class", "legend") + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/LineChart.tsx b/frontend/components/admin/dashboard/charts/LineChart.tsx index db4292a8..1d3c7a9a 100644 --- a/frontend/components/admin/dashboard/charts/LineChart.tsx +++ b/frontend/components/admin/dashboard/charts/LineChart.tsx @@ -23,7 +23,7 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -208,17 +208,23 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("line") .attr("x1", 0) .attr("y1", 7) @@ -227,7 +233,7 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart .attr("stroke", dataset.color || colors[i % colors.length]) .attr("stroke-width", 3); - legendRow + legendItem .append("circle") .attr("cx", 7.5) .attr("cy", 7) @@ -236,7 +242,7 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart .attr("stroke", "white") .attr("stroke-width", 2); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/PieChart.tsx b/frontend/components/admin/dashboard/charts/PieChart.tsx index f9ab4810..8afcb4c0 100644 --- a/frontend/components/admin/dashboard/charts/PieChart.tsx +++ b/frontend/components/admin/dashboard/charts/PieChart.tsx @@ -24,7 +24,7 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 120, bottom: 40, left: 120 }; + const margin = { top: 40, right: 150, bottom: 40, left: 120 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; const radius = Math.min(chartWidth, chartHeight) / 2; @@ -136,28 +136,33 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa .text(config.title); } - // 범례 + // 범례 (차트 오른쪽, 세로 배치) if (config.showLegend !== false) { + const legendX = width / 2 + radius + 30; // 차트 오른쪽 + const legendY = (height - pieData.length * 25) / 2; // 세로 중앙 정렬 + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendX}, ${legendY})`); pieData.forEach((d, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(0, ${i * 25})`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) - .style("font-size", "12px") + .style("font-size", "11px") .style("fill", "#333") .text(`${d.label} (${d.value})`); }); diff --git a/frontend/components/admin/dashboard/charts/StackedBarChart.tsx b/frontend/components/admin/dashboard/charts/StackedBarChart.tsx index 1a18c622..8aed840a 100644 --- a/frontend/components/admin/dashboard/charts/StackedBarChart.tsx +++ b/frontend/components/admin/dashboard/charts/StackedBarChart.tsx @@ -23,7 +23,7 @@ export function StackedBarChart({ data, config, width = 600, height = 400 }: Sta const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -241,24 +241,30 @@ export function StackedBarChart({ data, config, width = 600, height = 400 }: Sta .text(config.yAxisLabel); } - // 범례 + // 범례 (차트 하단 중앙) if (config.showLegend !== false) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx b/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx index a1115af5..5ddb7b85 100644 --- a/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx +++ b/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx @@ -54,7 +54,7 @@ export function StackedBarChartComponent({ data, config, width = 250, height = 2 top: 5, right: 30, left: 20, - bottom: 5, + bottom: 25, }} > @@ -79,7 +79,7 @@ export function StackedBarChartComponent({ data, config, width = 250, height = 2 name ]} /> - {showLegend && yKeys.length > 1 && ( + {showLegend && (