ERP-node/frontend/lib/registry/components/v2-table-grouped/components/GroupHeader.tsx

142 lines
3.8 KiB
TypeScript

"use client";
import React from "react";
import { ChevronDown, ChevronRight, Minus } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { cn } from "@/lib/utils";
import { GroupState, TableGroupedConfig } from "../types";
interface GroupHeaderProps {
/** 그룹 상태 */
group: GroupState;
/** 설정 */
config: TableGroupedConfig;
/** 그룹 토글 핸들러 */
onToggle: () => void;
/** 그룹 선택 토글 핸들러 */
onSelectToggle?: () => void;
/** 그룹 헤더 스타일 */
style?: "default" | "compact" | "card";
/** 컬럼 개수 (colspan용) */
columnCount?: number;
}
/**
* 그룹 헤더 컴포넌트
* 그룹 펼치기/접기, 체크박스, 요약 정보 표시
*/
export function GroupHeader({
group,
config,
onToggle,
onSelectToggle,
style = "default",
columnCount = 1,
}: GroupHeaderProps) {
const { showCheckbox } = config;
const { summary } = group;
// 일부 선택 여부
const isIndeterminate =
group.selectedItemIds &&
group.selectedItemIds.length > 0 &&
group.selectedItemIds.length < group.items.length;
// 요약 텍스트 생성
const summaryText = React.useMemo(() => {
const parts: string[] = [];
// 개수
if (config.groupConfig?.summary?.showCount !== false) {
parts.push(`${summary.count}`);
}
// 합계
if (summary.sum) {
for (const [col, value] of Object.entries(summary.sum)) {
const displayName =
config.columns?.find((c) => c.columnName === col)?.displayName || col;
parts.push(`${displayName}: ${value.toLocaleString()}`);
}
}
return parts.join(" | ");
}, [summary, config]);
// 스타일별 클래스
const headerClasses = cn(
"flex items-center gap-2 cursor-pointer select-none transition-colors",
{
// default 스타일
"px-3 py-2 bg-muted/50 hover:bg-muted border-b": style === "default",
// compact 스타일
"px-2 py-1 bg-muted/30 hover:bg-muted/50 border-b text-sm":
style === "compact",
// card 스타일
"px-4 py-3 bg-card border rounded-t-lg shadow-sm hover:shadow":
style === "card",
}
);
return (
<tr className="group-header-row">
<td
colSpan={columnCount}
className="p-0"
onClick={(e) => {
// 체크박스 클릭 시 토글 방지
if ((e.target as HTMLElement).closest('[data-checkbox="true"]')) {
return;
}
onToggle();
}}
>
<div className={headerClasses}>
{/* 펼치기/접기 아이콘 */}
<span className="flex-shrink-0 text-muted-foreground">
{group.expanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
</span>
{/* 체크박스 */}
{showCheckbox && onSelectToggle && (
<span
data-checkbox="true"
onClick={(e) => {
e.stopPropagation();
onSelectToggle();
}}
>
<Checkbox
checked={group.selected}
className={cn(
"data-[state=checked]:bg-primary",
isIndeterminate && "data-[state=checked]:bg-muted"
)}
/>
{isIndeterminate && (
<Minus className="absolute h-3 w-3 text-muted-foreground" />
)}
</span>
)}
{/* 그룹 라벨 */}
<span className="font-medium text-foreground">{group.groupLabel}</span>
{/* 요약 정보 */}
{summaryText && (
<span className="ml-auto text-sm text-muted-foreground">
{summaryText}
</span>
)}
</div>
</td>
</tr>
);
}
export default GroupHeader;