피벗에 필터링쪽에 초기화버튼 넣었음
This commit is contained in:
parent
cb8b434434
commit
e6bb366ec7
|
|
@ -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}
|
||||
/>
|
||||
|
||||
{/* 헤더 툴바 */}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
{/* 드래그 오버레이 */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue