대시보드 기타 수정사항 적용
This commit is contained in:
parent
d4579e4221
commit
270c322daf
|
|
@ -105,7 +105,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-gray-50">
|
||||
<div className="h-screen">
|
||||
{/* 대시보드 헤더 - 보기 모드에서는 숨김 */}
|
||||
{/* <div className="border-b border-gray-200 bg-white px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
|
|||
|
|
@ -246,7 +246,8 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende
|
|||
const isCircularChart = element.subtype === "pie" || element.subtype === "donut";
|
||||
const minWidth = isCircularChart ? 400 : 200;
|
||||
const finalWidth = Math.max(actualWidth - 20, minWidth);
|
||||
const finalHeight = Math.max(height - 20, 300);
|
||||
// 원형 차트는 범례 공간을 위해 더 많은 여백 필요
|
||||
const finalHeight = Math.max(height - (isCircularChart ? 60 : 20), 300);
|
||||
|
||||
console.log("🎨 ChartRenderer:", {
|
||||
elementSubtype: element.subtype,
|
||||
|
|
|
|||
|
|
@ -24,12 +24,17 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa
|
|||
const svg = d3.select(svgRef.current);
|
||||
svg.selectAll("*").remove();
|
||||
|
||||
const margin = { top: 40, right: 150, bottom: 40, left: 120 };
|
||||
// 범례를 위한 여백 확보 (아래 80px)
|
||||
const legendHeight = config.showLegend !== false ? 80 : 0;
|
||||
const margin = { top: 20, right: 20, bottom: 20 + legendHeight, left: 20 };
|
||||
const chartWidth = width - margin.left - margin.right;
|
||||
const chartHeight = height - margin.top - margin.bottom;
|
||||
const chartHeight = height - margin.top - margin.bottom - legendHeight;
|
||||
const radius = Math.min(chartWidth, chartHeight) / 2;
|
||||
|
||||
const g = svg.append("g").attr("transform", `translate(${width / 2},${height / 2})`);
|
||||
// 차트를 위쪽에 배치 (범례 공간 확보)
|
||||
const centerX = width / 2;
|
||||
const centerY = margin.top + radius + 20;
|
||||
const g = svg.append("g").attr("transform", `translate(${centerX},${centerY})`);
|
||||
|
||||
// 색상 팔레트
|
||||
const colors = config.colors || ["#3B82F6", "#EF4444", "#10B981", "#F59E0B", "#8B5CF6", "#EC4899"];
|
||||
|
|
@ -138,10 +143,10 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa
|
|||
|
||||
// 범례 (차트 아래, 가로 배치, 중앙 정렬)
|
||||
if (config.showLegend !== false) {
|
||||
const itemSpacing = 140; // 각 범례 항목 사이 간격
|
||||
const itemSpacing = 100; // 각 범례 항목 사이 간격 (줄임)
|
||||
const totalWidth = pieData.length * itemSpacing;
|
||||
const legendStartX = (width - totalWidth) / 2; // 시작 위치
|
||||
const legendY = height - 40; // 차트 아래 (여백 확보)
|
||||
const legendY = centerY + radius + 40; // 차트 아래 40px
|
||||
|
||||
const legend = svg.append("g").attr("class", "legend");
|
||||
|
||||
|
|
@ -152,19 +157,19 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa
|
|||
|
||||
legendItem
|
||||
.append("rect")
|
||||
.attr("x", -7.5) // 사각형을 중앙 기준으로
|
||||
.attr("y", -7.5)
|
||||
.attr("width", 15)
|
||||
.attr("height", 15)
|
||||
.attr("x", -6) // 사각형을 중앙 기준으로
|
||||
.attr("y", -6)
|
||||
.attr("width", 12)
|
||||
.attr("height", 12)
|
||||
.attr("fill", colors[i % colors.length])
|
||||
.attr("rx", 3);
|
||||
.attr("rx", 2);
|
||||
|
||||
legendItem
|
||||
.append("text")
|
||||
.attr("x", 0)
|
||||
.attr("y", 20)
|
||||
.attr("y", 18)
|
||||
.attr("text-anchor", "middle") // 텍스트 중앙 정렬
|
||||
.style("font-size", "11px")
|
||||
.style("font-size", "10px")
|
||||
.style("fill", "#333")
|
||||
.text(`${d.label} (${d.value})`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -111,19 +111,21 @@ export function ClockWidget({ element, onConfigUpdate }: ClockWidgetProps) {
|
|||
{/* 시계 콘텐츠 */}
|
||||
{renderClockContent()}
|
||||
|
||||
{/* 설정 버튼 - 우측 상단 */}
|
||||
<div className="absolute top-2 right-2">
|
||||
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 bg-white/80 hover:bg-white">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[500px] p-0" align="end">
|
||||
<ClockSettings config={config} onSave={handleSaveSettings} onClose={() => setSettingsOpen(false)} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
{/* 설정 버튼 - 우측 상단 (디자이너 모드에서만 표시) */}
|
||||
{onConfigUpdate && (
|
||||
<div className="absolute top-2 right-2">
|
||||
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 bg-white/80 hover:bg-white">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[500px] p-0" align="end">
|
||||
<ClockSettings config={config} onSave={handleSaveSettings} onClose={() => setSettingsOpen(false)} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ export function ListWidget({ element }: ListWidgetProps) {
|
|||
const paginatedRows = config.enablePagination ? data.rows.slice(startIdx, endIdx) : data.rows;
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col p-4">
|
||||
<div className="flex h-full w-full flex-col gap-3 p-4">
|
||||
{/* 테이블 뷰 */}
|
||||
{config.viewMode === "table" && (
|
||||
<div className={`flex-1 overflow-auto rounded-lg border ${config.compactMode ? "text-xs" : "text-sm"}`}>
|
||||
|
|
@ -306,7 +306,7 @@ export function ListWidget({ element }: ListWidgetProps) {
|
|||
|
||||
{/* 페이지네이션 */}
|
||||
{config.enablePagination && totalPages > 1 && (
|
||||
<div className="mt-4 flex items-center justify-between text-sm">
|
||||
<div className="flex shrink-0 items-center justify-between border-t pt-3 text-sm">
|
||||
<div className="text-gray-600">
|
||||
{startIdx + 1}-{Math.min(endIdx, data.rows.length)} / {data.rows.length}개
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -374,45 +374,46 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden bg-white p-4">
|
||||
{/* 스크롤 가능한 콘텐츠 영역 */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="grid w-full gap-4" style={{ gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))" }}>
|
||||
{/* 그룹별 카드 (활성화 시) */}
|
||||
{isGroupByMode &&
|
||||
groupedCards.map((card, index) => {
|
||||
// 색상 순환 (6가지 색상)
|
||||
const colorKeys = Object.keys(colorMap) as Array<keyof typeof colorMap>;
|
||||
const colorKey = colorKeys[index % colorKeys.length];
|
||||
const colors = colorMap[colorKey];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`group-${index}`}
|
||||
className={`rounded-lg border ${colors.bg} ${colors.border} p-4 text-center`}
|
||||
>
|
||||
<div className="text-sm text-gray-600">{card.label}</div>
|
||||
<div className={`mt-2 text-3xl font-bold ${colors.text}`}>{card.value.toLocaleString()}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 일반 지표 카드 (항상 표시) */}
|
||||
{metrics.map((metric) => {
|
||||
const colors = colorMap[metric.color as keyof typeof colorMap] || colorMap.gray;
|
||||
const formattedValue = metric.calculatedValue.toFixed(metric.decimals);
|
||||
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-white p-4">
|
||||
{/* 콘텐츠 영역 - 스크롤 없이 자동으로 크기 조정 */}
|
||||
<div className="grid h-full w-full gap-3" style={{ gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))" }}>
|
||||
{/* 그룹별 카드 (활성화 시) */}
|
||||
{isGroupByMode &&
|
||||
groupedCards.map((card, index) => {
|
||||
// 색상 순환 (6가지 색상)
|
||||
const colorKeys = Object.keys(colorMap) as Array<keyof typeof colorMap>;
|
||||
const colorKey = colorKeys[index % colorKeys.length];
|
||||
const colors = colorMap[colorKey];
|
||||
|
||||
return (
|
||||
<div key={metric.id} className={`rounded-lg border ${colors.bg} ${colors.border} p-4 text-center`}>
|
||||
<div className="text-sm text-gray-600">{metric.label}</div>
|
||||
<div className={`mt-2 text-3xl font-bold ${colors.text}`}>
|
||||
{formattedValue}
|
||||
<span className="ml-1 text-lg">{metric.unit}</span>
|
||||
</div>
|
||||
<div
|
||||
key={`group-${index}`}
|
||||
className={`flex flex-col items-center justify-center rounded-lg border ${colors.bg} ${colors.border} p-3`}
|
||||
>
|
||||
<div className="text-xs text-gray-600">{card.label}</div>
|
||||
<div className={`mt-1 text-2xl font-bold ${colors.text}`}>{card.value.toLocaleString()}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 일반 지표 카드 (항상 표시) */}
|
||||
{metrics.map((metric) => {
|
||||
const colors = colorMap[metric.color as keyof typeof colorMap] || colorMap.gray;
|
||||
const formattedValue = metric.calculatedValue.toFixed(metric.decimals);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={metric.id}
|
||||
className={`flex flex-col items-center justify-center rounded-lg border ${colors.bg} ${colors.border} p-3`}
|
||||
>
|
||||
<div className="text-xs text-gray-600">{metric.label}</div>
|
||||
<div className={`mt-1 text-2xl font-bold ${colors.text}`}>
|
||||
{formattedValue}
|
||||
<span className="ml-1 text-base">{metric.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue