화면관리의 테이블 리스트, 카드 디스플레이 수정
This commit is contained in:
parent
28485d6e5c
commit
d69feef9da
|
|
@ -0,0 +1,224 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Eye, Edit, Trash2, MoreHorizontal } from "lucide-react";
|
||||
import { CardDisplayConfig, ColumnConfig } from "./types";
|
||||
|
||||
interface CardModeRendererProps {
|
||||
data: Record<string, any>[];
|
||||
cardConfig: CardDisplayConfig;
|
||||
visibleColumns: ColumnConfig[];
|
||||
onRowClick?: (row: Record<string, any>) => void;
|
||||
onRowSelect?: (row: Record<string, any>, selected: boolean) => void;
|
||||
selectedRows?: string[];
|
||||
showActions?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 카드 모드 렌더러
|
||||
* 테이블 데이터를 카드 형태로 표시
|
||||
*/
|
||||
export const CardModeRenderer: React.FC<CardModeRendererProps> = ({
|
||||
data,
|
||||
cardConfig,
|
||||
visibleColumns,
|
||||
onRowClick,
|
||||
onRowSelect,
|
||||
selectedRows = [],
|
||||
showActions = true,
|
||||
}) => {
|
||||
// 기본값 설정
|
||||
const config = {
|
||||
cardsPerRow: 3,
|
||||
cardSpacing: 16,
|
||||
showActions: true,
|
||||
cardHeight: "auto",
|
||||
...cardConfig,
|
||||
};
|
||||
|
||||
// 카드 그리드 스타일 계산
|
||||
const gridStyle: React.CSSProperties = {
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${config.cardsPerRow}, 1fr)`,
|
||||
gap: `${config.cardSpacing}px`,
|
||||
padding: `${config.cardSpacing}px`,
|
||||
};
|
||||
|
||||
// 카드 높이 스타일
|
||||
const cardStyle: React.CSSProperties = {
|
||||
height: config.cardHeight === "auto" ? "auto" : `${config.cardHeight}px`,
|
||||
cursor: onRowClick ? "pointer" : "default",
|
||||
};
|
||||
|
||||
// 컬럼 값 가져오기 함수
|
||||
const getColumnValue = (row: Record<string, any>, columnName?: string): string => {
|
||||
if (!columnName || !row) return "";
|
||||
return String(row[columnName] || "");
|
||||
};
|
||||
|
||||
// 액션 버튼 렌더링
|
||||
const renderActions = (row: Record<string, any>) => {
|
||||
if (!showActions || !config.showActions) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-end space-x-1 mt-3 pt-3 border-t border-gray-100">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 상세보기 액션
|
||||
}}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 편집 액션
|
||||
}}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 삭제 액션
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 더보기 액션
|
||||
}}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 데이터가 없는 경우
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-slate-100 to-slate-200 rounded-2xl flex items-center justify-center mb-4">
|
||||
<div className="w-8 h-8 bg-slate-300 rounded-lg"></div>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-slate-600 mb-1">표시할 데이터가 없습니다</div>
|
||||
<div className="text-xs text-slate-400">조건을 변경하거나 새로운 데이터를 추가해보세요</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={gridStyle} className="w-full">
|
||||
{data.map((row, index) => {
|
||||
const idValue = getColumnValue(row, config.idColumn);
|
||||
const titleValue = getColumnValue(row, config.titleColumn);
|
||||
const subtitleValue = getColumnValue(row, config.subtitleColumn);
|
||||
const descriptionValue = getColumnValue(row, config.descriptionColumn);
|
||||
const imageValue = getColumnValue(row, config.imageColumn);
|
||||
|
||||
const isSelected = selectedRows.includes(idValue);
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={`card-${index}-${idValue}`}
|
||||
style={cardStyle}
|
||||
className={`transition-all duration-200 hover:shadow-md ${
|
||||
isSelected ? "ring-2 ring-blue-500 bg-blue-50/30" : ""
|
||||
}`}
|
||||
onClick={() => onRowClick?.(row)}
|
||||
>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-sm font-medium truncate">
|
||||
{titleValue || "제목 없음"}
|
||||
</CardTitle>
|
||||
{subtitleValue && (
|
||||
<div className="text-xs text-gray-500 mt-1 truncate">
|
||||
{subtitleValue}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ID 뱃지 */}
|
||||
{idValue && (
|
||||
<Badge variant="secondary" className="ml-2 text-xs">
|
||||
{idValue}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-0">
|
||||
{/* 이미지 표시 */}
|
||||
{imageValue && (
|
||||
<div className="mb-3">
|
||||
<img
|
||||
src={imageValue}
|
||||
alt={titleValue}
|
||||
className="w-full h-24 object-cover rounded-md bg-gray-100"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 설명 표시 */}
|
||||
{descriptionValue && (
|
||||
<div className="text-xs text-gray-600 line-clamp-2 mb-3">
|
||||
{descriptionValue}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 추가 필드들 표시 (선택적) */}
|
||||
<div className="space-y-1">
|
||||
{visibleColumns
|
||||
.filter(col =>
|
||||
col.columnName !== config.idColumn &&
|
||||
col.columnName !== config.titleColumn &&
|
||||
col.columnName !== config.subtitleColumn &&
|
||||
col.columnName !== config.descriptionColumn &&
|
||||
col.columnName !== config.imageColumn &&
|
||||
col.columnName !== "__checkbox__" &&
|
||||
col.visible
|
||||
)
|
||||
.slice(0, 3) // 최대 3개 추가 필드만 표시
|
||||
.map((col) => {
|
||||
const value = getColumnValue(row, col.columnName);
|
||||
if (!value) return null;
|
||||
|
||||
return (
|
||||
<div key={col.columnName} className="flex justify-between items-center text-xs">
|
||||
<span className="text-gray-500 truncate">{col.displayName}:</span>
|
||||
<span className="font-medium truncate ml-2">{value}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 액션 버튼들 */}
|
||||
{renderActions(row)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in New Issue