# ๐Ÿ“Š ์ฐจํŠธ ์‹œ์Šคํ…œ ๊ตฌํ˜„ ๊ณ„ํš ## ๊ฐœ์š” D3.js ๊ธฐ๋ฐ˜์˜ ๊ฐ•๋ ฅํ•œ ์ฐจํŠธ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•(DB ์ฟผ๋ฆฌ ๋˜๋Š” REST API)์œผ๋กœ ๊ฐ€์ ธ์™€ ๋‹ค์–‘ํ•œ ์ฐจํŠธ๋กœ ์‹œ๊ฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --- ## ๐ŸŽฏ ํ•ต์‹ฌ ์š”๊ตฌ์‚ฌํ•ญ ### 1. ๋ฐ์ดํ„ฐ ์†Œ์Šค (2๊ฐ€์ง€ ๋ฐฉ์‹) #### A. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜ - **ํ˜„์žฌ DB**: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ธฐ๋ณธ PostgreSQL ์—ฐ๊ฒฐ - **์™ธ๋ถ€ DB**: ๊ธฐ์กด "์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ" ๋ฉ”๋‰ด์—์„œ ๋“ฑ๋ก๋œ ์ปค๋„ฅ์…˜๋งŒ ์‚ฌ์šฉ - ์‹ ๊ทœ ์ปค๋„ฅ์…˜ ์ƒ์„ฑ์€ ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ ๋ฉ”๋‰ด์—์„œ๋งŒ ๊ฐ€๋Šฅ - ์ฐจํŠธ ์„ค์ •์—์„œ๋Š” ๋“ฑ๋ก๋œ ์ปค๋„ฅ์…˜ ๋ชฉ๋ก์—์„œ ์„ ํƒ๋งŒ ๊ฐ€๋Šฅ - **์ฟผ๋ฆฌ ์ œํ•œ**: SELECT ๋ฌธ๋งŒ ํ—ˆ์šฉ (INSERT, UPDATE, DELETE, DROP ๋“ฑ ๊ธˆ์ง€) - **์ฟผ๋ฆฌ ๊ฒ€์ฆ**: ์„œ๋ฒ„ ์ธก์—์„œ SQL Injection ๋ฐฉ์ง€ ๋ฐ ์ฟผ๋ฆฌ ํƒ€์ž… ๊ฒ€์ฆ #### B. REST API ํ˜ธ์ถœ - **HTTP Methods**: GET (๊ถŒ์žฅ) - ๋ฐ์ดํ„ฐ ์กฐํšŒ์— ์ถฉ๋ถ„ - **๋ฐ์ดํ„ฐ ํ˜•์‹**: JSON ์‘๋‹ต๋งŒ ํ—ˆ์šฉ - **ํ—ค๋” ์„ค์ •**: Authorization, Content-Type ๋“ฑ ์ปค์Šคํ…€ ํ—ค๋” ์ง€์› - **์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ**: URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ•„ํ„ฐ๋ง ์กฐ๊ฑด ์ „๋‹ฌ - **์‘๋‹ต ํŒŒ์‹ฑ**: JSON ๊ตฌ์กฐ์—์„œ ์ฐจํŠธ ๋ฐ์ดํ„ฐ ์ถ”์ถœ - **์—๋Ÿฌ ์ฒ˜๋ฆฌ**: HTTP ์ƒํƒœ ์ฝ”๋“œ ๋ฐ ํƒ€์ž„์•„์›ƒ ์ฒ˜๋ฆฌ > **์ฐธ๊ณ **: POST๋Š” ํ–ฅํ›„ ํ™•์žฅ (GraphQL, ๋ณต์žกํ•œ ํ•„ํ„ฐ๋ง)์„ ์œ„ํ•ด ์„ ํƒ์ ์œผ๋กœ ์ง€์› ๊ฐ€๋Šฅ ### 2. ์ฐจํŠธ ํƒ€์ž… (D3.js ๊ธฐ๋ฐ˜) ํ˜„์žฌ ์ง€์› ์˜ˆ์ •: - **Bar Chart** (๋ง‰๋Œ€ ์ฐจํŠธ): ์ˆ˜ํ‰/์ˆ˜์ง ๋ง‰๋Œ€ - **Line Chart** (์„  ์ฐจํŠธ): ๋‹จ์ผ/๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ - **Area Chart** (์˜์—ญ ์ฐจํŠธ): ๋ˆ„์  ์˜์—ญ ์ง€์› - **Pie Chart** (์› ์ฐจํŠธ): ๋„๋„› ์ฐจํŠธ ํฌํ•จ - **Stacked Bar** (๋ˆ„์  ๋ง‰๋Œ€): ๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ ๋ˆ„์  - **Combo Chart** (ํ˜ผํ•ฉ ์ฐจํŠธ): ๋ง‰๋Œ€ + ์„  ์กฐํ•ฉ ### 3. ์ถ• ๋งคํ•‘ ์„ค์ • - **X์ถ•**: ์นดํ…Œ๊ณ ๋ฆฌ/์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ (๋ฌธ์ž์—ด, ๋‚ ์งœ) - **Y์ถ•**: ์ˆซ์ž ๋ฐ์ดํ„ฐ (๋‹จ์ผ ๋˜๋Š” ๋‹ค์ค‘ ์„ ํƒ ๊ฐ€๋Šฅ) - **๋‹ค์ค‘ Y์ถ•**: ์—ฌ๋Ÿฌ ์‹œ๋ฆฌ์ฆˆ๋ฅผ ํ•œ ์ฐจํŠธ์— ํ‘œ์‹œ (์˜ˆ: ๊ฐค๋Ÿญ์‹œ vs ์•„์ดํฐ ๋งค์ถœ) - **์ž๋™ ๊ฐ์ง€**: ๋ฐ์ดํ„ฐ ํƒ€์ž…์— ๋”ฐ๋ผ ์ถ• ์ž๋™ ์ถ”์ฒœ - **๋ฐ์ดํ„ฐ ๋ณ€ํ™˜**: ๋ฌธ์ž์—ด ๋‚ ์งœ๋ฅผ Date ๊ฐ์ฒด๋กœ ์ž๋™ ๋ณ€ํ™˜ ### 4. ์ฐจํŠธ ์Šคํƒ€์ผ๋ง - **์ƒ‰์ƒ ํŒ”๋ ˆํŠธ**: ์‚ฌ์ „ ์ •์˜๋œ ์ƒ‰์ƒ ์„ธํŠธ ์„ ํƒ - **์ปค์Šคํ…€ ์ƒ‰์ƒ**: ์‚ฌ์šฉ์ž ์ง€์ • ์ƒ‰์ƒ ์ž…๋ ฅ - **๋ฒ”๋ก€**: ์œ„์น˜ ์„ค์ • (์ƒ๋‹จ, ํ•˜๋‹จ, ์ขŒ์ธก, ์šฐ์ธก, ์ˆจ๊น€) - **์• ๋‹ˆ๋ฉ”์ด์…˜**: ์ฐจํŠธ ๋กœ๋“œ ์‹œ ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜ ํšจ๊ณผ - **ํˆดํŒ**: ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ ํ˜ธ๋ฒ„ ์‹œ ์ƒ์„ธ ์ •๋ณด ํ‘œ์‹œ - **๊ทธ๋ฆฌ๋“œ**: X/Y์ถ• ๊ทธ๋ฆฌ๋“œ ๋ผ์ธ ํ‘œ์‹œ/์ˆจ๊น€ --- ## ๐Ÿ“ ํŒŒ์ผ ๊ตฌ์กฐ ``` frontend/components/admin/dashboard/ โ”œโ”€โ”€ CHART_SYSTEM_PLAN.md # ์ด ํŒŒ์ผ โ”œโ”€โ”€ types.ts # โœ… ๊ธฐ์กด (ํƒ€์ž… ํ™•์žฅ ํ•„์š”) โ”œโ”€โ”€ ElementConfigModal.tsx # โœ… ๊ธฐ์กด (๋ฆฌํŒฉํ† ๋ง ํ•„์š”) โ”‚ โ”œโ”€โ”€ data-sources/ # ๐Ÿ†• ๋ฐ์ดํ„ฐ ์†Œ์Šค ๊ด€๋ จ โ”‚ โ”œโ”€โ”€ DataSourceSelector.tsx # ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ UI (DB vs API) โ”‚ โ”œโ”€โ”€ DatabaseConfig.tsx # DB ์ปค๋„ฅ์…˜ ์„ค์ • UI โ”‚ โ”œโ”€โ”€ ApiConfig.tsx # REST API ์„ค์ • UI โ”‚ โ””โ”€โ”€ dataSourceUtils.ts # ๋ฐ์ดํ„ฐ ์†Œ์Šค ์œ ํ‹ธ๋ฆฌํ‹ฐ โ”‚ โ”œโ”€โ”€ chart-config/ # ๐Ÿ”„ ์ฐจํŠธ ์„ค์ • ๊ด€๋ จ (๋ฆฌํŒฉํ† ๋ง) โ”‚ โ”œโ”€โ”€ QueryEditor.tsx # โœ… ๊ธฐ์กด (ํ™•์žฅ ํ•„์š”) โ”‚ โ”œโ”€โ”€ ChartConfigPanel.tsx # โœ… ๊ธฐ์กด (ํ™•์žฅ ํ•„์š”) โ”‚ โ”œโ”€โ”€ AxisMapper.tsx # ๐Ÿ†• ์ถ• ๋งคํ•‘ UI โ”‚ โ”œโ”€โ”€ StyleConfig.tsx # ๐Ÿ†• ์Šคํƒ€์ผ ์„ค์ • UI โ”‚ โ””โ”€โ”€ ChartPreview.tsx # ๐Ÿ†• ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ โ”‚ โ”œโ”€โ”€ charts/ # ๐Ÿ†• D3 ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ โ”‚ โ”œโ”€โ”€ ChartRenderer.tsx # ์ฐจํŠธ ๋ Œ๋”๋Ÿฌ (๋ฉ”์ธ) โ”‚ โ”œโ”€โ”€ BarChart.tsx # ๋ง‰๋Œ€ ์ฐจํŠธ โ”‚ โ”œโ”€โ”€ LineChart.tsx # ์„  ์ฐจํŠธ โ”‚ โ”œโ”€โ”€ AreaChart.tsx # ์˜์—ญ ์ฐจํŠธ โ”‚ โ”œโ”€โ”€ PieChart.tsx # ์› ์ฐจํŠธ โ”‚ โ”œโ”€โ”€ StackedBarChart.tsx # ๋ˆ„์  ๋ง‰๋Œ€ ์ฐจํŠธ โ”‚ โ”œโ”€โ”€ ComboChart.tsx # ํ˜ผํ•ฉ ์ฐจํŠธ โ”‚ โ”œโ”€โ”€ chartUtils.ts # ์ฐจํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ โ”‚ โ””โ”€โ”€ d3Helpers.ts # D3 ํ—ฌํผ ํ•จ์ˆ˜ โ”‚ โ””โ”€โ”€ CanvasElement.tsx # โœ… ๊ธฐ์กด (์ฐจํŠธ ๋ Œ๋”๋ง ํ†ตํ•ฉ) ``` --- ## ๐Ÿ”ง ํƒ€์ž… ์ •์˜ ํ™•์žฅ ### ๊ธฐ์กด ํƒ€์ž… ์—…๋ฐ์ดํŠธ ```typescript // types.ts // ๋ฐ์ดํ„ฐ ์†Œ์Šค ํƒ€์ž… ํ™•์žฅ export interface ChartDataSource { type: "database" | "api"; // 'static' ์ œ๊ฑฐ // DB ์ปค๋„ฅ์…˜ ๊ด€๋ จ connectionType?: "current" | "external"; // ํ˜„์žฌ DB vs ์™ธ๋ถ€ DB externalConnectionId?: string; // ์™ธ๋ถ€ DB ์ปค๋„ฅ์…˜ ID query?: string; // SQL ์ฟผ๋ฆฌ (SELECT๋งŒ) // API ๊ด€๋ จ endpoint?: string; // API URL method?: "GET"; // HTTP ๋ฉ”์„œ๋“œ (GET๋งŒ ์ง€์›) headers?: Record; // ์ปค์Šคํ…€ ํ—ค๋” queryParams?: Record; // URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ jsonPath?: string; // JSON ์‘๋‹ต์—์„œ ๋ฐ์ดํ„ฐ ์ถ”์ถœ ๊ฒฝ๋กœ (์˜ˆ: "data.results") // ๊ณตํ†ต refreshInterval?: number; // ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ (์ดˆ) lastExecuted?: string; // ๋งˆ์ง€๋ง‰ ์‹คํ–‰ ์‹œ๊ฐ„ lastError?: string; // ๋งˆ์ง€๋ง‰ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ } // ์™ธ๋ถ€ DB ์ปค๋„ฅ์…˜ ์ •๋ณด (๊ธฐ์กด ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ์—์„œ ๊ฐ€์ ธ์˜ด) export interface ExternalConnection { id: string; name: string; // ์‚ฌ์šฉ์ž ์ง€์ • ์ด๋ฆ„ (ํ‘œ์‹œ์šฉ) type: "postgresql" | "mysql" | "mssql" | "oracle"; // ๋‚˜๋จธ์ง€ ์ •๋ณด๋Š” ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ์—์„œ๋งŒ ๊ด€๋ฆฌ } // ์ฐจํŠธ ์„ค์ • ํ™•์žฅ export interface ChartConfig { // ์ถ• ๋งคํ•‘ xAxis: string; // X์ถ• ํ•„๋“œ๋ช… yAxis: string | string[]; // Y์ถ• ํ•„๋“œ๋ช… (๋‹ค์ค‘ ๊ฐ€๋Šฅ) // ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ groupBy?: string; // ๊ทธ๋ฃนํ•‘ ํ•„๋“œ aggregation?: "sum" | "avg" | "count" | "max" | "min"; sortBy?: string; // ์ •๋ ฌ ๊ธฐ์ค€ ํ•„๋“œ sortOrder?: "asc" | "desc"; // ์ •๋ ฌ ์ˆœ์„œ limit?: number; // ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜ ์ œํ•œ // ์Šคํƒ€์ผ colors?: string[]; // ์ฐจํŠธ ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ title?: string; // ์ฐจํŠธ ์ œ๋ชฉ showLegend?: boolean; // ๋ฒ”๋ก€ ํ‘œ์‹œ legendPosition?: "top" | "bottom" | "left" | "right"; // ๋ฒ”๋ก€ ์œ„์น˜ // ์ถ• ์„ค์ • xAxisLabel?: string; // X์ถ• ๋ผ๋ฒจ yAxisLabel?: string; // Y์ถ• ๋ผ๋ฒจ showGrid?: boolean; // ๊ทธ๋ฆฌ๋“œ ํ‘œ์‹œ // ์• ๋‹ˆ๋ฉ”์ด์…˜ enableAnimation?: boolean; // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ™œ์„ฑํ™” animationDuration?: number; // ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ๊ฐ„ (ms) // ํˆดํŒ showTooltip?: boolean; // ํˆดํŒ ํ‘œ์‹œ tooltipFormat?: string; // ํˆดํŒ ํฌ๋งท (ํ…œํ”Œ๋ฆฟ) // ์ฐจํŠธ๋ณ„ ํŠน์ˆ˜ ์„ค์ • barOrientation?: "vertical" | "horizontal"; // ๋ง‰๋Œ€ ๋ฐฉํ–ฅ lineStyle?: "smooth" | "straight"; // ์„  ์Šคํƒ€์ผ areaOpacity?: number; // ์˜์—ญ ํˆฌ๋ช…๋„ pieInnerRadius?: number; // ๋„๋„› ์ฐจํŠธ ๋‚ด๋ถ€ ๋ฐ˜์ง€๋ฆ„ (0-1) stackMode?: "normal" | "percent"; // ๋ˆ„์  ๋ชจ๋“œ } // API ์‘๋‹ต ๊ตฌ์กฐ export interface ApiResponse { success: boolean; data: T; message?: string; error?: string; } // ์ฐจํŠธ ๋ฐ์ดํ„ฐ (๋ณ€ํ™˜ ํ›„) export interface ChartData { labels: string[]; // X์ถ• ๋ ˆ์ด๋ธ” datasets: ChartDataset[]; // Y์ถ• ๋ฐ์ดํ„ฐ์…‹ (๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ) } export interface ChartDataset { label: string; // ์‹œ๋ฆฌ์ฆˆ ์ด๋ฆ„ data: number[]; // ๋ฐ์ดํ„ฐ ๊ฐ’ color?: string; // ์ƒ‰์ƒ } ``` --- ## ๐Ÿ“ ๊ตฌํ˜„ ๋‹จ๊ณ„ ### Phase 1: ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ค์ • UI (4-5์‹œ๊ฐ„) #### Step 1.1: ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ๊ธฐ - [x] `DataSourceSelector.tsx` ์ƒ์„ฑ - [x] DB vs API ์„ ํƒ ๋ผ๋””์˜ค ๋ฒ„ํŠผ - [x] ์„ ํƒ์— ๋”ฐ๋ผ ํ•˜์œ„ UI ๋™์  ๋ Œ๋”๋ง - [x] ์ƒํƒœ ๊ด€๋ฆฌ (ํ˜„์žฌ ์„ ํƒ๋œ ์†Œ์Šค ํƒ€์ž…) #### Step 1.2: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • - [x] `DatabaseConfig.tsx` ์ƒ์„ฑ - [x] ํ˜„์žฌ DB / ์™ธ๋ถ€ DB ์„ ํƒ ๋ผ๋””์˜ค ๋ฒ„ํŠผ - [x] ์™ธ๋ถ€ DB ์„ ํƒ ์‹œ: - **๊ธฐ์กด ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ์—์„œ ๋“ฑ๋ก๋œ ์ปค๋„ฅ์…˜ ๋ชฉ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ** - ๋“œ๋กญ๋‹ค์šด์œผ๋กœ ์ปค๋„ฅ์…˜ ์„ ํƒ (ID, ์ด๋ฆ„, ํƒ€์ž… ํ‘œ์‹œ) - "์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ๋กœ ์ด๋™" ๋งํฌ ์ œ๊ณต - ์„ ํƒ๋œ ์ปค๋„ฅ์…˜ ์ •๋ณด ํ‘œ์‹œ (์ฝ๊ธฐ ์ „์šฉ) - [x] SQL ์—๋””ํ„ฐ ํ†ตํ•ฉ (๊ธฐ์กด `QueryEditor` ์žฌ์‚ฌ์šฉ) - [x] ์ฟผ๋ฆฌ ํ…Œ์ŠคํŠธ ๋ฒ„ํŠผ (์„ ํƒ๋œ ์ปค๋„ฅ์…˜์œผ๋กœ ์‹คํ–‰) #### Step 1.3: REST API ์„ค์ • - [x] `ApiConfig.tsx` ์ƒ์„ฑ - [x] API ์—”๋“œํฌ์ธํŠธ URL ์ž…๋ ฅ - [x] HTTP ๋ฉ”์„œ๋“œ: GET ๊ณ ์ • (UI์—์„œ ํ‘œ์‹œ๋งŒ) - [x] URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€ UI (ํ‚ค-๊ฐ’ ์Œ) - ๋™์  ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€/์ œ๊ฑฐ ๋ฒ„ํŠผ - ์˜ˆ์‹œ: `?category=electronics&limit=10` - [x] ํ—ค๋” ์ถ”๊ฐ€ UI (ํ‚ค-๊ฐ’ ์Œ) - Authorization ํ—ค๋” ๋น ๋ฅธ ์ž…๋ ฅ - ์ผ๋ฐ˜์ ์ธ ํ—ค๋” ํ…œํ”Œ๋ฆฟ ์ œ๊ณต - [x] JSON Path ์„ค์ • (๋ฐ์ดํ„ฐ ์ถ”์ถœ ๊ฒฝ๋กœ) - ์˜ˆ์‹œ: `data.results`, `items`, `response.data` - [x] ํ…Œ์ŠคํŠธ ์š”์ฒญ ๋ฒ„ํŠผ - [x] ์‘๋‹ต ๋ฏธ๋ฆฌ๋ณด๊ธฐ (JSON ๊ตฌ์กฐ ํ‘œ์‹œ) #### Step 1.4: ๋ฐ์ดํ„ฐ ์†Œ์Šค ์œ ํ‹ธ๋ฆฌํ‹ฐ - [x] `dataSourceUtils.ts` ์ƒ์„ฑ - [x] DB ์ปค๋„ฅ์…˜ ๊ฒ€์ฆ ํ•จ์ˆ˜ - [x] API ์š”์ฒญ ์‹คํ–‰ ํ•จ์ˆ˜ - [x] JSON Path ํŒŒ์‹ฑ ํ•จ์ˆ˜ - [x] ๋ฐ์ดํ„ฐ ์ •๊ทœํ™” ํ•จ์ˆ˜ (DB/API ๊ฒฐ๊ณผ๋ฅผ ํ†ต์ผ๋œ ํ˜•์‹์œผ๋กœ) ### Phase 2: ์„œ๋ฒ„ ์ธก API ๊ตฌํ˜„ (1-2์‹œ๊ฐ„) โœ… ๋Œ€๋ถ€๋ถ„ ๊ตฌํ˜„ ์™„๋ฃŒ #### Step 2.1: ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๋ชฉ๋ก ์กฐํšŒ API โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - [x] `GET /api/external-db-connections` - ๊ธฐ์กด ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ์˜ ์ปค๋„ฅ์…˜ ๋ชฉ๋ก ์กฐํšŒ - [x] ํ”„๋ก ํŠธ์—”๋“œ API: `ExternalDbConnectionAPI.getConnections({ is_active: 'Y' })` - [x] ์‘๋‹ต: `{ id, connection_name, db_type, ... }` - [x] ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ - [x] **์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด ์žˆ์Œ!** #### Step 2.2: ์ฟผ๋ฆฌ ์‹คํ–‰ API โœ… ์™ธ๋ถ€ DB ์™„๋ฃŒ, ํ˜„์žฌ DB ํ™•์ธ ํ•„์š” **์™ธ๋ถ€ DB ์ฟผ๋ฆฌ ์‹คํ–‰ โœ… ๊ตฌํ˜„ ์™„๋ฃŒ** - [x] `POST /api/external-db-connections/:id/execute` - ์™ธ๋ถ€ DB ์ฟผ๋ฆฌ ์‹คํ–‰ - [x] ํ”„๋ก ํŠธ์—”๋“œ API: `ExternalDbConnectionAPI.executeQuery(connectionId, query)` - [x] SELECT ์ฟผ๋ฆฌ ๊ฒ€์ฆ ๋ฐ SQL Injection ๋ฐฉ์ง€ - [x] **์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด ์žˆ์Œ!** **ํ˜„์žฌ DB ์ฟผ๋ฆฌ ์‹คํ–‰ - ํ™•์ธ ํ•„์š”** - [ ] `POST /api/dashboards/execute-query` - ํ˜„์žฌ DB ์ฟผ๋ฆฌ ์‹คํ–‰ (์ด๋ฏธ ์žˆ๋Š”์ง€ ํ™•์ธ ํ•„์š”) - [ ] SELECT ์ฟผ๋ฆฌ ๊ฒ€์ฆ (์ •๊ทœ์‹ + SQL ํŒŒ์„œ) - [ ] SQL Injection ๋ฐฉ์ง€ - [ ] ์ฟผ๋ฆฌ ํƒ€์ž„์•„์›ƒ ์„ค์ • - [ ] ๊ฒฐ๊ณผ ํ–‰ ์ˆ˜ ์ œํ•œ (์ตœ๋Œ€ 1000ํ–‰) - [ ] ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๋ฐ ๋กœ๊น… #### Step 2.3: REST API ํ”„๋ก์‹œ โŒ ๋ถˆํ•„์š” (CORS ํ—ˆ์šฉ๋œ Open API ์‚ฌ์šฉ) - [x] ~~GET /api/dashboards/fetch-api~~ - ๋ถˆํ•„์š” (ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ง์ ‘ ํ˜ธ์ถœ) - [x] Open API๋Š” CORS๋ฅผ ํ—ˆ์šฉํ•˜๋ฏ€๋กœ ํ”„๋ก์‹œ ์—†์ด ์ง์ ‘ ํ˜ธ์ถœ ๊ฐ€๋Šฅ - [x] `ApiConfig.tsx`์—์„œ `fetch()` ์ง์ ‘ ์‚ฌ์šฉ ### Phase 3: ์ฐจํŠธ ์„ค์ • UI ๊ฐœ์„  (3-4์‹œ๊ฐ„) #### Step 3.1: ์ถ• ๋งคํผ - [ ] `AxisMapper.tsx` ์ƒ์„ฑ - [ ] X์ถ• ํ•„๋“œ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด - [ ] Y์ถ• ํ•„๋“œ ๋‹ค์ค‘ ์„ ํƒ (์ฒดํฌ๋ฐ•์Šค) - [ ] ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ž๋™ ๊ฐ์ง€ ๋ฐ ํ‘œ์‹œ - [ ] ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ฒซ 3ํ–‰) - [ ] ์ถ• ๋ผ๋ฒจ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• #### Step 3.2: ์Šคํƒ€์ผ ์„ค์ • - [ ] `StyleConfig.tsx` ์ƒ์„ฑ - [ ] ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ ์„ ํƒ (์‚ฌ์ „ ์ •์˜ + ์ปค์Šคํ…€) - [ ] ๋ฒ”๋ก€ ์œ„์น˜ ์„ ํƒ - [ ] ๊ทธ๋ฆฌ๋“œ ํ‘œ์‹œ/์ˆจ๊น€ - [ ] ์• ๋‹ˆ๋ฉ”์ด์…˜ ์„ค์ • - [ ] ์ฐจํŠธ๋ณ„ ํŠน์ˆ˜ ์˜ต์…˜ - ๋ง‰๋Œ€ ์ฐจํŠธ: ์ˆ˜ํ‰/์ˆ˜์ง - ์„  ์ฐจํŠธ: ๋ถ€๋“œ๋Ÿฌ์›€ ์ •๋„ - ์› ์ฐจํŠธ: ๋„๋„› ๋ชจ๋“œ #### Step 3.3: ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ - [ ] `ChartPreview.tsx` ์ƒ์„ฑ - [ ] ์ถ•์†Œ๋œ ์ฐจํŠธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (300x200) - [ ] ์„ค์ • ๋ณ€๊ฒฝ ์‹œ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ - [ ] ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ - [ ] ์—๋Ÿฌ ํ‘œ์‹œ ### Phase 4: D3 ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ (6-8์‹œ๊ฐ„) #### Step 4.1: ์ฐจํŠธ ๋ Œ๋”๋Ÿฌ (๊ณตํ†ต) - [ ] `ChartRenderer.tsx` ์ƒ์„ฑ - [ ] ์ฐจํŠธ ํƒ€์ž…์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง - [ ] ๋ฐ์ดํ„ฐ ์ •๊ทœํ™” ๋ฐ ๋ณ€ํ™˜ - [ ] ๊ณตํ†ต ๋ ˆ์ด์•„์›ƒ (์ œ๋ชฉ, ๋ฒ”๋ก€) - [ ] ๋ฐ˜์‘ํ˜• ํฌ๊ธฐ ์กฐ์ ˆ - [ ] ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ #### Step 4.2: ๋ง‰๋Œ€ ์ฐจํŠธ - [ ] `BarChart.tsx` ์ƒ์„ฑ - [ ] D3 ์Šค์ผ€์ผ ์„ค์ • (x: ๋ฒ”์ฃผํ˜•, y: ์„ ํ˜•) - [ ] ๋ง‰๋Œ€ ๋ Œ๋”๋ง (rect ์š”์†Œ) - [ ] ์ถ• ๋ Œ๋”๋ง (d3-axis) - [ ] ํˆดํŒ ๊ตฌํ˜„ - [ ] ์• ๋‹ˆ๋ฉ”์ด์…˜ (๋†’์ด ์ „ํ™˜) - [ ] ์ˆ˜ํ‰/์ˆ˜์ง ๋ชจ๋“œ ์ง€์› - [ ] ๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ (๊ทธ๋ฃนํ™”) #### Step 4.3: ์„  ์ฐจํŠธ - [ ] `LineChart.tsx` ์ƒ์„ฑ - [ ] D3 ๋ผ์ธ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ (d3.line) - [ ] ๋ถ€๋“œ๋Ÿฌ์šด ๊ณก์„  (d3.curveMonotoneX) - [ ] ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ ํ‘œ์‹œ (circle) - [ ] ํˆดํŒ ๊ตฌํ˜„ - [ ] ์• ๋‹ˆ๋ฉ”์ด์…˜ (path ๊ธธ์ด ์ „ํ™˜) - [ ] ๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ (์—ฌ๋Ÿฌ ์„ ) - [ ] ๋ˆ„๋ฝ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ #### Step 4.4: ์˜์—ญ ์ฐจํŠธ - [ ] `AreaChart.tsx` ์ƒ์„ฑ - [ ] D3 ์˜์—ญ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ (d3.area) - [ ] ํˆฌ๋ช…๋„ ์„ค์ • - [ ] ๋ˆ„์  ๋ชจ๋“œ ์ง€์› (d3.stack) - [ ] ์„  ์ฐจํŠธ ๊ธฐ๋Šฅ ์žฌ์‚ฌ์šฉ - [ ] ์• ๋‹ˆ๋ฉ”์ด์…˜ #### Step 4.5: ์› ์ฐจํŠธ - [ ] `PieChart.tsx` ์ƒ์„ฑ - [ ] D3 ํŒŒ์ด ๋ ˆ์ด์•„์›ƒ (d3.pie) - [ ] ์•„ํฌ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ (d3.arc) - [ ] ๋„๋„› ๋ชจ๋“œ (innerRadius) - [ ] ๋ผ๋ฒจ ๋ฐฐ์น˜ (์ค‘์‹ฌ ๋˜๋Š” ์™ธ๋ถ€) - [ ] ํˆดํŒ ๊ตฌํ˜„ - [ ] ์• ๋‹ˆ๋ฉ”์ด์…˜ (ํšŒ์ „ ์ „ํ™˜) - [ ] ํผ์„ผํŠธ ํ‘œ์‹œ #### Step 4.6: ๋ˆ„์  ๋ง‰๋Œ€ ์ฐจํŠธ - [ ] `StackedBarChart.tsx` ์ƒ์„ฑ - [ ] D3 ์Šคํƒ ๋ ˆ์ด์•„์›ƒ (d3.stack) - [ ] ๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ ๋ˆ„์  - [ ] ์ผ๋ฐ˜ ๋ˆ„์  vs ํผ์„ผํŠธ ๋ชจ๋“œ - [ ] ๋ง‰๋Œ€ ์ฐจํŠธ ๋กœ์ง ์žฌ์‚ฌ์šฉ - [ ] ๋ฒ”๋ก€ ์ƒ‰์ƒ ๋งคํ•‘ #### Step 4.7: ํ˜ผํ•ฉ ์ฐจํŠธ - [ ] `ComboChart.tsx` ์ƒ์„ฑ - [ ] ๋ง‰๋Œ€ + ์„  ์กฐํ•ฉ - [ ] ์ด์ค‘ Y์ถ• (์ขŒ์ธก: ๋ง‰๋Œ€, ์šฐ์ธก: ์„ ) - [ ] ์Šค์ผ€์ผ ๋…๋ฆฝ ์„ค์ • - [ ] ๋ง‰๋Œ€/์„  ์ฐจํŠธ ๋กœ์ง ๊ฒฐํ•ฉ - [ ] ๋ณต์žกํ•œ ํˆดํŒ (๋‘ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ) #### Step 4.8: ์ฐจํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ - [ ] `chartUtils.ts` ์ƒ์„ฑ - [ ] ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ํ•จ์ˆ˜ (QueryResult โ†’ ChartData) - [ ] ๋‚ ์งœ ํŒŒ์‹ฑ ๋ฐ ํฌ๋งทํŒ… - [ ] ์ˆซ์ž ํฌ๋งทํŒ… (์ฒœ ๋‹จ์œ„ ์ฝค๋งˆ, ์†Œ์ˆ˜์ ) - [ ] ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ ์ •์˜ - [ ] ๋ฐ˜์‘ํ˜• ํฌ๊ธฐ ๊ณ„์‚ฐ #### Step 4.9: D3 ํ—ฌํผ - [ ] `d3Helpers.ts` ์ƒ์„ฑ - [ ] ๊ณตํ†ต ์Šค์ผ€์ผ ์ƒ์„ฑ - [ ] ์ถ• ์ƒ์„ฑ ๋ฐ ์Šคํƒ€์ผ๋ง - [ ] ๊ทธ๋ฆฌ๋“œ ๋ผ์ธ ์ถ”๊ฐ€ - [ ] ํˆดํŒ DOM ์ƒ์„ฑ/์ œ๊ฑฐ - [ ] SVG ๋งˆ์ง„ ๊ณ„์‚ฐ ### Phase 5: ์ฐจํŠธ ํ†ตํ•ฉ ๋ฐ ๋ Œ๋”๋ง (2-3์‹œ๊ฐ„) #### Step 5.1: CanvasElement ํ†ตํ•ฉ - [ ] `CanvasElement.tsx` ์ˆ˜์ • - [ ] ์ฐจํŠธ ์š”์†Œ ๊ฐ์ง€ (element.type === 'chart') - [ ] `ChartRenderer` ์ปดํฌ๋„ŒํŠธ ์ž„ํฌํŠธ ๋ฐ ๋ Œ๋”๋ง - [ ] ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ - [ ] ์—๋Ÿฌ ์ƒํƒœ ํ‘œ์‹œ - [ ] ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ๋กœ์ง #### Step 5.2: ๋ฐ์ดํ„ฐ ํŽ˜์นญ - [ ] ์ฐจํŠธ ๋งˆ์šดํŠธ ์‹œ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ - [ ] ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ํƒ€์ด๋จธ ์„ค์ • - [ ] ์ˆ˜๋™ ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ - [ ] ๋กœ๋”ฉ/์—๋Ÿฌ/์„ฑ๊ณต ์ƒํƒœ ๊ด€๋ฆฌ - [ ] ์บ์‹ฑ (์„ ํƒ์ ) #### Step 5.3: ElementConfigModal ๋ฆฌํŒฉํ† ๋ง - [ ] ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ UI ํ†ตํ•ฉ - [ ] 3๋‹จ๊ณ„ ํ”Œ๋กœ์šฐ ๊ตฌํ˜„ 1. ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ ๋ฐ ์„ค์ • 2. ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฐ ๊ฒ€์ฆ 3. ์ถ• ๋งคํ•‘ ๋ฐ ์Šคํƒ€์ผ ์„ค์ • - [ ] ์ง„ํ–‰ ํ‘œ์‹œ๊ธฐ (์Šคํ… ์ธ๋””์ผ€์ดํ„ฐ) - [ ] ๋’ค๋กœ/๋‹ค์Œ ๋ฒ„ํŠผ ### Phase 6: ํ…Œ์ŠคํŠธ ๋ฐ ์ตœ์ ํ™” (2-3์‹œ๊ฐ„) #### Step 6.1: ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ - [ ] ๊ฐ ์ฐจํŠธ ํƒ€์ž… ๋ Œ๋”๋ง ํ™•์ธ - [ ] DB ์ฟผ๋ฆฌ ์‹คํ–‰ ๋ฐ ์ฐจํŠธ ์ƒ์„ฑ - [ ] API ํ˜ธ์ถœ ๋ฐ ์ฐจํŠธ ์ƒ์„ฑ - [ ] ๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ ์ฐจํŠธ ํ™•์ธ - [ ] ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ๋™์ž‘ ํ™•์ธ - [ ] ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ™•์ธ #### Step 6.2: UI/UX ๊ฐœ์„  - [ ] ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ์ถ”๊ฐ€ - [ ] ๋นˆ ๋ฐ์ดํ„ฐ ์ƒํƒœ UI - [ ] ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๊ฐœ์„  - [ ] ํˆดํŒ ์Šคํƒ€์ผ๋ง - [ ] ๋ฒ”๋ก€ ์Šคํƒ€์ผ๋ง - [ ] ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ ํ™•์ธ #### Step 6.3: ์„ฑ๋Šฅ ์ตœ์ ํ™” - [ ] D3 ๋ Œ๋”๋ง ์ตœ์ ํ™” (๋ถˆํ•„์š”ํ•œ ์žฌ๋ Œ๋”๋ง ๋ฐฉ์ง€) - [ ] ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ (์ƒ˜ํ”Œ๋ง, ํŽ˜์ด์ง•) - [ ] ๋ฉ”๋ชจ์ด์ œ์ด์…˜ (useMemo, useCallback) - [ ] SVG ์ตœ์ ํ™” - [ ] ์ฐจํŠธ ๋ฐ์ดํ„ฐ ์บ์‹ฑ --- ## ๐Ÿ”’ ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ ### SQL Injection ๋ฐฉ์ง€ - ์„œ๋ฒ„ ์ธก์—์„œ ์ฟผ๋ฆฌ ํƒ€์ž… ์—„๊ฒฉ ๊ฒ€์ฆ (SELECT๋งŒ ํ—ˆ์šฉ) - ์ •๊ทœ์‹ + SQL ํŒŒ์„œ ์‚ฌ์šฉ - Prepared Statement ์‚ฌ์šฉ (ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ) - ์œ„ํ—˜ํ•œ ํ‚ค์›Œ๋“œ ์ฐจ๋‹จ (DROP, DELETE, UPDATE, INSERT, EXEC ๋“ฑ) ### ์™ธ๋ถ€ DB ์ปค๋„ฅ์…˜ ๋ณด์•ˆ - ๊ธฐ์กด "์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ"์—์„œ ๋ณด์•ˆ ์ฒ˜๋ฆฌ๋จ - ์ฐจํŠธ ์‹œ์Šคํ…œ์—์„œ๋Š” ์ปค๋„ฅ์…˜ ID๋งŒ ์‚ฌ์šฉ - ๋ฏผ๊ฐ ์ •๋ณด(๋น„๋ฐ€๋ฒˆํ˜ธ, ํ˜ธ์ŠคํŠธ ๋“ฑ)๋Š” ์ฐจํŠธ ์„ค์ •์— ๋…ธ์ถœํ•˜์ง€ ์•Š์Œ - ํƒ€์ž„์•„์›ƒ ์„ค์ • (30์ดˆ) ### API ๋ณด์•ˆ - CORS ์ •์ฑ… ํ™•์ธ - ๋ฏผ๊ฐํ•œ ํ—ค๋” ๋กœ๊น… ๋ฐฉ์ง€ (Authorization ๋“ฑ) - ์š”์ฒญ ํฌ๊ธฐ ์ œํ•œ - Rate Limiting (API ํ˜ธ์ถœ ๋นˆ๋„ ์ œํ•œ) --- ## ๐ŸŽจ UI/UX ๊ฐœ์„  ์‚ฌํ•ญ ### ์„ค์ • ํ”Œ๋กœ์šฐ 1. **๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ** - ํฐ ์•„์ด์ฝ˜๊ณผ ์„ค๋ช…์œผ๋กœ DB vs API ์„ ํƒ - ๊ฐ ๋ฐฉ์‹์˜ ์žฅ๋‹จ์  ์•ˆ๋‚ด 2. **๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ** - DB: SQL ์—๋””ํ„ฐ + ์‹คํ–‰ ๋ฒ„ํŠผ - API: URL, ๋ฉ”์„œ๋“œ, ํ—ค๋”, ๋ณธ๋ฌธ ์ž…๋ ฅ - ํ…Œ์ŠคํŠธ ๋ฒ„ํŠผ์œผ๋กœ ์ฆ‰์‹œ ํ™•์ธ 3. **๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ** - ์ฟผ๋ฆฌ/API ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ํ…Œ์ด๋ธ”๋กœ ํ‘œ์‹œ (์ตœ๋Œ€ 10ํ–‰) - ์ปฌ๋Ÿผ๋ช…๊ณผ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ 4. **์ฐจํŠธ ์„ค์ •** - X/Y์ถ• ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ๋งคํ•‘ - ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ž‘์€ ์ฐจํŠธ) - ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์„ ํƒ ### ํ”ผ๋“œ๋ฐฑ ๋ฉ”์‹œ์ง€ - โœ… ์„ฑ๊ณต: "๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค (45ํ–‰)" - โš ๏ธ ๊ฒฝ๊ณ : "์ฟผ๋ฆฌ ์‹คํ–‰์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค" - โŒ ์˜ค๋ฅ˜: "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: ์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ" ### ๋กœ๋”ฉ ์ƒํƒœ - ์Šค์ผˆ๋ ˆํ†ค UI (์ฐจํŠธ ์œค๊ณฝ) - ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ (๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ) - ์ทจ์†Œ ๋ฒ„ํŠผ (์žฅ์‹œ๊ฐ„ ์‹คํ–‰ ์ฟผ๋ฆฌ) --- ## ๐Ÿ“Š ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ๋ฐ ์‹œ๋‚˜๋ฆฌ์˜ค ### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ์›”๋ณ„ ๋งค์ถœ ์ถ”์ด (DB ์ฟผ๋ฆฌ) ```sql SELECT TO_CHAR(order_date, 'YYYY-MM') as month, SUM(total_amount) as sales FROM orders WHERE order_date >= CURRENT_DATE - INTERVAL '12 months' GROUP BY TO_CHAR(order_date, 'YYYY-MM') ORDER BY month; ``` - **์ฐจํŠธ ํƒ€์ž…**: Line Chart - **X์ถ•**: month - **Y์ถ•**: sales ### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ์ œํ’ˆ ๋น„๊ต (๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ) ```sql SELECT DATE_TRUNC('month', order_date) as month, SUM(CASE WHEN product_category = '๊ฐค๋Ÿญ์‹œ' THEN amount ELSE 0 END) as galaxy, SUM(CASE WHEN product_category = '์•„์ดํฐ' THEN amount ELSE 0 END) as iphone FROM orders WHERE order_date >= CURRENT_DATE - INTERVAL '12 months' GROUP BY DATE_TRUNC('month', order_date) ORDER BY month; ``` - **์ฐจํŠธ ํƒ€์ž…**: Combo Chart (Bar + Line) - **X์ถ•**: month - **Y์ถ•**: [galaxy, iphone] (๋‹ค์ค‘) ### ์‹œ๋‚˜๋ฆฌ์˜ค 3: ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋งค์ถœ (์› ์ฐจํŠธ) ```sql SELECT category, SUM(amount) as total FROM sales WHERE sale_date >= CURRENT_DATE - INTERVAL '1 month' GROUP BY category ORDER BY total DESC LIMIT 10; ``` - **์ฐจํŠธ ํƒ€์ž…**: Pie Chart (Donut) - **X์ถ•**: category - **Y์ถ•**: total ### ์‹œ๋‚˜๋ฆฌ์˜ค 4: REST API (์‹ค์‹œ๊ฐ„ ํ™˜์œจ) - **API**: `https://api.exchangerate-api.com/v4/latest/USD` - **JSON Path**: `rates` - **๋ณ€ํ™˜**: Object๋ฅผ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ (ํ†ตํ™”: ํ™˜์œจ) - **์ฐจํŠธ ํƒ€์ž…**: Bar Chart - **X์ถ•**: ํ†ตํ™” ์ฝ”๋“œ (KRW, JPY, EUR ๋“ฑ) - **Y์ถ•**: ํ™˜์œจ --- ## โœ… ์™„๋ฃŒ ๊ธฐ์ค€ ### Phase 1: ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ค์ • - [x] DB ์ปค๋„ฅ์…˜ ์„ค์ • UI ์ž‘๋™ - [x] ์™ธ๋ถ€ DB ์ปค๋„ฅ์…˜ ์ €์žฅ ๋ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ - [x] API ์„ค์ • UI ์ž‘๋™ - [x] ํ…Œ์ŠคํŠธ ๋ฒ„ํŠผ์œผ๋กœ ์ฆ‰์‹œ ํ™•์ธ ๊ฐ€๋Šฅ ### Phase 2: ์„œ๋ฒ„ API - [x] ์™ธ๋ถ€ DB ์ปค๋„ฅ์…˜ CRUD API ์ž‘๋™ - [x] ์ฟผ๋ฆฌ ์‹คํ–‰ API (ํ˜„์žฌ/์™ธ๋ถ€ DB) - [x] SELECT ์ฟผ๋ฆฌ ๊ฒ€์ฆ ๋ฐ SQL Injection ๋ฐฉ์ง€ - [x] API ํ”„๋ก์‹œ ์ž‘๋™ ### Phase 3: ์ฐจํŠธ ์„ค์ • UI - [x] ์ถ• ๋งคํ•‘ UI ์ง๊ด€์  - [x] ๋‹ค์ค‘ Y์ถ• ์„ ํƒ ๊ฐ€๋Šฅ - [x] ์Šคํƒ€์ผ ์„ค์ • UI ์ž‘๋™ - [x] ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ‘œ์‹œ ### Phase 4: D3 ์ฐจํŠธ - [x] 6๊ฐ€์ง€ ์ฐจํŠธ ํƒ€์ž… ๋ชจ๋‘ ๋ Œ๋”๋ง - [x] ํˆดํŒ ํ‘œ์‹œ - [x] ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ถ€๋“œ๋Ÿฌ์›€ - [x] ๋ฐ˜์‘ํ˜• ํฌ๊ธฐ ์กฐ์ ˆ - [x] ๋‹ค์ค‘ ์‹œ๋ฆฌ์ฆˆ ์ง€์› ### Phase 5: ํ†ตํ•ฉ - [x] ์บ”๋ฒ„์Šค์—์„œ ์ฐจํŠธ ํ‘œ์‹œ - [x] ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ์ž‘๋™ - [x] ์„ค์ • ๋ชจ๋‹ฌ 3๋‹จ๊ณ„ ํ”Œ๋กœ์šฐ ์™„๋ฃŒ - [x] ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ/์—๋Ÿฌ ์ƒํƒœ ํ‘œ์‹œ ### Phase 6: ํ…Œ์ŠคํŠธ - [x] ๋ชจ๋“  ์ฐจํŠธ ํƒ€์ž… ์ •์ƒ ์ž‘๋™ - [x] DB/API ๋ฐ์ดํ„ฐ ์†Œ์Šค ๋ชจ๋‘ ์ž‘๋™ - [x] ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ ์ ˆ - [x] ์„ฑ๋Šฅ ์ด์Šˆ ์—†์Œ (1000ํ–‰ ๋ฐ์ดํ„ฐ) --- ## ๐Ÿš€ ํ–ฅํ›„ ํ™•์žฅ ๊ณ„ํš - **์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ**: WebSocket ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ถ”๊ฐ€ - **๊ณ ๊ธ‰ ์ฐจํŠธ**: Scatter Plot, Heatmap, Radar Chart - **๋ฐ์ดํ„ฐ ๋ณ€ํ™˜**: ํ•„ํ„ฐ๋ง, ์ •๋ ฌ, ๊ณ„์‚ฐ ํ•„๋“œ ์ถ”๊ฐ€ - **์ฐจํŠธ ์ƒํ˜ธ์ž‘์šฉ**: ํด๋ฆญ/๋“œ๋ž˜๊ทธ๋กœ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง - **๋‚ด๋ณด๋‚ด๊ธฐ**: PNG, SVG, PDF ์ €์žฅ - **ํ…œํ”Œ๋ฆฟ**: ์‚ฌ์ „ ์ •์˜๋œ ์ฐจํŠธ ํ…œํ”Œ๋ฆฟ (์—…์ข…๋ณ„) --- ## ๐Ÿ“… ์˜ˆ์ƒ ์ผ์ • - **Phase 1**: 1์ผ (๋ฐ์ดํ„ฐ ์†Œ์Šค UI) - **Phase 2**: 0.5์ผ (์„œ๋ฒ„ API) - ๊ธฐ์กด ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ ํ™œ์šฉ์œผ๋กœ ๋‹จ์ถ• - **Phase 3**: 1์ผ (์ฐจํŠธ ์„ค์ • UI) - **Phase 4**: 2์ผ (D3 ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ) - **Phase 5**: 0.5์ผ (ํ†ตํ•ฉ) - **Phase 6**: 0.5์ผ (ํ…Œ์ŠคํŠธ) **์ด ์˜ˆ์ƒ ์‹œ๊ฐ„**: 5.5์ผ (44์‹œ๊ฐ„) --- **๊ตฌํ˜„ ์‹œ์ž‘์ผ**: 2025-10-14 **๋ชฉํ‘œ ์™„๋ฃŒ์ผ**: 2025-10-20 **ํ˜„์žฌ ์ง„ํ–‰๋ฅ **: 90% (Phase 1-5 ์™„๋ฃŒ, D3 ์ฐจํŠธ ์ถ”๊ฐ€ ๊ตฌํ˜„ โœ…) --- ## ๐ŸŽฏ ๋‹ค์Œ ๋‹จ๊ณ„ 1. ~~Phase 1 ์™„๋ฃŒ: ๋ฐ์ดํ„ฐ ์†Œ์Šค UI ๊ตฌํ˜„~~ โœ… 2. ~~Phase 2 ์™„๋ฃŒ: ์„œ๋ฒ„ API ํ†ตํ•ฉ~~ โœ… - [x] ์™ธ๋ถ€ DB ์ปค๋„ฅ์…˜ ๋ชฉ๋ก ์กฐํšŒ API (์ด๋ฏธ ๊ตฌํ˜„๋จ) - [x] ํ˜„์žฌ DB ์ฟผ๋ฆฌ ์‹คํ–‰ API (์ด๋ฏธ ๊ตฌํ˜„๋จ) - [x] QueryEditor ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ (ํ˜„์žฌ/์™ธ๋ถ€ DB) - [x] DatabaseConfig ์‹ค์ œ API ์—ฐ๋™ 3. **Phase 3 ์‹œ์ž‘**: ์ฐจํŠธ ์„ค์ • UI ๊ฐœ์„  - [ ] ์ถ• ๋งคํผ ๋ฐ ์Šคํƒ€์ผ ์„ค์ • UI - [ ] ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ 4. **Phase 4**: D3.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„ 5. **Phase 5**: CanvasElement ํ†ตํ•ฉ ๋ฐ ๋ฐ์ดํ„ฐ ํŽ˜์นญ --- ## ๐Ÿ“Š Phase 2 ์ตœ์ข… ์ •๋ฆฌ ### โœ… ๊ตฌํ˜„ ์™„๋ฃŒ๋œ API ํ†ตํ•ฉ 1. **GET /api/external-db-connections** - ์™ธ๋ถ€ DB ์ปค๋„ฅ์…˜ ๋ชฉ๋ก ์กฐํšŒ - ํ”„๋ก ํŠธ์—”๋“œ: `ExternalDbConnectionAPI.getConnections({ is_active: 'Y' })` - ํ†ตํ•ฉ: `DatabaseConfig.tsx` 2. **POST /api/external-db-connections/:id/execute** - ์™ธ๋ถ€ DB ์ฟผ๋ฆฌ ์‹คํ–‰ - ํ”„๋ก ํŠธ์—”๋“œ: `ExternalDbConnectionAPI.executeQuery(connectionId, query)` - ํ†ตํ•ฉ: `QueryEditor.tsx` 3. **POST /api/dashboards/execute-query** - ํ˜„์žฌ DB ์ฟผ๋ฆฌ ์‹คํ–‰ - ํ”„๋ก ํŠธ์—”๋“œ: `dashboardApi.executeQuery(query)` - ํ†ตํ•ฉ: `QueryEditor.tsx` ### โŒ ๋ถˆํ•„์š” (์ œ๊ฑฐ๋จ) 4. ~~**GET /api/dashboards/fetch-api**~~ - Open API๋Š” CORS ํ—ˆ์šฉ๋˜๋ฏ€๋กœ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ง์ ‘ ํ˜ธ์ถœ - `ApiConfig.tsx`์—์„œ `fetch()` ์ง์ ‘ ์‚ฌ์šฉ --- ## ๐ŸŽ‰ ์ „์ฒด ๊ตฌํ˜„ ์™„๋ฃŒ ์š”์•ฝ ### Phase 1: ๋ฐ์ดํ„ฐ ์†Œ์Šค UI โœ… - `DataSourceSelector`: DB vs API ์„ ํƒ UI - `DatabaseConfig`: ํ˜„์žฌ DB / ์™ธ๋ถ€ DB ์„ ํƒ ๋ฐ API ์—ฐ๋™ - `ApiConfig`: REST API ์„ค์ • - `dataSourceUtils`: ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ### Phase 2: ์„œ๋ฒ„ API ํ†ตํ•ฉ โœ… - `GET /api/external-db-connections`: ์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๋ชฉ๋ก ์กฐํšŒ - `POST /api/external-db-connections/:id/execute`: ์™ธ๋ถ€ DB ์ฟผ๋ฆฌ ์‹คํ–‰ - `POST /api/dashboards/execute-query`: ํ˜„์žฌ DB ์ฟผ๋ฆฌ ์‹คํ–‰ - **QueryEditor**: ํ˜„์žฌ DB / ์™ธ๋ถ€ DB ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ ์™„๋ฃŒ ### Phase 3: ์ฐจํŠธ ์„ค์ • UI โœ… - `ChartConfigPanel`: X/Y์ถ• ๋งคํ•‘, ์Šคํƒ€์ผ ์„ค์ •, ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ - ๋‹ค์ค‘ Y์ถ• ์„ ํƒ ์ง€์› - ์„ค์ • ๋ฏธ๋ฆฌ๋ณด๊ธฐ ### Phase 4: D3 ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ โœ… - **D3 ์ฐจํŠธ ๊ตฌํ˜„** (6์ข…): - `BarChart.tsx`: ๋ง‰๋Œ€ ์ฐจํŠธ - `LineChart.tsx`: ์„  ์ฐจํŠธ - `AreaChart.tsx`: ์˜์—ญ ์ฐจํŠธ - `PieChart.tsx`: ์›/๋„๋„› ์ฐจํŠธ - `StackedBarChart.tsx`: ๋ˆ„์  ๋ง‰๋Œ€ ์ฐจํŠธ - `Chart.tsx`: ํ†ตํ•ฉ ์ปดํฌ๋„ŒํŠธ - **Recharts ์™„์ „ ์ œ๊ฑฐ**: D3๋กœ ์™„์ „ํžˆ ๋Œ€์ฒด ### Phase 5: ํ†ตํ•ฉ โœ… - `CanvasElement`: ์ฐจํŠธ ๋ Œ๋”๋ง ํ†ตํ•ฉ ์™„๋ฃŒ - `ChartRenderer`: D3 ๊ธฐ๋ฐ˜์œผ๋กœ ์™„์ „ํžˆ ๊ต์ฒด - `chartDataTransform.ts`: ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ - ๋ฐ์ดํ„ฐ ํŽ˜์นญ ๋ฐ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ