'use client'; import { ReactNode, ComponentType, memo, useState } from 'react'; import {} 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 { ChevronDown } from 'lucide-react'; import type { 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 그리드 컬럼 수 (기본: 1) showSeparator?: boolean; // 구분선 표시 여부 (기본: infoGrid 사용시 true) // === 추가 콘텐츠 === topContent?: ReactNode; bottomContent?: ReactNode; // === Collapsible === collapsible?: boolean; // 기본값: true (접기/펼치기 자동 적용) defaultExpanded?: boolean; // 기본값: false (접힌 상태로 시작) } export function MobileCard({ // 공통 title, id: _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 = 1, showSeparator, // 추가 콘텐츠 topContent, bottomContent, // Collapsible collapsible: collapsibleProp = true, defaultExpanded = false, }: MobileCardProps) { // === 별칭 통합 === const handleClick = onClick || onCardClick; const handleToggle = onToggle || onToggleSelection; const itemDetails = details || fields || []; const shouldShowCheckbox = showCheckbox ?? !!handleToggle; const shouldShowSeparator = showSeparator ?? (!!infoGrid || !!headerBadges); // === Collapsible 로직 === const hasDetailContent = itemDetails.length > 0 || !!infoGrid || !!actions || !!bottomContent || !!description; const isCollapsible = collapsibleProp && hasDetailContent; const [expanded, setExpanded] = useState(defaultExpanded); // === 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(); }; // === 헤더 클릭 핸들러 === const handleHeaderClick = () => { if (isCollapsible) { setExpanded((prev) => !prev); } else { handleClick?.(); } }; // === 세부 영역 클릭 핸들러 === const handleDetailClick = () => { handleClick?.(); }; return (
{/* 상단 추가 콘텐츠 */} {topContent &&
{topContent}
} {/* 헤더 영역 - 항상 보임 */}
{/* 1행: 체크박스 + 뱃지들 + 상태뱃지 (flex-wrap으로 자연 줄바꿈) */}
{shouldShowCheckbox && handleToggle && ( e.stopPropagation()} className="h-5 w-5 shrink-0" /> )} {icon &&
{icon}
} {headerBadges} {renderBadge()}
{/* 3행: 제목 + 쉐브론 */}

{title}

{isCollapsible && ( )}
{/* 4행: 부제목 */} {subtitle && (

{subtitle}

)}
{/* 세부 영역 - collapsible 시 접기/펼치기 */} {isCollapsible ? (
{/* 설명 */} {description && (

{description}

)} {/* 구분선 */} {shouldShowSeparator && (infoGrid || itemDetails.length > 0) && ( )} {/* 정보 영역 */} {renderInfoArea()} {/* 액션 버튼 */} {renderActions()} {/* 하단 추가 콘텐츠 */} {bottomContent}
) : ( /* 비-collapsible: 기존과 동일 */ hasDetailContent && (
{/* 설명 */} {description && (

{description}

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