Merge pull request '차트 간 공유 기능 구현' (#141) from feat/dashboard into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/141
This commit is contained in:
commit
4ca4ea3b3c
|
|
@ -2,9 +2,26 @@
|
|||
|
||||
import React, { useState, useCallback, useRef, useEffect } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { DashboardElement, QueryResult, Position } from "./types";
|
||||
import {
|
||||
DashboardElement,
|
||||
QueryResult,
|
||||
Position,
|
||||
ElementSubtype,
|
||||
AXIS_BASED_CHARTS,
|
||||
CIRCULAR_CHARTS,
|
||||
getChartCategory,
|
||||
} from "./types";
|
||||
import { ChartRenderer } from "./charts/ChartRenderer";
|
||||
import { GRID_CONFIG, magneticSnap, snapSizeToGrid } from "./gridUtils";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
// 위젯 동적 임포트
|
||||
const WeatherWidget = dynamic(() => import("@/components/dashboard/widgets/WeatherWidget"), {
|
||||
|
|
@ -710,14 +727,54 @@ export function CanvasElement({
|
|||
top: displayPosition.y,
|
||||
width: displaySize.width,
|
||||
height: displaySize.height,
|
||||
padding: `${GRID_CONFIG.ELEMENT_PADDING}px`,
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
{/* 헤더 */}
|
||||
<div className="flex cursor-move items-center justify-between p-3">
|
||||
<span className="text-sm font-bold text-gray-800">{element.customTitle || element.title}</span>
|
||||
<div className="flex cursor-move items-center justify-between px-4 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 차트 타입 전환 드롭다운 (차트일 경우만) */}
|
||||
{element.type === "chart" && (
|
||||
<Select
|
||||
value={element.subtype}
|
||||
onValueChange={(newSubtype: string) => {
|
||||
onUpdate(element.id, { subtype: newSubtype as ElementSubtype });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="h-7 w-[140px] text-xs"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="z-[99999]" onClick={(e) => e.stopPropagation()}>
|
||||
{getChartCategory(element.subtype) === "axis-based" ? (
|
||||
<SelectGroup>
|
||||
<SelectLabel>축 기반 차트</SelectLabel>
|
||||
<SelectItem value="bar">바 차트</SelectItem>
|
||||
<SelectItem value="horizontal-bar">수평 바 차트</SelectItem>
|
||||
<SelectItem value="stacked-bar">누적 바 차트</SelectItem>
|
||||
<SelectItem value="line">꺾은선 차트</SelectItem>
|
||||
<SelectItem value="area">영역 차트</SelectItem>
|
||||
<SelectItem value="combo">콤보 차트</SelectItem>
|
||||
</SelectGroup>
|
||||
) : (
|
||||
<SelectGroup>
|
||||
<SelectLabel>원형 차트</SelectLabel>
|
||||
<SelectItem value="pie">원형 차트</SelectItem>
|
||||
<SelectItem value="donut">도넛 차트</SelectItem>
|
||||
</SelectGroup>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
{/* 제목 */}
|
||||
{!element.type || element.type !== "chart" ? (
|
||||
<span className="text-sm font-bold text-gray-800">{element.customTitle || element.title}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{/* 삭제 버튼 */}
|
||||
<Button
|
||||
|
|
@ -725,6 +782,7 @@ export function CanvasElement({
|
|||
size="icon"
|
||||
className="element-close hover:bg-destructive h-6 w-6 text-gray-400 hover:text-white"
|
||||
onClick={handleRemove}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
title="삭제"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
|
|
@ -733,7 +791,7 @@ export function CanvasElement({
|
|||
</div>
|
||||
|
||||
{/* 내용 */}
|
||||
<div className="relative h-[calc(100%-45px)]">
|
||||
<div className="relative h-[calc(100%-50px)] px-4 pb-4">
|
||||
{element.type === "chart" ? (
|
||||
// 차트 렌더링
|
||||
<div className="h-full w-full bg-white">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import { ChartConfig, QueryResult } from "./types";
|
||||
import { ChartConfig, QueryResult, isCircularChart, isAxisBasedChart } from "./types";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -40,8 +40,8 @@ export function ChartConfigPanel({
|
|||
const [currentConfig, setCurrentConfig] = useState<ChartConfig>(config || {});
|
||||
const [dateColumns, setDateColumns] = useState<string[]>([]);
|
||||
|
||||
// 원형/도넛 차트 또는 REST API는 Y축이 필수가 아님
|
||||
const isPieChart = chartType === "pie" || chartType === "donut";
|
||||
// 원형 차트 또는 REST API는 Y축이 필수가 아님
|
||||
const isPieChart = chartType ? isCircularChart(chartType as any) : false;
|
||||
const isApiSource = dataSourceType === "api";
|
||||
|
||||
// 설정 업데이트
|
||||
|
|
|
|||
|
|
@ -159,15 +159,18 @@ export function DashboardTopMenu({
|
|||
</SelectTrigger>
|
||||
<SelectContent className="z-[99999]">
|
||||
<SelectGroup>
|
||||
<SelectLabel>차트</SelectLabel>
|
||||
<SelectLabel>축 기반 차트</SelectLabel>
|
||||
<SelectItem value="bar">바 차트</SelectItem>
|
||||
<SelectItem value="horizontal-bar">수평 바 차트</SelectItem>
|
||||
<SelectItem value="stacked-bar">누적 바 차트</SelectItem>
|
||||
<SelectItem value="line">꺾은선 차트</SelectItem>
|
||||
<SelectItem value="area">영역 차트</SelectItem>
|
||||
<SelectItem value="combo">콤보 차트</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>원형 차트</SelectLabel>
|
||||
<SelectItem value="pie">원형 차트</SelectItem>
|
||||
<SelectItem value="donut">도넛 차트</SelectItem>
|
||||
<SelectItem value="combo">콤보 차트</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,40 @@ export type ElementSubtype =
|
|||
| "transport-stats" // 커스텀 통계 카드 위젯
|
||||
| "custom-metric"; // 사용자 커스텀 카드 위젯
|
||||
|
||||
// 차트 분류
|
||||
export type ChartCategory = "axis-based" | "circular";
|
||||
|
||||
// 축 기반 차트 (X/Y축 사용)
|
||||
export const AXIS_BASED_CHARTS = ["bar", "horizontal-bar", "line", "area", "stacked-bar", "combo"] as const;
|
||||
|
||||
// 원형 차트 (그룹핑 사용)
|
||||
export const CIRCULAR_CHARTS = ["pie", "donut"] as const;
|
||||
|
||||
// 차트인지 확인
|
||||
export const isChartType = (subtype: ElementSubtype): boolean => {
|
||||
return (
|
||||
(AXIS_BASED_CHARTS as readonly string[]).includes(subtype) ||
|
||||
(CIRCULAR_CHARTS as readonly string[]).includes(subtype)
|
||||
);
|
||||
};
|
||||
|
||||
// 축 기반 차트인지 확인
|
||||
export const isAxisBasedChart = (subtype: ElementSubtype): boolean => {
|
||||
return (AXIS_BASED_CHARTS as readonly string[]).includes(subtype);
|
||||
};
|
||||
|
||||
// 원형 차트인지 확인
|
||||
export const isCircularChart = (subtype: ElementSubtype): boolean => {
|
||||
return (CIRCULAR_CHARTS as readonly string[]).includes(subtype);
|
||||
};
|
||||
|
||||
// 차트 카테고리 가져오기
|
||||
export const getChartCategory = (subtype: ElementSubtype): ChartCategory | null => {
|
||||
if (isAxisBasedChart(subtype)) return "axis-based";
|
||||
if (isCircularChart(subtype)) return "circular";
|
||||
return null;
|
||||
};
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
|
|
|
|||
Loading…
Reference in New Issue