사소한 수정

This commit is contained in:
dohyeons 2025-10-15 15:45:58 +09:00
parent 95c685051d
commit 3613f9eef4
3 changed files with 23 additions and 84 deletions

View File

@ -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} />

View File

@ -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 && (

View File

@ -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>