Files
sam-react-prod/src/components/organisms/ListMobileCard.tsx
byeongcheolryu 3be5714805 refactor: 품목관리 시스템 리팩토링 및 Sales 페이지 추가
DynamicItemForm 개선:
- 품목코드 자동생성 기능 추가
- 조건부 표시 로직 개선
- 불필요한 컴포넌트 정리 (DynamicField, DynamicSection 등)
- 타입 시스템 단순화

새로운 기능:
- Sales 페이지 마이그레이션 (견적관리, 거래처관리)
- 공통 컴포넌트 추가 (atoms, molecules, organisms, templates)

문서화:
- 구현 문서 및 참조 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 12:48:41 +09:00

171 lines
4.2 KiB
TypeScript

"use client";
import { ReactNode } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Separator } from "@/components/ui/separator";
/**
* 목록 모바일 카드 컴포넌트
*
* 모바일 환경에서 사용하는 공통 목록 카드 컴포넌트입니다.
* 체크박스, 헤더, 뱃지, 정보 그리드, 액션 버튼을 포함합니다.
*
* @example
* ```tsx
* <ListMobileCard
* id="item-1"
* isSelected={false}
* onToggleSelection={() => handleToggle("item-1")}
* onCardClick={() => handleView("item-1")}
* headerBadges={[
* <Badge key="num">#{1}</Badge>,
* <Badge key="code">Q2024-001</Badge>
* ]}
* title="ABC건설"
* statusBadge={<StatusBadge variant="success" label="완료" />}
* infoGrid={
* <div className="grid grid-cols-2 gap-x-4 gap-y-3">
* <InfoField label="현장명" value="강남 현장" />
* <InfoField label="담당자" value="김철수" />
* </div>
* }
* actions={isSelected && <ActionButtonGroup actions={actions} isMobile />}
* />
* ```
*/
export interface ListMobileCardProps {
/** 아이템 고유 ID */
id: string;
/** 선택 상태 */
isSelected: boolean;
/** 체크박스 토글 핸들러 */
onToggleSelection: () => void;
/** 카드 클릭 핸들러 */
onCardClick?: () => void;
/** 헤더 영역 뱃지들 (번호, 코드 등) */
headerBadges?: ReactNode;
/** 카드 제목 (주요 정보) */
title: string;
/** 상태 뱃지 (우측 상단) */
statusBadge?: ReactNode;
/** 정보 그리드 영역 */
infoGrid: ReactNode;
/** 액션 버튼 영역 */
actions?: ReactNode;
/** 추가 className */
className?: string;
/** 카드 상단 추가 콘텐츠 */
topContent?: ReactNode;
/** 카드 하단 추가 콘텐츠 */
bottomContent?: ReactNode;
}
/**
* 정보 필드 컴포넌트
*
* 카드 내부의 레이블-값 쌍을 표시합니다.
*/
export interface InfoFieldProps {
label: string;
value: string | number | ReactNode;
valueClassName?: string;
}
export function InfoField({ label, value, valueClassName = "" }: InfoFieldProps) {
return (
<div className="space-y-0.5">
<p className="text-xs text-muted-foreground">{label}</p>
<div className={`text-sm font-medium ${valueClassName}`}>{value}</div>
</div>
);
}
export function ListMobileCard({
id,
isSelected,
onToggleSelection,
onCardClick,
headerBadges,
title,
statusBadge,
infoGrid,
actions,
className = "",
topContent,
bottomContent
}: ListMobileCardProps) {
return (
<div
className={`border rounded-lg p-5 space-y-4 bg-white dark:bg-card transition-all cursor-pointer ${
isSelected
? 'border-blue-500 bg-blue-50/50'
: 'border-gray-200 hover:border-primary/50'
} ${className}`}
onClick={onCardClick}
>
{/* 상단 추가 콘텐츠 */}
{topContent}
{/* 헤더: 체크박스 + 뱃지 + 제목 / 우측에 상태 뱃지 */}
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 flex-1 min-w-0">
<Checkbox
checked={isSelected}
onCheckedChange={onToggleSelection}
onClick={(e) => e.stopPropagation()}
className="mt-0.5 h-5 w-5"
/>
<div className="flex-1 min-w-0">
{/* 헤더 뱃지들 (번호, 코드 등) */}
{headerBadges && (
<div className="flex items-center gap-2 flex-wrap mb-2">
{headerBadges}
</div>
)}
{/* 제목 */}
<h3 className="font-semibold text-gray-900 font-bold whitespace-nowrap">
{title}
</h3>
</div>
</div>
{/* 우측 상단: 상태 뱃지 */}
{statusBadge && (
<div className="shrink-0">
{statusBadge}
</div>
)}
</div>
{/* 구분선 */}
<Separator className="bg-gray-100" />
{/* 정보 그리드 */}
{infoGrid}
{/* 액션 버튼 - 선택된 경우만 표시 */}
{actions && (
<>
<Separator className="bg-gray-100" />
{actions}
</>
)}
{/* 하단 추가 콘텐츠 */}
{bottomContent}
</div>
);
}