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 })} + /> + +
+ + )}