feat(pop-dashboard): 페이지 기반 구조 전환 및 설정 패널 고도화
구조 변경: - grid 모드를 독립 displayMode에서 페이지 내부 그리드 레이아웃으로 전환 - DashboardPage 타입 추가 (각 페이지가 독립 그리드 보유) - migrateConfig()로 기존 grid/useGridLayout 설정 자동 마이그레이션 설정 패널 (PopDashboardConfig): - 드롭다운 기반 집계 설정 UI 전면 재작성 (+917줄) - 테이블/컬럼 선택 Combobox, 페이지 관리, 셀 배치 편집기 - fetchTableList() 추가 (테이블 목록 조회) 컴포넌트/모드 개선: - GridMode: 반응형 자동 열 축소 (MIN_CELL_WIDTH 기준) - PopDashboardComponent: 페이지 기반 렌더링 로직 - PopDashboardPreview: 페이지 뱃지 표시 기타: - ComponentEditorPanel: 탭 콘텐츠 스크롤 수정 (min-h-0 추가) - types.ts: grid를 displayMode에서 제거, DashboardPage 타입 추가 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
73e3d56381
commit
dc523d86c3
|
|
@ -97,8 +97,8 @@ export default function ComponentEditorPanel({
|
|||
</div>
|
||||
|
||||
{/* 탭 */}
|
||||
<Tabs defaultValue="position" className="flex flex-1 flex-col">
|
||||
<TabsList className="w-full justify-start rounded-none border-b bg-transparent px-2">
|
||||
<Tabs defaultValue="position" className="flex min-h-0 flex-1 flex-col">
|
||||
<TabsList className="w-full shrink-0 justify-start rounded-none border-b bg-transparent px-2">
|
||||
<TabsTrigger value="position" className="gap-1 text-xs">
|
||||
<Grid3x3 className="h-3 w-3" />
|
||||
위치
|
||||
|
|
@ -118,7 +118,7 @@ export default function ComponentEditorPanel({
|
|||
</TabsList>
|
||||
|
||||
{/* 위치 탭 */}
|
||||
<TabsContent value="position" className="flex-1 overflow-auto p-4">
|
||||
<TabsContent value="position" className="min-h-0 flex-1 overflow-auto p-4">
|
||||
<PositionForm
|
||||
component={component}
|
||||
currentMode={currentMode}
|
||||
|
|
@ -129,7 +129,7 @@ export default function ComponentEditorPanel({
|
|||
</TabsContent>
|
||||
|
||||
{/* 설정 탭 */}
|
||||
<TabsContent value="settings" className="flex-1 overflow-auto p-4">
|
||||
<TabsContent value="settings" className="min-h-0 flex-1 overflow-auto p-4">
|
||||
<ComponentSettingsForm
|
||||
component={component}
|
||||
onUpdate={onUpdateComponent}
|
||||
|
|
@ -137,7 +137,7 @@ export default function ComponentEditorPanel({
|
|||
</TabsContent>
|
||||
|
||||
{/* 표시 탭 */}
|
||||
<TabsContent value="visibility" className="flex-1 overflow-auto p-4">
|
||||
<TabsContent value="visibility" className="min-h-0 flex-1 overflow-auto p-4">
|
||||
<VisibilityForm
|
||||
component={component}
|
||||
onUpdate={onUpdateComponent}
|
||||
|
|
@ -145,7 +145,7 @@ export default function ComponentEditorPanel({
|
|||
</TabsContent>
|
||||
|
||||
{/* 데이터 탭 */}
|
||||
<TabsContent value="data" className="flex-1 overflow-auto p-4">
|
||||
<TabsContent value="data" className="min-h-0 flex-1 overflow-auto p-4">
|
||||
<DataBindingPlaceholder />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import React, { useState, useEffect, useCallback, useRef } from "react";
|
|||
import type {
|
||||
PopDashboardConfig,
|
||||
DashboardItem,
|
||||
DashboardPage,
|
||||
} from "../types";
|
||||
import { fetchAggregatedData } from "./utils/dataFetcher";
|
||||
import {
|
||||
|
|
@ -33,6 +34,62 @@ import { AutoSlideModeComponent } from "./modes/AutoSlideMode";
|
|||
import { GridModeComponent } from "./modes/GridMode";
|
||||
import { ScrollModeComponent } from "./modes/ScrollMode";
|
||||
|
||||
// ===== 마이그레이션: 기존 config -> 페이지 기반 구조 변환 =====
|
||||
|
||||
/**
|
||||
* 기존 config를 페이지 기반 구조로 마이그레이션.
|
||||
* 런타임에서만 사용 (저장된 config 원본은 변경하지 않음).
|
||||
*
|
||||
* 시나리오1: displayMode="grid" (가장 오래된 형태)
|
||||
* 시나리오2: useGridLayout=true (직전 마이그레이션 결과)
|
||||
* 시나리오3: pages 이미 있음 (새 형태) -> 변환 불필요
|
||||
* 시나리오4: pages 없음 + grid 아님 -> 빈 pages (아이템 하나씩 슬라이드)
|
||||
*/
|
||||
export function migrateConfig(
|
||||
raw: Record<string, unknown>
|
||||
): PopDashboardConfig {
|
||||
const config = { ...raw } as PopDashboardConfig & Record<string, unknown>;
|
||||
|
||||
// pages가 이미 있으면 마이그레이션 불필요
|
||||
if (
|
||||
Array.isArray(config.pages) &&
|
||||
config.pages.length > 0
|
||||
) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// 시나리오1: displayMode="grid" / 시나리오2: useGridLayout=true
|
||||
const wasGrid =
|
||||
config.displayMode === ("grid" as string) ||
|
||||
(config as Record<string, unknown>).useGridLayout === true;
|
||||
|
||||
if (wasGrid) {
|
||||
const cols =
|
||||
((config as Record<string, unknown>).gridColumns as number) ?? 2;
|
||||
const rows =
|
||||
((config as Record<string, unknown>).gridRows as number) ?? 2;
|
||||
const cells =
|
||||
((config as Record<string, unknown>).gridCells as DashboardPage["gridCells"]) ?? [];
|
||||
|
||||
const page: DashboardPage = {
|
||||
id: "migrated-page-1",
|
||||
label: "페이지 1",
|
||||
gridColumns: cols,
|
||||
gridRows: rows,
|
||||
gridCells: cells,
|
||||
};
|
||||
|
||||
config.pages = [page];
|
||||
|
||||
// displayMode="grid" 보정
|
||||
if (config.displayMode === ("grid" as string)) {
|
||||
(config as Record<string, unknown>).displayMode = "arrows";
|
||||
}
|
||||
}
|
||||
|
||||
return config as PopDashboardConfig;
|
||||
}
|
||||
|
||||
// ===== 내부 타입 =====
|
||||
|
||||
interface ItemData {
|
||||
|
|
@ -259,9 +316,43 @@ export function PopDashboardComponent({
|
|||
);
|
||||
}
|
||||
|
||||
// 표시 모드별 렌더링
|
||||
const displayMode = config.displayMode;
|
||||
// 마이그레이션: 기존 config를 페이지 기반으로 변환
|
||||
const migrated = migrateConfig(config as unknown as Record<string, unknown>);
|
||||
const pages = migrated.pages ?? [];
|
||||
const displayMode = migrated.displayMode;
|
||||
|
||||
// 페이지 하나를 GridModeComponent로 렌더링
|
||||
const renderPageContent = (page: DashboardPage) => (
|
||||
<GridModeComponent
|
||||
cells={page.gridCells}
|
||||
columns={page.gridColumns}
|
||||
rows={page.gridRows}
|
||||
gap={config.gap}
|
||||
containerWidth={containerWidth}
|
||||
renderItem={(itemId) => {
|
||||
const item = visibleItems.find((i) => i.id === itemId);
|
||||
if (!item) return null;
|
||||
return renderSingleItem(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// 슬라이드 수: pages가 있으면 페이지 수, 없으면 아이템 수 (기존 동작)
|
||||
const slideCount = pages.length > 0 ? pages.length : visibleItems.length;
|
||||
|
||||
// 슬라이드 렌더 콜백: pages가 있으면 페이지 렌더, 없으면 단일 아이템
|
||||
const renderSlide = (index: number) => {
|
||||
if (pages.length > 0 && pages[index]) {
|
||||
return renderPageContent(pages[index]);
|
||||
}
|
||||
// fallback: 아이템 하나씩 (기존 동작 - pages 미설정 시)
|
||||
if (visibleItems[index]) {
|
||||
return renderSingleItem(visibleItems[index]);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 표시 모드별 렌더링
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
|
|
@ -274,41 +365,27 @@ export function PopDashboardComponent({
|
|||
>
|
||||
{displayMode === "arrows" && (
|
||||
<ArrowsModeComponent
|
||||
itemCount={visibleItems.length}
|
||||
itemCount={slideCount}
|
||||
showIndicator={config.showIndicator}
|
||||
renderItem={(index) => renderSingleItem(visibleItems[index])}
|
||||
renderItem={renderSlide}
|
||||
/>
|
||||
)}
|
||||
|
||||
{displayMode === "auto-slide" && (
|
||||
<AutoSlideModeComponent
|
||||
itemCount={visibleItems.length}
|
||||
itemCount={slideCount}
|
||||
interval={config.autoSlideInterval}
|
||||
resumeDelay={config.autoSlideResumeDelay}
|
||||
showIndicator={config.showIndicator}
|
||||
renderItem={(index) => renderSingleItem(visibleItems[index])}
|
||||
/>
|
||||
)}
|
||||
|
||||
{displayMode === "grid" && (
|
||||
<GridModeComponent
|
||||
cells={config.gridCells ?? []}
|
||||
columns={config.gridColumns ?? 2}
|
||||
rows={config.gridRows ?? 2}
|
||||
gap={config.gap}
|
||||
renderItem={(itemId) => {
|
||||
const item = visibleItems.find((i) => i.id === itemId);
|
||||
if (!item) return null;
|
||||
return renderSingleItem(item);
|
||||
}}
|
||||
renderItem={renderSlide}
|
||||
/>
|
||||
)}
|
||||
|
||||
{displayMode === "scroll" && (
|
||||
<ScrollModeComponent
|
||||
itemCount={visibleItems.length}
|
||||
itemCount={slideCount}
|
||||
showIndicator={config.showIndicator}
|
||||
renderItem={(index) => renderSingleItem(visibleItems[index])}
|
||||
renderItem={renderSlide}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,6 +10,7 @@
|
|||
import React from "react";
|
||||
import { BarChart3, PieChart, Gauge, LayoutList } from "lucide-react";
|
||||
import type { PopDashboardConfig, DashboardSubType } from "../types";
|
||||
import { migrateConfig } from "./PopDashboardComponent";
|
||||
|
||||
// ===== 서브타입별 아이콘 매핑 =====
|
||||
|
||||
|
|
@ -32,7 +33,6 @@ const SUBTYPE_LABELS: Record<DashboardSubType, string> = {
|
|||
const MODE_LABELS: Record<string, string> = {
|
||||
arrows: "좌우 버튼",
|
||||
"auto-slide": "자동 슬라이드",
|
||||
grid: "그리드",
|
||||
scroll: "스크롤",
|
||||
};
|
||||
|
||||
|
|
@ -75,34 +75,43 @@ export function PopDashboardPreviewComponent({
|
|||
}
|
||||
|
||||
const visibleItems = config.items.filter((i) => i.visible);
|
||||
const mode = config.displayMode;
|
||||
|
||||
// 마이그레이션 적용
|
||||
const migrated = migrateConfig(config as unknown as Record<string, unknown>);
|
||||
const pages = migrated.pages ?? [];
|
||||
const hasPages = pages.length > 0;
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden p-1">
|
||||
{/* 모드 표시 */}
|
||||
{/* 모드 + 페이지 뱃지 */}
|
||||
<div className="mb-1 flex items-center gap-1">
|
||||
<span className="rounded bg-primary/10 px-1 py-0.5 text-[8px] font-medium text-primary">
|
||||
{MODE_LABELS[mode] ?? mode}
|
||||
{MODE_LABELS[migrated.displayMode] ?? migrated.displayMode}
|
||||
</span>
|
||||
{hasPages && (
|
||||
<span className="rounded bg-muted px-1 py-0.5 text-[8px] font-medium text-muted-foreground">
|
||||
{pages.length}페이지
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[8px] text-muted-foreground">
|
||||
{visibleItems.length}개
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 모드별 미리보기 */}
|
||||
{/* 미리보기 */}
|
||||
<div className="min-h-0 flex-1">
|
||||
{mode === "grid" ? (
|
||||
// 그리드: 셀 구조 시각화
|
||||
{hasPages ? (
|
||||
// 첫 번째 페이지 그리드 미리보기
|
||||
<div
|
||||
className="h-full w-full gap-1"
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${config.gridColumns ?? 2}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${config.gridRows ?? 2}, 1fr)`,
|
||||
gridTemplateColumns: `repeat(${pages[0].gridColumns}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${pages[0].gridRows}, 1fr)`,
|
||||
}}
|
||||
>
|
||||
{config.gridCells?.length
|
||||
? config.gridCells.map((cell) => {
|
||||
{pages[0].gridCells.length > 0
|
||||
? pages[0].gridCells.map((cell) => {
|
||||
const item = visibleItems.find(
|
||||
(i) => i.id === cell.itemId
|
||||
);
|
||||
|
|
@ -125,8 +134,7 @@ export function PopDashboardPreviewComponent({
|
|||
</div>
|
||||
);
|
||||
})
|
||||
: // 셀 미설정: 아이템만 나열
|
||||
visibleItems.slice(0, 4).map((item) => (
|
||||
: visibleItems.slice(0, 4).map((item) => (
|
||||
<DummyItemPreview
|
||||
key={item.id}
|
||||
subType={item.subType}
|
||||
|
|
@ -135,7 +143,7 @@ export function PopDashboardPreviewComponent({
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
// 다른 모드: 첫 번째 아이템만 크게 표시
|
||||
// 페이지 미설정: 첫 번째 아이템만 크게 표시
|
||||
<div className="relative h-full">
|
||||
{visibleItems[0] && (
|
||||
<DummyItemPreview
|
||||
|
|
@ -143,7 +151,6 @@ export function PopDashboardPreviewComponent({
|
|||
label={visibleItems[0].label}
|
||||
/>
|
||||
)}
|
||||
{/* 추가 아이템 수 뱃지 */}
|
||||
{visibleItems.length > 1 && (
|
||||
<div className="absolute bottom-1 right-1 rounded-full bg-primary/80 px-1.5 py-0.5 text-[8px] font-medium text-primary-foreground">
|
||||
+{visibleItems.length - 1}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ PopComponentRegistry.registerComponent({
|
|||
preview: PopDashboardPreviewComponent,
|
||||
defaultProps: {
|
||||
items: [],
|
||||
pages: [],
|
||||
displayMode: "arrows",
|
||||
autoSlideInterval: 5,
|
||||
autoSlideResumeDelay: 3,
|
||||
|
|
|
|||
|
|
@ -5,26 +5,110 @@
|
|||
*
|
||||
* CSS Grid로 셀 배치 (엑셀형 분할/병합 결과 반영)
|
||||
* 각 셀에 @container 적용하여 내부 아이템 반응형
|
||||
*
|
||||
* 반응형 자동 조정:
|
||||
* - containerWidth에 따라 열 수를 자동 축소
|
||||
* - 설정된 열 수가 최대값이고, 공간이 부족하면 줄어듦
|
||||
* - 셀당 최소 너비(MIN_CELL_WIDTH) 기준으로 판단
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import type { DashboardCell } from "../../types";
|
||||
|
||||
// ===== 상수 =====
|
||||
|
||||
/** 셀 하나의 최소 너비 (px). 이보다 좁아지면 열 수 축소 */
|
||||
const MIN_CELL_WIDTH = 160;
|
||||
|
||||
// ===== Props =====
|
||||
|
||||
export interface GridModeProps {
|
||||
/** 셀 배치 정보 */
|
||||
cells: DashboardCell[];
|
||||
/** 열 수 */
|
||||
/** 설정된 열 수 (최대값) */
|
||||
columns: number;
|
||||
/** 행 수 */
|
||||
/** 설정된 행 수 */
|
||||
rows: number;
|
||||
/** 아이템 간 간격 (px) */
|
||||
gap?: number;
|
||||
/** 컨테이너 너비 (px, 반응형 자동 조정용) */
|
||||
containerWidth?: number;
|
||||
/** 셀의 아이템 렌더링. itemId가 null이면 빈 셀 */
|
||||
renderItem: (itemId: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
// ===== 반응형 열 수 계산 =====
|
||||
|
||||
/**
|
||||
* 컨테이너 너비에 맞는 실제 열 수를 계산
|
||||
*
|
||||
* 설정된 columns가 최대값이고, 공간이 부족하면 축소.
|
||||
* gap도 고려하여 계산.
|
||||
*
|
||||
* 예: columns=3, containerWidth=400, gap=8, MIN_CELL_WIDTH=160
|
||||
* 사용 가능 너비 = 400 - (3-1)*8 = 384
|
||||
* 셀당 너비 = 384/3 = 128 < 160 -> 열 축소
|
||||
* columns=2: 사용 가능 = 400 - (2-1)*8 = 392, 셀당 = 196 >= 160 -> OK
|
||||
*/
|
||||
function computeResponsiveColumns(
|
||||
configColumns: number,
|
||||
containerWidth: number,
|
||||
gap: number
|
||||
): number {
|
||||
if (containerWidth <= 0) return configColumns;
|
||||
|
||||
for (let cols = configColumns; cols >= 1; cols--) {
|
||||
const totalGap = (cols - 1) * gap;
|
||||
const cellWidth = (containerWidth - totalGap) / cols;
|
||||
if (cellWidth >= MIN_CELL_WIDTH) return cols;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 열 수가 줄어들 때 셀 배치를 자동 재배열
|
||||
*
|
||||
* 원본 gridColumn/gridRow를 actualColumns에 맞게 재매핑
|
||||
* 셀이 원래 위치를 유지하려고 시도하되, 넘치면 아래로 이동
|
||||
*/
|
||||
function remapCells(
|
||||
cells: DashboardCell[],
|
||||
configColumns: number,
|
||||
actualColumns: number,
|
||||
configRows: number
|
||||
): { remappedCells: DashboardCell[]; actualRows: number } {
|
||||
// 열 수가 같으면 원본 그대로
|
||||
if (actualColumns >= configColumns) {
|
||||
return { remappedCells: cells, actualRows: configRows };
|
||||
}
|
||||
|
||||
// 셀을 원래 위치 순서대로 정렬 (행 우선)
|
||||
const sorted = [...cells].sort((a, b) => {
|
||||
const aRow = parseInt(a.gridRow.split(" / ")[0]) || 0;
|
||||
const bRow = parseInt(b.gridRow.split(" / ")[0]) || 0;
|
||||
if (aRow !== bRow) return aRow - bRow;
|
||||
const aCol = parseInt(a.gridColumn.split(" / ")[0]) || 0;
|
||||
const bCol = parseInt(b.gridColumn.split(" / ")[0]) || 0;
|
||||
return aCol - bCol;
|
||||
});
|
||||
|
||||
// 순서대로 새 위치에 배치
|
||||
let maxRow = 0;
|
||||
const remapped = sorted.map((cell, index) => {
|
||||
const newCol = (index % actualColumns) + 1;
|
||||
const newRow = Math.floor(index / actualColumns) + 1;
|
||||
maxRow = Math.max(maxRow, newRow);
|
||||
return {
|
||||
...cell,
|
||||
gridColumn: `${newCol} / ${newCol + 1}`,
|
||||
gridRow: `${newRow} / ${newRow + 1}`,
|
||||
};
|
||||
});
|
||||
|
||||
return { remappedCells: remapped, actualRows: maxRow };
|
||||
}
|
||||
|
||||
// ===== 메인 컴포넌트 =====
|
||||
|
||||
export function GridModeComponent({
|
||||
|
|
@ -32,9 +116,25 @@ export function GridModeComponent({
|
|||
columns,
|
||||
rows,
|
||||
gap = 8,
|
||||
containerWidth,
|
||||
renderItem,
|
||||
}: GridModeProps) {
|
||||
if (!cells.length) {
|
||||
// 반응형 열 수 계산
|
||||
const actualColumns = useMemo(
|
||||
() =>
|
||||
containerWidth
|
||||
? computeResponsiveColumns(columns, containerWidth, gap)
|
||||
: columns,
|
||||
[columns, containerWidth, gap]
|
||||
);
|
||||
|
||||
// 열 수가 줄었으면 셀 재배열
|
||||
const { remappedCells, actualRows } = useMemo(
|
||||
() => remapCells(cells, columns, actualColumns, rows),
|
||||
[cells, columns, actualColumns, rows]
|
||||
);
|
||||
|
||||
if (!remappedCells.length) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<span className="text-xs text-muted-foreground">셀 없음</span>
|
||||
|
|
@ -47,12 +147,12 @@ export function GridModeComponent({
|
|||
className="h-full w-full"
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${rows}, 1fr)`,
|
||||
gridTemplateColumns: `repeat(${actualColumns}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${actualRows}, 1fr)`,
|
||||
gap: `${gap}px`,
|
||||
}}
|
||||
>
|
||||
{cells.map((cell) => (
|
||||
{remappedCells.map((cell) => (
|
||||
<div
|
||||
key={cell.id}
|
||||
className="@container min-h-0 min-w-0 overflow-hidden rounded-md border border-border/50 bg-card"
|
||||
|
|
@ -65,7 +165,9 @@ export function GridModeComponent({
|
|||
renderItem(cell.itemId)
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<span className="text-[10px] text-muted-foreground/50">빈 셀</span>
|
||||
<span className="text-[10px] text-muted-foreground/50">
|
||||
빈 셀
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,14 @@
|
|||
|
||||
import { dashboardApi } from "@/lib/api/dashboard";
|
||||
import { dataApi } from "@/lib/api/data";
|
||||
import { tableManagementApi } from "@/lib/api/tableManagement";
|
||||
import type { TableInfo } from "@/lib/api/tableManagement";
|
||||
import type { DataSourceConfig, DataSourceFilter } from "../../types";
|
||||
|
||||
// ===== 타입 re-export =====
|
||||
|
||||
export type { TableInfo };
|
||||
|
||||
// ===== 반환 타입 =====
|
||||
|
||||
export interface AggregatedResult {
|
||||
|
|
@ -233,3 +239,21 @@ export async function fetchTableColumns(
|
|||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 목록 조회 (설정 패널 Combobox용)
|
||||
* tableManagementApi.getTableList() 래핑
|
||||
*
|
||||
* @INFRA-EXTRACT: useDataSource 완성 후 교체 예정
|
||||
*/
|
||||
export async function fetchTableList(): Promise<TableInfo[]> {
|
||||
try {
|
||||
const response = await tableManagementApi.getTableList();
|
||||
if (response.success && response.data) {
|
||||
return response.data;
|
||||
}
|
||||
return [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ export interface PopActionConfig {
|
|||
export type DashboardDisplayMode =
|
||||
| "arrows"
|
||||
| "auto-slide"
|
||||
| "grid"
|
||||
| "scroll";
|
||||
export type DashboardSubType = "kpi-card" | "chart" | "gauge" | "stat-card";
|
||||
export type FormulaDisplayFormat = "value" | "fraction" | "percent" | "ratio";
|
||||
|
|
@ -280,6 +279,17 @@ export interface DashboardCell {
|
|||
itemId: string | null; // null이면 빈 셀
|
||||
}
|
||||
|
||||
// ----- 대시보드 페이지(슬라이드) -----
|
||||
|
||||
/** 대시보드 한 페이지(슬라이드) - 독립적인 그리드 레이아웃 보유 */
|
||||
export interface DashboardPage {
|
||||
id: string;
|
||||
label?: string; // 디자이너에서 표시할 라벨 (예: "페이지 1")
|
||||
gridColumns: number; // 이 페이지의 열 수
|
||||
gridRows: number; // 이 페이지의 행 수
|
||||
gridCells: DashboardCell[]; // 이 페이지의 셀 배치 (각 셀에 itemId 지정)
|
||||
}
|
||||
|
||||
// ----- 대시보드 아이템 -----
|
||||
|
||||
export interface DashboardItem {
|
||||
|
|
@ -306,17 +316,18 @@ export interface DashboardItem {
|
|||
|
||||
export interface PopDashboardConfig {
|
||||
items: DashboardItem[];
|
||||
displayMode: DashboardDisplayMode;
|
||||
pages?: DashboardPage[]; // 페이지 배열 (각 페이지가 독립 그리드 레이아웃)
|
||||
displayMode: DashboardDisplayMode; // 페이지 간 전환 방식
|
||||
|
||||
// 모드별 설정
|
||||
autoSlideInterval?: number; // 초 (기본 5)
|
||||
autoSlideResumeDelay?: number; // 터치 후 재개 대기 초 (기본 3)
|
||||
gridCells?: DashboardCell[]; // grid 모드 셀 배치
|
||||
gridColumns?: number; // grid 모드 열 수 (기본 2)
|
||||
gridRows?: number; // grid 모드 행 수 (기본 2)
|
||||
|
||||
// 공통 스타일
|
||||
showIndicator?: boolean; // 페이지 인디케이터
|
||||
gap?: number; // 아이템 간 간격 px
|
||||
backgroundColor?: string;
|
||||
|
||||
// 데이터 소스 (아이템 공통)
|
||||
dataSource?: DataSourceConfig;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue