피벗에 필터링쪽에 초기화버튼 넣었음

This commit is contained in:
leeheejin 2026-01-21 16:15:20 +09:00
parent cb8b434434
commit e6bb366ec7
2 changed files with 157 additions and 9 deletions

View File

@ -299,6 +299,8 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
// ==================== 상태 ====================
const [fields, setFields] = useState<PivotFieldConfig[]>(initialFields);
// 초기 필드 설정 저장 (초기화용)
const initialFieldsRef = useRef<PivotFieldConfig[]>(initialFields);
const [pivotState, setPivotState] = useState<PivotGridState>({
expandedRowPaths: [],
expandedColumnPaths: [],
@ -1129,6 +1131,7 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
onFieldsChange={handleFieldsChange}
collapsed={!showFieldPanel}
onToggleCollapse={() => setShowFieldPanel(!showFieldPanel)}
initialFields={initialFieldsRef.current}
/>
{/* 안내 메시지 */}
@ -1405,6 +1408,7 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
onFieldsChange={handleFieldsChange}
collapsed={!showFieldPanel}
onToggleCollapse={() => setShowFieldPanel(!showFieldPanel)}
initialFields={initialFieldsRef.current}
/>
{/* 헤더 툴바 */}

View File

@ -37,6 +37,10 @@ import {
BarChart3,
GripVertical,
ChevronDown,
RotateCcw,
FilterX,
LayoutGrid,
Trash2,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
@ -56,6 +60,8 @@ interface FieldPanelProps {
onFieldSettingsChange?: (field: PivotFieldConfig) => void;
collapsed?: boolean;
onToggleCollapse?: () => void;
/** 초기 필드 설정 (필드 배치 초기화용) */
initialFields?: PivotFieldConfig[];
}
interface FieldChipProps {
@ -123,15 +129,23 @@ const SortableFieldChip: React.FC<FieldChipProps> = ({
transition,
};
// 필터 적용 여부 확인
const hasFilter = field.filterValues && field.filterValues.length > 0;
const filterCount = field.filterValues?.length || 0;
return (
<div
ref={setNodeRef}
style={style}
className={cn(
"inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs",
"bg-background border border-border shadow-sm",
"bg-background border shadow-sm",
"hover:bg-accent/50 transition-colors",
isDragging && "opacity-50 shadow-lg"
isDragging && "opacity-50 shadow-lg",
// 필터 적용 시 강조 표시
hasFilter
? "border-primary bg-primary/5"
: "border-border"
)}
>
{/* 드래그 핸들 */}
@ -143,11 +157,24 @@ const SortableFieldChip: React.FC<FieldChipProps> = ({
<GripVertical className="h-3 w-3" />
</button>
{/* 필터 아이콘 (필터 적용 시) */}
{hasFilter && (
<Filter className="h-3 w-3 text-primary" />
)}
{/* 필드 라벨 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="flex items-center gap-1 hover:text-primary">
<span className="font-medium">{field.caption}</span>
<span className={cn("font-medium", hasFilter && "text-primary")}>
{field.caption}
</span>
{/* 필터 적용 개수 배지 */}
{hasFilter && (
<span className="bg-primary text-primary-foreground text-[10px] px-1 rounded">
{filterCount}
</span>
)}
{field.area === "data" && field.summaryType && (
<span className="text-muted-foreground">
({getSummaryLabel(field.summaryType)})
@ -208,6 +235,19 @@ const SortableFieldChip: React.FC<FieldChipProps> = ({
{field.sortOrder === "asc" ? "내림차순 정렬" : "오름차순 정렬"}
</DropdownMenuItem>
<DropdownMenuSeparator />
{/* 필터 초기화 (필터가 적용된 경우에만 표시) */}
{hasFilter && (
<>
<DropdownMenuItem
onClick={() => onSettingsChange?.({ ...field, filterValues: [] })}
className="text-orange-600"
>
<Filter className="h-3 w-3 mr-2" />
({filterCount} )
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem
onClick={() => onSettingsChange?.({ ...field, visible: false })}
>
@ -326,10 +366,73 @@ export const FieldPanel: React.FC<FieldPanelProps> = ({
onFieldSettingsChange,
collapsed = false,
onToggleCollapse,
initialFields,
}) => {
const [activeId, setActiveId] = useState<string | null>(null);
const [overArea, setOverArea] = useState<PivotAreaType | null>(null);
// 필터만 초기화
const handleResetFilters = () => {
const newFields = fields.map((f) => ({
...f,
filterValues: [],
filterType: "include" as const,
}));
onFieldsChange(newFields);
};
// 필드 배치 초기화 (initialFields가 있으면 사용, 없으면 모든 필드를 row로)
const handleResetLayout = () => {
if (initialFields && initialFields.length > 0) {
// initialFields의 영역 배치를 복원하되 현재 필터 값은 유지
const newFields = fields.map((f) => {
const initial = initialFields.find((i) => i.field === f.field);
if (initial) {
return {
...f,
area: initial.area,
areaIndex: initial.areaIndex,
};
}
return f;
});
onFieldsChange(newFields);
} else {
// 기본값: 숫자는 data, 나머지는 row로
const newFields = fields.map((f, idx) => ({
...f,
area: f.dataType === "number" ? "data" : "row" as PivotAreaType,
areaIndex: idx,
visible: true,
}));
onFieldsChange(newFields);
}
};
// 전체 초기화 (필드 배치 + 필터)
const handleResetAll = () => {
if (initialFields && initialFields.length > 0) {
// initialFields로 완전히 복원
onFieldsChange([...initialFields]);
} else {
// 기본값으로 초기화
const newFields = fields.map((f, idx) => ({
...f,
area: f.dataType === "number" ? "data" : "row" as PivotAreaType,
areaIndex: idx,
visible: true,
filterValues: [],
filterType: "include" as const,
}));
onFieldsChange(newFields);
}
};
// 필터가 적용된 필드 개수
const filteredFieldCount = fields.filter(
(f) => f.filterValues && f.filterValues.length > 0
).length;
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
@ -576,19 +679,60 @@ export const FieldPanel: React.FC<FieldPanelProps> = ({
/>
</div>
{/* 접기 버튼 */}
{onToggleCollapse && (
<div className="flex justify-center mt-1.5">
{/* 하단 버튼 영역 */}
<div className="flex items-center justify-between mt-1.5">
{/* 초기화 드롭다운 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="text-xs h-6 px-2 text-muted-foreground hover:text-foreground"
>
<RotateCcw className="h-3 w-3 mr-1" />
{filteredFieldCount > 0 && (
<span className="ml-1 bg-orange-500 text-white text-[10px] px-1 rounded">
{filteredFieldCount}
</span>
)}
<ChevronDown className="h-3 w-3 ml-1" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
<DropdownMenuItem onClick={handleResetFilters}>
<FilterX className="h-3.5 w-3.5 mr-2 text-orange-500" />
{filteredFieldCount > 0 && (
<span className="ml-auto text-xs text-muted-foreground">
({filteredFieldCount})
</span>
)}
</DropdownMenuItem>
<DropdownMenuItem onClick={handleResetLayout}>
<LayoutGrid className="h-3.5 w-3.5 mr-2 text-blue-500" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleResetAll} className="text-destructive">
<Trash2 className="h-3.5 w-3.5 mr-2" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* 접기 버튼 */}
{onToggleCollapse && (
<Button
variant="ghost"
size="sm"
onClick={onToggleCollapse}
className="text-xs h-5 px-2"
className="text-xs h-6 px-2"
>
</Button>
</div>
)}
)}
</div>
</div>
{/* 드래그 오버레이 */}