lhj #114
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,10 +155,15 @@ export class TodoService {
|
|||
updates: Partial<TodoItem>
|
||||
): Promise<TodoItem> {
|
||||
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<void> {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export function ComboChartComponent({ data, config, width = 250, height = 200 }:
|
|||
top: 5,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
bottom: 25,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
|
|
@ -84,7 +84,7 @@ export function ComboChartComponent({ data, config, width = 250, height = 200 }:
|
|||
name
|
||||
]}
|
||||
/>
|
||||
{showLegend && yKeys.length > 1 && (
|
||||
{showLegend && (
|
||||
<Legend
|
||||
wrapperStyle={{ fontSize: '12px' }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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})`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export function StackedBarChartComponent({ data, config, width = 250, height = 2
|
|||
top: 5,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
bottom: 25,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
|
|
@ -79,7 +79,7 @@ export function StackedBarChartComponent({ data, config, width = 250, height = 2
|
|||
name
|
||||
]}
|
||||
/>
|
||||
{showLegend && yKeys.length > 1 && (
|
||||
{showLegend && (
|
||||
<Legend
|
||||
wrapperStyle={{ fontSize: '12px' }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue