97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
|
|
/**
|
||
|
|
* CardView - DataView의 카드 뷰
|
||
|
|
* - 카드 그리드 레이아웃
|
||
|
|
* - Phase A: 기본 카드 렌더링만 구현
|
||
|
|
*/
|
||
|
|
|
||
|
|
"use client";
|
||
|
|
|
||
|
|
import React from "react";
|
||
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
||
|
|
import { cn } from "@/lib/utils";
|
||
|
|
|
||
|
|
interface CardViewProps {
|
||
|
|
data: any[];
|
||
|
|
columns: string[];
|
||
|
|
tableName: string;
|
||
|
|
loading?: boolean;
|
||
|
|
onCardClick?: (row: any) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function CardView({ data, columns, tableName, loading = false, onCardClick }: CardViewProps) {
|
||
|
|
// 로딩 스켈레톤
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-1 gap-4 p-4 md:grid-cols-2 lg:grid-cols-3">
|
||
|
|
{Array.from({ length: 6 }).map((_, i) => (
|
||
|
|
<Skeleton key={i} className="h-32 w-full" />
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 데이터 없음
|
||
|
|
if (data.length === 0) {
|
||
|
|
return (
|
||
|
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||
|
|
<div className="mb-2 text-sm font-semibold">데이터가 없습니다</div>
|
||
|
|
<p className="text-xs text-muted-foreground">아직 등록된 항목이 없습니다</p>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-1 gap-4 p-4 md:grid-cols-2 lg:grid-cols-3">
|
||
|
|
{data.map((row, index) => {
|
||
|
|
const rowId = row.id || String(index);
|
||
|
|
const titleColumn = columns[0]; // 첫 번째 컬럼을 제목으로 사용
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card
|
||
|
|
key={rowId}
|
||
|
|
className={cn(
|
||
|
|
"cursor-pointer transition-all hover:shadow-md",
|
||
|
|
onCardClick && "hover:border-primary"
|
||
|
|
)}
|
||
|
|
onClick={() => onCardClick?.(row)}
|
||
|
|
>
|
||
|
|
<CardHeader className="p-4">
|
||
|
|
<CardTitle className="text-sm font-semibold">{row[titleColumn] || "(제목 없음)"}</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className="space-y-1 p-4 pt-0">
|
||
|
|
{columns.slice(1).map((column) => (
|
||
|
|
<div key={column} className="flex justify-between text-xs">
|
||
|
|
<span className="text-muted-foreground">{column}:</span>
|
||
|
|
<span className="font-medium">{formatValue(row[column])}</span>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 값 포맷팅
|
||
|
|
function formatValue(value: any): string {
|
||
|
|
if (value === null || value === undefined) {
|
||
|
|
return "-";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof value === "boolean") {
|
||
|
|
return value ? "O" : "X";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (value instanceof Date) {
|
||
|
|
return value.toLocaleDateString("ko-KR");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof value === "object") {
|
||
|
|
return JSON.stringify(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
return String(value);
|
||
|
|
}
|