리스트 위젯 컨텐츠가 렌더링이 안되는 문제 해결

This commit is contained in:
dohyeons 2025-10-31 12:10:46 +09:00
parent b54413978b
commit 085679a95a
4 changed files with 57 additions and 27 deletions

View File

@ -903,11 +903,6 @@ export function CanvasElement({
<div className="widget-interactive-area h-full w-full"> <div className="widget-interactive-area h-full w-full">
<ChartTestWidget element={element} /> <ChartTestWidget element={element} />
</div> </div>
) : element.type === "widget" && element.subtype === "list-v2" ? (
// 리스트 위젯 (다중 데이터 소스) - 승격 완료
<div className="widget-interactive-area h-full w-full">
<ListTestWidget element={element} />
</div>
) : element.type === "widget" && element.subtype === "custom-metric-v2" ? ( ) : element.type === "widget" && element.subtype === "custom-metric-v2" ? (
// 통계 카드 위젯 (다중 데이터 소스) - 승격 완료 // 통계 카드 위젯 (다중 데이터 소스) - 승격 완료
<div className="widget-interactive-area h-full w-full"> <div className="widget-interactive-area h-full w-full">
@ -1014,8 +1009,8 @@ export function CanvasElement({
}} }}
/> />
</div> </div>
) : element.type === "widget" && element.subtype === "list" ? ( ) : element.type === "widget" && (element.subtype === "list" || element.subtype === "list-v2") ? (
// 리스트 위젯 렌더링 (구버전) // 리스트 위젯 렌더링 (v1 & v2)
<div className="h-full w-full"> <div className="h-full w-full">
<ListWidget element={element} /> <ListWidget element={element} />
</div> </div>

View File

@ -270,7 +270,14 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
// 요소 업데이트 // 요소 업데이트
const updateElement = useCallback((id: string, updates: Partial<DashboardElement>) => { const updateElement = useCallback((id: string, updates: Partial<DashboardElement>) => {
setElements((prev) => prev.map((el) => (el.id === id ? { ...el, ...updates } : el))); setElements((prev) =>
prev.map((el) => {
if (el.id === id) {
return { ...el, ...updates };
}
return el;
}),
);
}, []); }, []);
// 요소 삭제 // 요소 삭제
@ -359,14 +366,17 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
(updatedElement: DashboardElement) => { (updatedElement: DashboardElement) => {
// 현재 요소의 최신 상태를 가져와서 position과 size는 유지 // 현재 요소의 최신 상태를 가져와서 position과 size는 유지
const currentElement = elements.find((el) => el.id === updatedElement.id); const currentElement = elements.find((el) => el.id === updatedElement.id);
if (currentElement) { if (currentElement) {
// position과 size는 현재 상태 유지, 나머지만 업데이트 // id, position, size 제거 후 나머지만 업데이트
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, position, size, ...updates } = updatedElement;
const finalElement = { const finalElement = {
...updatedElement, ...currentElement,
position: currentElement.position, ...updates,
size: currentElement.size,
}; };
updateElement(finalElement.id, finalElement);
updateElement(id, updates);
// 사이드바도 최신 상태로 업데이트 // 사이드바도 최신 상태로 업데이트
setSidebarElement(finalElement); setSidebarElement(finalElement);
} }

View File

@ -270,14 +270,28 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
const handleApply = useCallback(() => { const handleApply = useCallback(() => {
if (!element) return; if (!element) return;
// 다중 데이터 소스를 사용하는 위젯 체크
const isMultiDataSourceWidget =
element.subtype === "map-summary-v2" ||
element.subtype === "chart" ||
element.subtype === "list-v2" ||
element.subtype === "custom-metric-v2" ||
element.subtype === "risk-alert-v2";
const updatedElement: DashboardElement = { const updatedElement: DashboardElement = {
...element, ...element,
customTitle: customTitle.trim() || undefined, customTitle: customTitle.trim() || undefined,
showHeader, showHeader,
// 데이터 소스가 필요한 위젯만 dataSource 포함 // 데이터 소스 처리
...(needsDataSource(element.subtype) ...(needsDataSource(element.subtype)
? { ? {
dataSource, dataSource,
// 다중 데이터 소스 위젯은 dataSources도 포함
...(isMultiDataSourceWidget
? {
dataSources: element.dataSources || [],
}
: {}),
} }
: {}), : {}),
// 리스트 위젯 설정 // 리스트 위젯 설정
@ -291,7 +305,16 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
element.subtype === "chart" || element.subtype === "chart" ||
["bar", "horizontal-bar", "pie", "line", "area", "stacked-bar", "donut", "combo"].includes(element.subtype) ["bar", "horizontal-bar", "pie", "line", "area", "stacked-bar", "donut", "combo"].includes(element.subtype)
? { ? {
chartConfig, // 다중 데이터 소스 위젯은 chartConfig에 dataSources 포함
chartConfig: isMultiDataSourceWidget
? { ...chartConfig, dataSources: element.dataSources || [] }
: chartConfig,
// 프론트엔드 호환성을 위해 dataSources도 element에 직접 포함
...(isMultiDataSourceWidget
? {
dataSources: element.dataSources || [],
}
: {}),
} }
: {}), : {}),
// 커스텀 메트릭 설정 // 커스텀 메트릭 설정

View File

@ -39,7 +39,9 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
// 데이터 로드 // 데이터 로드
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
if (!element.dataSource || (!element.dataSource.query && !element.dataSource.endpoint)) return; if (!element.dataSource || (!element.dataSource.query && !element.dataSource.endpoint)) {
return;
}
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
@ -168,8 +170,8 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" /> <div className="border-primary mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
<div className="text-sm text-foreground"> ...</div> <div className="text-foreground text-sm"> ...</div>
</div> </div>
</div> </div>
); );
@ -181,8 +183,8 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="mb-2 text-2xl"></div> <div className="mb-2 text-2xl"></div>
<div className="text-sm font-medium text-destructive"> </div> <div className="text-destructive text-sm font-medium"> </div>
<div className="mt-1 text-xs text-muted-foreground">{error}</div> <div className="text-muted-foreground mt-1 text-xs">{error}</div>
</div> </div>
</div> </div>
); );
@ -194,8 +196,8 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<div className="flex h-full w-full flex-col items-center justify-center gap-4 p-4"> <div className="flex h-full w-full flex-col items-center justify-center gap-4 p-4">
<div className="text-center"> <div className="text-center">
<div className="mb-2 text-4xl">📋</div> <div className="mb-2 text-4xl">📋</div>
<div className="text-sm font-medium text-foreground"> </div> <div className="text-foreground text-sm font-medium"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div> <div className="text-muted-foreground mt-1 text-xs"> </div>
</div> </div>
</div> </div>
); );
@ -222,7 +224,7 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<div className="flex h-full w-full flex-col p-4"> <div className="flex h-full w-full flex-col p-4">
{/* 제목 - 항상 표시 */} {/* 제목 - 항상 표시 */}
<div className="mb-4"> <div className="mb-4">
<h3 className="text-sm font-semibold text-foreground">{element.customTitle || element.title}</h3> <h3 className="text-foreground text-sm font-semibold">{element.customTitle || element.title}</h3>
</div> </div>
{/* 테이블 뷰 */} {/* 테이블 뷰 */}
@ -251,7 +253,7 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={displayColumns.filter((col) => col.visible).length} colSpan={displayColumns.filter((col) => col.visible).length}
className="text-center text-muted-foreground" className="text-muted-foreground text-center"
> >
</TableCell> </TableCell>
@ -281,7 +283,7 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
{config.viewMode === "card" && ( {config.viewMode === "card" && (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{paginatedRows.length === 0 ? ( {paginatedRows.length === 0 ? (
<div className="flex h-full items-center justify-center text-muted-foreground"> </div> <div className="text-muted-foreground flex h-full items-center justify-center"> </div>
) : ( ) : (
<div <div
className={`grid gap-4 ${config.compactMode ? "text-xs" : "text-sm"}`} className={`grid gap-4 ${config.compactMode ? "text-xs" : "text-sm"}`}
@ -296,9 +298,9 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
.filter((col) => col.visible) .filter((col) => col.visible)
.map((col) => ( .map((col) => (
<div key={col.id}> <div key={col.id}>
<div className="text-xs font-medium text-muted-foreground">{col.label || col.name}</div> <div className="text-muted-foreground text-xs font-medium">{col.label || col.name}</div>
<div <div
className={`font-medium text-foreground ${col.align === "center" ? "text-center" : col.align === "right" ? "text-right" : ""}`} className={`text-foreground font-medium ${col.align === "center" ? "text-center" : col.align === "right" ? "text-right" : ""}`}
> >
{String(row[col.dataKey || col.field] ?? "")} {String(row[col.dataKey || col.field] ?? "")}
</div> </div>