From 31fcceef20968f13ab60cb829c6505c1bbd3a362 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Wed, 15 Oct 2025 11:29:53 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EB=B7=B0=20=EB=AA=A8=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/admin/dashboard/types.ts | 6 +- .../admin/dashboard/widgets/ListWidget.tsx | 120 ++++++++++++------ .../widgets/ListWidgetConfigModal.tsx | 3 + .../widgets/list-widget/ListTableOptions.tsx | 83 +++++++++--- 4 files changed, 150 insertions(+), 62 deletions(-) diff --git a/frontend/components/admin/dashboard/types.ts b/frontend/components/admin/dashboard/types.ts index 5b8b2f5d..0eece704 100644 --- a/frontend/components/admin/dashboard/types.ts +++ b/frontend/components/admin/dashboard/types.ts @@ -213,12 +213,14 @@ export interface ChartDataset { // 리스트 위젯 설정 export interface ListWidgetConfig { columnMode: "auto" | "manual"; // 컬럼 설정 방식 (자동 or 수동) + viewMode: "table" | "card"; // 뷰 모드 (테이블 or 카드) (기본: table) columns: ListColumn[]; // 컬럼 정의 pageSize: number; // 페이지당 행 수 (기본: 10) enablePagination: boolean; // 페이지네이션 활성화 (기본: true) - showHeader: boolean; // 헤더 표시 (기본: true) - stripedRows: boolean; // 줄무늬 행 (기본: true) + showHeader: boolean; // 헤더 표시 (기본: true, 테이블 모드에만 적용) + stripedRows: boolean; // 줄무늬 행 (기본: true, 테이블 모드에만 적용) compactMode: boolean; // 압축 모드 (기본: false) + cardColumns: number; // 카드 뷰 컬럼 수 (기본: 3) } // 리스트 컬럼 diff --git a/frontend/components/admin/dashboard/widgets/ListWidget.tsx b/frontend/components/admin/dashboard/widgets/ListWidget.tsx index e7b2781d..390767f4 100644 --- a/frontend/components/admin/dashboard/widgets/ListWidget.tsx +++ b/frontend/components/admin/dashboard/widgets/ListWidget.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect } from "react"; import { DashboardElement, QueryResult, ListWidgetConfig } from "../types"; import { Button } from "@/components/ui/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Card } from "@/components/ui/card"; interface ListWidgetProps { element: DashboardElement; @@ -24,12 +25,14 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) { const config = element.listConfig || { columnMode: "auto", + viewMode: "table", columns: [], pageSize: 10, enablePagination: true, showHeader: true, stripedRows: true, compactMode: false, + cardColumns: 3, }; // 데이터 로드 @@ -209,55 +212,92 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {

{element.title}

- {/* 테이블 */} -
- - {config.showHeader && ( - - - {config.columns - .filter((col) => col.visible) - .map((col) => ( - - {col.label} - - ))} - - - )} - - {paginatedRows.length === 0 ? ( - - col.visible).length} - className="text-center text-gray-500" - > - 데이터가 없습니다 - - - ) : ( - paginatedRows.map((row, idx) => ( - + {/* 테이블 뷰 */} + {config.viewMode === "table" && ( +
+
+ {config.showHeader && ( + + {config.columns .filter((col) => col.visible) .map((col) => ( - - {String(row[col.field] ?? "")} - + {col.label} + ))} - )) + )} - -
-
+ + {paginatedRows.length === 0 ? ( + + col.visible).length} + className="text-center text-gray-500" + > + 데이터가 없습니다 + + + ) : ( + paginatedRows.map((row, idx) => ( + + {config.columns + .filter((col) => col.visible) + .map((col) => ( + + {String(row[col.field] ?? "")} + + ))} + + )) + )} + + + + )} + + {/* 카드 뷰 */} + {config.viewMode === "card" && ( +
+ {paginatedRows.length === 0 ? ( +
데이터가 없습니다
+ ) : ( +
+ {paginatedRows.map((row, idx) => ( + +
+ {config.columns + .filter((col) => col.visible) + .map((col) => ( +
+
{col.label}
+
+ {String(row[col.field] ?? "")} +
+
+ ))} +
+
+ ))} +
+ )} +
+ )} {/* 페이지네이션 */} {config.enablePagination && totalPages > 1 && ( diff --git a/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx b/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx index d5b4e3d0..eb2a0e8a 100644 --- a/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx +++ b/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx @@ -36,12 +36,14 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List const [listConfig, setListConfig] = useState( element.listConfig || { columnMode: "auto", + viewMode: "table", columns: [], pageSize: 10, enablePagination: true, showHeader: true, stripedRows: true, compactMode: false, + cardColumns: 3, }, ); @@ -64,6 +66,7 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List // 현재 스텝은 1로 초기화 setCurrentStep(1); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen, element.id]); // element.id가 변경될 때만 재실행 // 데이터 소스 타입 변경 diff --git a/frontend/components/admin/dashboard/widgets/list-widget/ListTableOptions.tsx b/frontend/components/admin/dashboard/widgets/list-widget/ListTableOptions.tsx index 134f74ee..870d1abf 100644 --- a/frontend/components/admin/dashboard/widgets/list-widget/ListTableOptions.tsx +++ b/frontend/components/admin/dashboard/widgets/list-widget/ListTableOptions.tsx @@ -7,6 +7,7 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { Input } from "@/components/ui/input"; interface ListTableOptionsProps { config: ListWidgetConfig; @@ -26,6 +27,28 @@ export function ListTableOptions({ config, onChange }: ListTableOptionsProps) {
+ {/* 뷰 모드 */} +
+ + onChange({ viewMode: value })} + > +
+ + +
+
+ + +
+
+
+ {/* 컬럼 모드 */}
@@ -48,6 +71,22 @@ export function ListTableOptions({ config, onChange }: ListTableOptionsProps) {
+ {/* 카드 뷰 컬럼 수 */} + {config.viewMode === "card" && ( +
+ + onChange({ cardColumns: parseInt(e.target.value) || 3 })} + className="w-full" + /> +

한 줄에 표시할 카드 개수 (1-6)

+
+ )} + {/* 페이지 크기 */}
@@ -86,26 +125,30 @@ export function ListTableOptions({ config, onChange }: ListTableOptionsProps) {
-
- onChange({ showHeader: checked as boolean })} - /> - -
-
- onChange({ stripedRows: checked as boolean })} - /> - -
+ {config.viewMode === "table" && ( + <> +
+ onChange({ showHeader: checked as boolean })} + /> + +
+
+ onChange({ stripedRows: checked as boolean })} + /> + +
+ + )}