'use client'; import { ReactNode, ComponentType, memo } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Separator } from '@/components/ui/separator'; import { cn } from '@/lib/utils'; import { LucideIcon } from 'lucide-react'; type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline'; /** * 정보 필드 컴포넌트 * 카드 내부의 레이블-값 쌍을 표시합니다. */ export interface InfoFieldProps { label: string; value: string | number | ReactNode; valueClassName?: string; className?: string; } export const InfoField = memo(function InfoField({ label, value, valueClassName = '', className = '', }: InfoFieldProps) { return (

{label}

{value}
); }); /** * 통합 MobileCard Props * * molecules/MobileCard + organisms/ListMobileCard 기능 통합 * - 두 가지 사용 방식 모두 지원 * - 하위 호환성 유지 */ export interface MobileCardProps { // === 공통 (필수) === title: string | ReactNode; // === 공통 (선택) === id?: string; subtitle?: string; description?: string; icon?: ReactNode; className?: string; // === 클릭 핸들러 (별칭 지원) === onClick?: () => void; onCardClick?: () => void; // onClick 별칭 // === Badge - 두 가지 형식 지원 === // 방식 1: 단순 (molecules 스타일) badge?: string | { label: string; variant?: BadgeVariant }; badgeVariant?: BadgeVariant; badgeClassName?: string; // 방식 2: ReactNode (ListMobileCard 스타일) statusBadge?: ReactNode; headerBadges?: ReactNode; // === Checkbox Selection (별칭 지원) === isSelected?: boolean; onToggle?: () => void; // molecules 스타일 onToggleSelection?: () => void; // ListMobileCard 스타일 showCheckbox?: boolean; // 기본값: onToggle/onToggleSelection 있으면 true // === 정보 표시 - 두 가지 방식 지원 === // 방식 1: 배열 (molecules 스타일) - 자동 렌더링 details?: Array<{ label: string; value: string | ReactNode; badge?: boolean; badgeVariant?: BadgeVariant; colSpan?: number; }>; fields?: Array<{ // details 별칭 label: string; value: string; badge?: boolean; badgeVariant?: string; colSpan?: number; }>; // 방식 2: ReactNode (ListMobileCard 스타일) - 완전 커스텀 infoGrid?: ReactNode; // === Actions - 두 가지 방식 지원 === // 방식 1: ReactNode (권장) // 방식 2: 배열 (자동 버튼 생성) actions?: | ReactNode | Array<{ label: string; onClick: () => void; icon?: LucideIcon | ComponentType<{ className?: string }>; variant?: 'default' | 'outline' | 'destructive'; }>; // === Layout === detailsColumns?: 1 | 2 | 3; // details 그리드 컬럼 수 (기본: 2) showSeparator?: boolean; // 구분선 표시 여부 (기본: infoGrid 사용시 true) // === 추가 콘텐츠 === topContent?: ReactNode; bottomContent?: ReactNode; } export function MobileCard({ // 공통 title, id, subtitle, description, icon, className, // 클릭 onClick, onCardClick, // Badge badge, badgeVariant = 'default', badgeClassName, statusBadge, headerBadges, // Selection isSelected = false, onToggle, onToggleSelection, showCheckbox, // 정보 표시 details, fields, infoGrid, // Actions actions, // Layout detailsColumns = 2, showSeparator, // 추가 콘텐츠 topContent, bottomContent, }: MobileCardProps) { // === 별칭 통합 === const handleClick = onClick || onCardClick; const handleToggle = onToggle || onToggleSelection; const itemDetails = details || fields || []; const shouldShowCheckbox = showCheckbox ?? !!handleToggle; const shouldShowSeparator = showSeparator ?? (!!infoGrid || !!headerBadges); // === Badge 렌더링 === const renderBadge = () => { // statusBadge 우선 (ReactNode) if (statusBadge) return statusBadge; if (!badge) return null; if (typeof badge === 'string') { return ( {badge} ); } return ( {badge.label} ); }; // === Details 자동 렌더링 === const renderDetails = () => { if (itemDetails.length === 0) return null; const gridColsClass = detailsColumns === 1 ? 'grid-cols-1' : detailsColumns === 3 ? 'grid-cols-3' : 'grid-cols-2'; return (
{itemDetails.map((detail, index) => (
{detail.label}: {detail.badge ? ( {detail.value} ) : ( {detail.value} )}
))}
); }; // === Actions 렌더링 === const renderActions = () => { if (!actions) return null; // ReactNode인 경우 그대로 렌더링 if (!Array.isArray(actions)) { return (
e.stopPropagation()}> {shouldShowSeparator && } {actions}
); } // Array인 경우 버튼 자동 생성 if (actions.length === 0) return null; return (
e.stopPropagation()}> {shouldShowSeparator && }
{actions.map((action, index) => { const Icon = action.icon; return ( ); })}
); }; // === 정보 영역 렌더링 (infoGrid 우선) === const renderInfoArea = () => { if (infoGrid) return infoGrid; return renderDetails(); }; return (
{/* 상단 추가 콘텐츠 */} {topContent} {/* 헤더 영역 */}
{/* Checkbox */} {shouldShowCheckbox && handleToggle && ( e.stopPropagation()} className="mt-0.5 h-5 w-5" /> )} {/* Icon */} {icon &&
{icon}
}
{/* 헤더 뱃지들 */} {headerBadges && (
{headerBadges}
)} {/* 제목 */}

{title}

{/* 부제목 */} {subtitle && (

{subtitle}

)}
{/* 우측 상단: 뱃지 */}
{renderBadge()}
{/* 설명 */} {description && (

{description}

)} {/* 구분선 (infoGrid/headerBadges 사용시) */} {shouldShowSeparator && (infoGrid || itemDetails.length > 0) && ( )} {/* 정보 영역 */} {renderInfoArea()} {/* 액션 버튼 */} {renderActions()} {/* 하단 추가 콘텐츠 */} {bottomContent}
); } // 하위 호환성을 위한 별칭 export export { MobileCard as ListMobileCard };