사소한 수정
This commit is contained in:
parent
95c685051d
commit
3613f9eef4
|
|
@ -10,7 +10,7 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { TrendingUp, AlertCircle } from "lucide-react";
|
import { AlertCircle } from "lucide-react";
|
||||||
import { DateFilterPanel } from "./DateFilterPanel";
|
import { DateFilterPanel } from "./DateFilterPanel";
|
||||||
import { extractTableNameFromQuery } from "./utils/queryHelpers";
|
import { extractTableNameFromQuery } from "./utils/queryHelpers";
|
||||||
import { dashboardApi } from "@/lib/api/dashboard";
|
import { dashboardApi } from "@/lib/api/dashboard";
|
||||||
|
|
@ -85,34 +85,23 @@ export function ChartConfigPanel({
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableName = extractTableNameFromQuery(query);
|
const tableName = extractTableNameFromQuery(query);
|
||||||
console.log("📋 쿼리에서 추출한 테이블명:", tableName);
|
|
||||||
console.log("📋 원본 쿼리:", query);
|
|
||||||
|
|
||||||
if (!tableName) {
|
if (!tableName) {
|
||||||
console.log("⚠️ 테이블명을 추출할 수 없습니다.");
|
|
||||||
setDateColumns([]);
|
setDateColumns([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("🔍 테이블 스키마 조회 중:", tableName);
|
|
||||||
dashboardApi
|
dashboardApi
|
||||||
.getTableSchema(tableName)
|
.getTableSchema(tableName)
|
||||||
.then((schema) => {
|
.then((schema) => {
|
||||||
console.log("✅ 스키마 조회 성공:", schema);
|
|
||||||
console.log("📅 전체 날짜 컬럼:", schema.dateColumns);
|
|
||||||
console.log("📊 쿼리 결과 컬럼:", availableColumns);
|
|
||||||
|
|
||||||
// 원본 테이블의 모든 날짜 컬럼을 표시
|
// 원본 테이블의 모든 날짜 컬럼을 표시
|
||||||
// (SELECT에 없어도 WHERE 절에 사용 가능)
|
// (SELECT에 없어도 WHERE 절에 사용 가능)
|
||||||
setDateColumns(schema.dateColumns);
|
setDateColumns(schema.dateColumns);
|
||||||
console.log("✨ 사용 가능한 날짜 필터 컬럼:", schema.dateColumns);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("❌ 테이블 스키마 조회 실패:", error);
|
console.error("❌ 테이블 스키마 조회 실패:", error);
|
||||||
// 실패 시 빈 배열 (날짜 필터 비활성화)
|
// 실패 시 빈 배열 (날짜 필터 비활성화)
|
||||||
setDateColumns([]);
|
setDateColumns([]);
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [query, queryResult, dataSourceType]);
|
}, [query, queryResult, dataSourceType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -125,7 +114,7 @@ export function ChartConfigPanel({
|
||||||
<Card className="border-blue-200 bg-blue-50 p-4">
|
<Card className="border-blue-200 bg-blue-50 p-4">
|
||||||
<div className="mb-2 flex items-center gap-2">
|
<div className="mb-2 flex items-center gap-2">
|
||||||
<AlertCircle className="h-4 w-4 text-blue-600" />
|
<AlertCircle className="h-4 w-4 text-blue-600" />
|
||||||
<h4 className="font-semibold text-blue-900">📋 API 응답 데이터 미리보기</h4>
|
<h4 className="font-semibold text-blue-900">API 응답 데이터 미리보기</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded bg-white p-3 text-xs">
|
<div className="rounded bg-white p-3 text-xs">
|
||||||
<div className="mb-2 text-gray-600">총 {queryResult.totalRows}개 데이터 중 첫 번째 행:</div>
|
<div className="mb-2 text-gray-600">총 {queryResult.totalRows}개 데이터 중 첫 번째 행:</div>
|
||||||
|
|
@ -139,7 +128,7 @@ export function ChartConfigPanel({
|
||||||
<Alert variant="destructive">
|
<Alert variant="destructive">
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<div className="font-semibold">⚠️ 차트에 사용할 수 없는 컬럼 감지</div>
|
<div className="font-semibold">차트에 사용할 수 없는 컬럼 감지</div>
|
||||||
<div className="mt-1 text-sm">
|
<div className="mt-1 text-sm">
|
||||||
다음 컬럼은 객체 또는 배열 타입이라서 차트 축으로 선택할 수 없습니다:
|
다음 컬럼은 객체 또는 배열 타입이라서 차트 축으로 선택할 수 없습니다:
|
||||||
<div className="mt-1 flex flex-wrap gap-1">
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
|
|
@ -151,7 +140,7 @@ export function ChartConfigPanel({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-xs text-gray-600">
|
<div className="mt-2 text-xs text-gray-600">
|
||||||
💡 <strong>해결 방법:</strong> JSON Path를 사용하여 중첩된 객체 내부의 값을 직접 추출하세요.
|
<strong>해결 방법:</strong> JSON Path를 사용하여 중첩된 객체 내부의 값을 직접 추출하세요.
|
||||||
<br />
|
<br />
|
||||||
예: <code className="rounded bg-gray-100 px-1">main</code> 또는{" "}
|
예: <code className="rounded bg-gray-100 px-1">main</code> 또는{" "}
|
||||||
<code className="rounded bg-gray-100 px-1">data.items</code>
|
<code className="rounded bg-gray-100 px-1">data.items</code>
|
||||||
|
|
@ -203,7 +192,7 @@ export function ChartConfigPanel({
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
{simpleColumns.length === 0 && (
|
{simpleColumns.length === 0 && (
|
||||||
<p className="text-xs text-red-500">⚠️ 사용 가능한 컬럼이 없습니다. JSON Path를 확인하세요.</p>
|
<p className="text-xs text-red-500">사용 가능한 컬럼이 없습니다. JSON Path를 확인하세요.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -221,17 +210,14 @@ export function ChartConfigPanel({
|
||||||
{/* 숫자 타입 우선 표시 */}
|
{/* 숫자 타입 우선 표시 */}
|
||||||
{numericColumns.length > 0 && (
|
{numericColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-2 text-xs font-medium text-green-700">✅ 숫자 타입 (권장)</div>
|
<div className="mb-2 text-xs font-medium text-green-700">숫자 타입 (권장)</div>
|
||||||
{numericColumns.map((col) => {
|
{numericColumns.map((col) => {
|
||||||
const isSelected = Array.isArray(currentConfig.yAxis)
|
const isSelected = Array.isArray(currentConfig.yAxis)
|
||||||
? currentConfig.yAxis.includes(col)
|
? currentConfig.yAxis.includes(col)
|
||||||
: currentConfig.yAxis === col;
|
: currentConfig.yAxis === col;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={col} className="flex items-center gap-2 rounded border-green-500 bg-green-50 p-2">
|
||||||
key={col}
|
|
||||||
className="flex items-center gap-2 rounded border-l-2 border-green-500 bg-green-50 p-2"
|
|
||||||
>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
|
|
@ -271,7 +257,7 @@ export function ChartConfigPanel({
|
||||||
{simpleColumns.filter((col) => !numericColumns.includes(col)).length > 0 && (
|
{simpleColumns.filter((col) => !numericColumns.includes(col)).length > 0 && (
|
||||||
<>
|
<>
|
||||||
{numericColumns.length > 0 && <div className="my-2 border-t"></div>}
|
{numericColumns.length > 0 && <div className="my-2 border-t"></div>}
|
||||||
<div className="mb-2 text-xs font-medium text-gray-600">📝 기타 타입</div>
|
<div className="mb-2 text-xs font-medium text-gray-600">기타 타입</div>
|
||||||
{simpleColumns
|
{simpleColumns
|
||||||
.filter((col) => !numericColumns.includes(col))
|
.filter((col) => !numericColumns.includes(col))
|
||||||
.map((col) => {
|
.map((col) => {
|
||||||
|
|
@ -320,7 +306,7 @@ export function ChartConfigPanel({
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
{simpleColumns.length === 0 && (
|
{simpleColumns.length === 0 && (
|
||||||
<p className="text-xs text-red-500">⚠️ 사용 가능한 컬럼이 없습니다. JSON Path를 확인하세요.</p>
|
<p className="text-xs text-red-500">사용 가능한 컬럼이 없습니다. JSON Path를 확인하세요.</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
팁: 여러 항목을 선택하면 비교 차트가 생성됩니다 (예: 갤럭시 vs 아이폰)
|
팁: 여러 항목을 선택하면 비교 차트가 생성됩니다 (예: 갤럭시 vs 아이폰)
|
||||||
|
|
@ -356,7 +342,7 @@ export function ChartConfigPanel({
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
💡 그룹핑 필드와 함께 사용하면 자동으로 데이터를 집계합니다. (예: 부서별 개수, 월별 합계)
|
그룹핑 필드와 함께 사용하면 자동으로 데이터를 집계합니다. (예: 부서별 개수, 월별 합계)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -432,45 +418,6 @@ export function ChartConfigPanel({
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
{/* 설정 미리보기 */}
|
|
||||||
<Card className="bg-gray-50 p-4">
|
|
||||||
<div className="mb-3 flex items-center gap-2 text-sm font-medium text-gray-700">
|
|
||||||
<TrendingUp className="h-4 w-4" />
|
|
||||||
설정 미리보기
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2 text-xs text-gray-600">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<span className="font-medium">X축:</span>
|
|
||||||
<span>{currentConfig.xAxis || "미설정"}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<span className="font-medium">Y축:</span>
|
|
||||||
<span>
|
|
||||||
{Array.isArray(currentConfig.yAxis) && currentConfig.yAxis.length > 0
|
|
||||||
? `${currentConfig.yAxis.length}개 (${currentConfig.yAxis.join(", ")})`
|
|
||||||
: currentConfig.yAxis || "미설정"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<span className="font-medium">집계:</span>
|
|
||||||
<span>{currentConfig.aggregation || "없음"}</span>
|
|
||||||
</div>
|
|
||||||
{currentConfig.groupBy && (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<span className="font-medium">그룹핑:</span>
|
|
||||||
<span>{currentConfig.groupBy}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<span className="font-medium">데이터 행 수:</span>
|
|
||||||
<Badge variant="secondary">{queryResult.rows.length}개</Badge>
|
|
||||||
</div>
|
|
||||||
{Array.isArray(currentConfig.yAxis) && currentConfig.yAxis.length > 1 && (
|
|
||||||
<div className="mt-2 text-blue-600">✨ 다중 시리즈 차트가 생성됩니다!</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 날짜 필터 */}
|
{/* 날짜 필터 */}
|
||||||
{dateColumns.length > 0 && (
|
{dateColumns.length > 0 && (
|
||||||
<DateFilterPanel config={currentConfig} dateColumns={dateColumns} onChange={updateConfig} />
|
<DateFilterPanel config={currentConfig} dateColumns={dateColumns} onChange={updateConfig} />
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { DatabaseConfig } from "./data-sources/DatabaseConfig";
|
||||||
import { ApiConfig } from "./data-sources/ApiConfig";
|
import { ApiConfig } from "./data-sources/ApiConfig";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Progress } from "@/components/ui/progress";
|
|
||||||
import { X, ChevronLeft, ChevronRight, Save } from "lucide-react";
|
import { X, ChevronLeft, ChevronRight, Save } from "lucide-react";
|
||||||
|
|
||||||
interface ElementConfigModalProps {
|
interface ElementConfigModalProps {
|
||||||
|
|
@ -110,10 +109,6 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
|
|
||||||
// 저장 처리
|
// 저장 처리
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
console.log("💾 저장 버튼 클릭!");
|
|
||||||
console.log(" 현재 chartConfig:", chartConfig);
|
|
||||||
console.log(" dateFilter:", chartConfig?.dateFilter);
|
|
||||||
|
|
||||||
const updatedElement: DashboardElement = {
|
const updatedElement: DashboardElement = {
|
||||||
...element,
|
...element,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
|
@ -141,6 +136,11 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
const isPieChart = element.subtype === "pie" || element.subtype === "donut";
|
const isPieChart = element.subtype === "pie" || element.subtype === "donut";
|
||||||
const isApiSource = dataSource.type === "api";
|
const isApiSource = dataSource.type === "api";
|
||||||
|
|
||||||
|
// Y축 검증 헬퍼
|
||||||
|
const hasYAxis =
|
||||||
|
chartConfig.yAxis &&
|
||||||
|
(typeof chartConfig.yAxis === "string" || (Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0));
|
||||||
|
|
||||||
const canSave = isSimpleWidget
|
const canSave = isSimpleWidget
|
||||||
? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능
|
? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능
|
||||||
currentStep === 2 && queryResult && queryResult.rows.length > 0
|
currentStep === 2 && queryResult && queryResult.rows.length > 0
|
||||||
|
|
@ -157,12 +157,12 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
queryResult.rows.length > 0 &&
|
queryResult.rows.length > 0 &&
|
||||||
chartConfig.xAxis &&
|
chartConfig.xAxis &&
|
||||||
(isPieChart || isApiSource
|
(isPieChart || isApiSource
|
||||||
? // 파이/도넛 차트 또는 REST API: Y축 또는 집계 함수 필요
|
? // 파이/도넛 차트 또는 REST API
|
||||||
chartConfig.yAxis ||
|
|
||||||
(Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0) ||
|
|
||||||
chartConfig.aggregation === "count"
|
chartConfig.aggregation === "count"
|
||||||
|
? true // count는 Y축 없어도 됨
|
||||||
|
: hasYAxis // 다른 집계(sum, avg, max, min) 또는 집계 없음 → Y축 필수
|
||||||
: // 일반 차트 (DB): Y축 필수
|
: // 일반 차트 (DB): Y축 필수
|
||||||
chartConfig.yAxis || (Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0));
|
hasYAxis);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||||
|
|
@ -191,13 +191,11 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
{/* 진행 상황 표시 - 간단한 위젯은 표시 안 함 */}
|
{/* 진행 상황 표시 - 간단한 위젯은 표시 안 함 */}
|
||||||
{!isSimpleWidget && (
|
{!isSimpleWidget && (
|
||||||
<div className="border-b bg-gray-50 px-6 py-4">
|
<div className="border-b bg-gray-50 px-6 py-4">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-sm font-medium text-gray-700">
|
<div className="text-sm font-medium text-gray-700">
|
||||||
단계 {currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정 및 차트 설정"}
|
단계 {currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정 및 차트 설정"}
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="secondary">{Math.round((currentStep / 2) * 100)}% 완료</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
<Progress value={(currentStep / 2) * 100} className="h-2" />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -268,13 +266,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||||
|
|
||||||
{/* 모달 푸터 */}
|
{/* 모달 푸터 */}
|
||||||
<div className="flex items-center justify-between border-t bg-gray-50 p-6">
|
<div className="flex items-center justify-between border-t bg-gray-50 p-6">
|
||||||
<div>
|
<div>{queryResult && <Badge variant="default">{queryResult.rows.length}개 데이터 로드됨</Badge>}</div>
|
||||||
{queryResult && (
|
|
||||||
<Badge variant="default" className="bg-green-600">
|
|
||||||
📊 {queryResult.rows.length}개 데이터 로드됨
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
{!isSimpleWidget && currentStep > 1 && (
|
{!isSimpleWidget && currentStep > 1 && (
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,7 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const headers = normalizeHeaders();
|
const headers = normalizeHeaders();
|
||||||
onChange({
|
onChange({
|
||||||
headers: [...headers, { id: `header_${Date.now()}`, key: "Authorization", value: "Bearer YOUR_TOKEN" }],
|
headers: [...headers, { id: `header_${Date.now()}`, key: "Authorization", value: "" }],
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -398,7 +398,7 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
||||||
{/* 테스트 결과 */}
|
{/* 테스트 결과 */}
|
||||||
{testResult && (
|
{testResult && (
|
||||||
<Card className="border-green-200 bg-green-50 p-4">
|
<Card className="border-green-200 bg-green-50 p-4">
|
||||||
<div className="mb-2 text-sm font-medium text-green-800">✅ API 호출 성공</div>
|
<div className="mb-2 text-sm font-medium text-green-800">API 호출 성공</div>
|
||||||
<div className="space-y-1 text-xs text-green-700">
|
<div className="space-y-1 text-xs text-green-700">
|
||||||
<div>총 {testResult.rows.length}개의 데이터를 불러왔습니다</div>
|
<div>총 {testResult.rows.length}개의 데이터를 불러왔습니다</div>
|
||||||
<div>컬럼: {testResult.columns.join(", ")}</div>
|
<div>컬럼: {testResult.columns.join(", ")}</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue