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