Skip to content

Commit a5b65f2

Browse files
Seungwoo321claude
andcommitted
feat: PivotModel 양방향 바인딩 기능 구현
- VPivottableUi 컴포넌트에 v-model:pivotModel 지원 추가 - 필터 변경사항이 즉시 PivotModel에 반영되도록 수정 - pivotModelsEqual 함수에 valueFilter 비교 로직 추가 - props 정의를 Partial<DefaultPropsType>로 변경하여 유연성 향상 - 디버그 로그 제거 및 코드 정리 - ESLint 오류 수정 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1e401fb commit a5b65f2

File tree

6 files changed

+157
-34
lines changed

6 files changed

+157
-34
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"vue-pivottable": minor
3+
---
4+
5+
feat: PivotModel 양방향 바인딩 기능 구현
6+
7+
- VPivottableUi 컴포넌트에 v-model:pivotModel 지원 추가
8+
- PivotModel 인터페이스 정의 및 타입 시스템 구축
9+
- 필터 변경사항이 즉시 PivotModel에 반영되도록 수정
10+
- props 정의를 Partial<DefaultPropsType>로 변경하여 pivotModel 사용 시 개별 props를 선택적으로 만듦
11+
- PivotModel 유틸리티 함수 추가 (비교, 생성, 복제)
12+
- PivotModel 히스토리 관리를 위한 composable 추가 (usePivotModelHistory)
13+
- PivotModel 직렬화/역직렬화 유틸리티 추가

src/App.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,7 @@
2222
v-if="data.length > 0"
2323
v-model:pivot-model="pivotModel"
2424
:data="data"
25-
:rows="rows"
26-
:cols="cols"
27-
:vals="vals"
2825
:renderers="renderers"
29-
:aggregator-name="aggregatorName"
30-
:renderer-name="rendererName"
3126
:sorters="sorters"
3227
@change="onPivotModelChange"
3328
>
@@ -136,8 +131,8 @@ const pivotModel = ref({
136131
heatmapMode: ''
137132
})
138133
139-
const onPivotModelChange = (model) => {
140-
console.log('PivotModel 변경됨:', model)
134+
const onPivotModelChange = () => {
135+
// PivotModel이 변경될 때 호출됩니다
141136
}
142137
143138
const onDataParsed = () => {

src/components/pivottable-ui/VFilterBox.vue

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ const addValuesToFilter = (values: string[]) => {
116116
},
117117
Object.assign({}, unselectedValues.value)
118118
)
119-
console.log('FilterBox emit addValues:', props.filterBoxKey, filterValues)
120119
emit('update:unselectedFilterValues', {
121120
key: props.filterBoxKey,
122121
value: filterValues
@@ -132,14 +131,12 @@ const removeValuesFromFilter = (values: string[]) => {
132131
},
133132
Object.assign({}, unselectedValues.value)
134133
)
135-
console.log('FilterBox emit removeValues:', props.filterBoxKey, filterValues)
136134
emit('update:unselectedFilterValues', {
137135
key: props.filterBoxKey,
138136
value: filterValues
139137
})
140138
}
141139
const toggleValue = (value: string) => {
142-
console.log('FilterBox toggleValue:', value, 'in unselected:', value in unselectedValues.value)
143140
if (value in unselectedValues.value) {
144141
removeValuesFromFilter([value])
145142
} else {

src/components/pivottable-ui/VPivottableUi.vue

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
</template>
133133

134134
<script setup lang="ts">
135-
import { aggregators, PivotData, sortAs , locales } from '@/helper'
135+
import { PivotData, sortAs , locales, AggregatorTemplate, Locale } from '@/helper'
136136
import VRendererCell from './VRendererCell.vue'
137137
import VAggregatorCell from './VAggregatorCell.vue'
138138
import VDragAndDropCell from './VDragAndDropCell.vue'
@@ -144,22 +144,43 @@ import {
144144
usePivotUiState,
145145
provideFilterBox
146146
} from '@/composables'
147-
import { DefaultPropsType, PivotModelInterface } from '@/types'
147+
import { DefaultPropsType, PivotModelInterface, RendererDefinition } from '@/types'
148148
149149
const props = withDefaults(
150150
defineProps<
151-
DefaultPropsType & {
151+
Partial<DefaultPropsType> & {
152+
data: any
153+
renderers: Record<string, RendererDefinition>
152154
hiddenAttributes?: string[]
153155
hiddenFromAggregators?: string[]
154156
hiddenFromDragDrop?: string[]
155157
restrictedFromDragDrop?: string[]
156158
menuLimit?: number
157159
pivotModel?: PivotModelInterface
158160
hideFilterBoxOfUnusedAttributes?: boolean
161+
aggregators?: Record<string, AggregatorTemplate>
162+
aggregatorName?: string
163+
heatmapMode?: 'full' | 'col' | 'row' | ''
164+
tableColorScaleGenerator?: (...args: any[]) => any
165+
tableOptions?: Record<string, any>
166+
rendererName?: string
167+
locale?: string
168+
languagePack?: Record<string, Locale>
169+
showRowTotal?: boolean
170+
showColTotal?: boolean
171+
cols?: string[]
172+
rows?: string[]
173+
vals?: string[]
174+
attributes?: string[]
175+
valueFilter?: Record<string, any>
176+
sorters?: any
177+
derivedAttributes?: any
178+
rowOrder?: 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a'
179+
colOrder?: 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a'
180+
tableMaxWidth?: number
159181
}
160182
>(),
161183
{
162-
aggregators: () => aggregators,
163184
hiddenAttributes: () => [],
164185
hiddenFromAggregators: () => [],
165186
pivotModel: undefined,
@@ -170,7 +191,20 @@ const props = withDefaults(
170191
rowOrder: 'key_a_to_z',
171192
colOrder: 'key_a_to_z',
172193
languagePack: () => locales,
173-
locale: 'en'
194+
locale: 'en',
195+
rows: () => [],
196+
cols: () => [],
197+
vals: () => [],
198+
aggregatorName: 'Count',
199+
rendererName: 'Table',
200+
valueFilter: () => ({}),
201+
heatmapMode: '',
202+
tableColorScaleGenerator: undefined,
203+
tableOptions: () => ({}),
204+
attributes: () => [],
205+
sorters: () => ({}),
206+
derivedAttributes: () => ({}),
207+
tableMaxWidth: 0
174208
}
175209
)
176210
@@ -179,6 +213,55 @@ const emit = defineEmits<{
179213
'change': [model: PivotModelInterface]
180214
}>()
181215
216+
// pivotModel이 제공되면 해당 값으로 props 오버라이드
217+
const propsWithModel = computed(() => {
218+
const base = {
219+
data: props.data,
220+
renderers: props.renderers,
221+
aggregators: props.aggregators,
222+
aggregatorName: props.aggregatorName || 'Count',
223+
heatmapMode: (props.heatmapMode || '') as 'full' | 'col' | 'row' | '',
224+
tableColorScaleGenerator: props.tableColorScaleGenerator,
225+
tableOptions: props.tableOptions,
226+
rendererName: props.rendererName || 'Table',
227+
locale: props.locale || 'en',
228+
languagePack: props.languagePack || locales,
229+
showRowTotal: props.showRowTotal,
230+
showColTotal: props.showColTotal,
231+
cols: props.cols || [],
232+
rows: props.rows || [],
233+
vals: props.vals || [],
234+
attributes: props.attributes,
235+
valueFilter: props.valueFilter || {},
236+
sorters: props.sorters,
237+
derivedAttributes: props.derivedAttributes,
238+
rowOrder: (props.rowOrder || 'key_a_to_z') as 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a',
239+
colOrder: (props.colOrder || 'key_a_to_z') as 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a',
240+
tableMaxWidth: props.tableMaxWidth,
241+
hiddenAttributes: props.hiddenAttributes || [],
242+
hiddenFromAggregators: props.hiddenFromAggregators || [],
243+
hiddenFromDragDrop: props.hiddenFromDragDrop || [],
244+
restrictedFromDragDrop: props.restrictedFromDragDrop || [],
245+
hideFilterBoxOfUnusedAttributes: props.hideFilterBoxOfUnusedAttributes || false
246+
}
247+
248+
if (props.pivotModel && Object.keys(props.pivotModel).length > 0) {
249+
return {
250+
...base,
251+
rows: props.pivotModel.rows || base.rows,
252+
cols: props.pivotModel.cols || base.cols,
253+
vals: props.pivotModel.vals || base.vals,
254+
aggregatorName: props.pivotModel.aggregatorName || base.aggregatorName,
255+
rendererName: props.pivotModel.rendererName || base.rendererName,
256+
valueFilter: props.pivotModel.valueFilter || base.valueFilter,
257+
rowOrder: (props.pivotModel.rowOrder || base.rowOrder) as 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a',
258+
colOrder: (props.pivotModel.colOrder || base.colOrder) as 'key_a_to_z' | 'value_a_to_z' | 'value_z_to_a',
259+
heatmapMode: (props.pivotModel.heatmapMode || base.heatmapMode) as 'full' | 'col' | 'row' | ''
260+
}
261+
}
262+
return base
263+
})
264+
182265
const {
183266
state,
184267
localeStrings,
@@ -190,7 +273,7 @@ const {
190273
onUpdateRowOrder,
191274
onUpdateColOrder,
192275
onUpdateVals
193-
} = usePropsState(props, emit)
276+
} = usePropsState(propsWithModel.value, emit)
194277
195278
const {
196279
state: pivotUiState,
@@ -283,8 +366,8 @@ const pivotProps = computed(() => ({
283366
tableOptions: state.tableOptions,
284367
renderers: rendererItems.value,
285368
rendererName: state.rendererName,
286-
locale: state.locale,
287-
languagePack: state.languagePack,
369+
locale: state.locale || 'en',
370+
languagePack: state.languagePack || locales,
288371
showRowTotal: state.showRowTotal,
289372
showColTotal: state.showColTotal,
290373
cols: state.cols,

src/composables/usePropsState.ts

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,24 @@ import { debounce } from '@/utils/performance'
44
import { pivotModelsEqual, clonePivotModel } from '@/utils/pivotModel'
55
import { locales, LocaleStrings } from '@/helper'
66
export type UsePropsStateProps = Pick<DefaultPropsType,
7-
'aggregators' | 'languagePack' | 'locale' | 'valueFilter' | 'rendererName' | 'heatmapMode' | 'aggregatorName' | 'rowOrder' | 'colOrder' | 'vals' | 'rows' | 'cols'>
7+
'aggregators' | 'languagePack' | 'locale' | 'valueFilter' | 'rendererName' | 'heatmapMode' | 'aggregatorName' | 'rowOrder' | 'colOrder' | 'vals' | 'rows' | 'cols'> & {
8+
data?: any
9+
renderers?: any
10+
hiddenAttributes?: string[]
11+
hiddenFromAggregators?: string[]
12+
hiddenFromDragDrop?: string[]
13+
restrictedFromDragDrop?: string[]
14+
hideFilterBoxOfUnusedAttributes?: boolean
15+
tableOptions?: any
16+
showRowTotal?: boolean
17+
showColTotal?: boolean
18+
attributes?: string[]
19+
sorters?: any
20+
derivedAttributes?: any
21+
tableMaxWidth?: number
22+
allFilters?: any
23+
materializedInput?: any
24+
}
825

926
export interface UsePropsStateReturn<T extends UsePropsStateProps> {
1027
state: UnwrapRef<T>
@@ -26,7 +43,21 @@ export function usePropsState<T extends UsePropsStateProps> (
2643
emit?: (event: string, payload: any) => void
2744
): UsePropsStateReturn<T> {
2845
const state = reactive({
29-
...initialProps
46+
...initialProps,
47+
data: initialProps.data || [],
48+
renderers: initialProps.renderers || {},
49+
hiddenAttributes: initialProps.hiddenAttributes || [],
50+
hiddenFromAggregators: initialProps.hiddenFromAggregators || [],
51+
hiddenFromDragDrop: initialProps.hiddenFromDragDrop || [],
52+
restrictedFromDragDrop: initialProps.restrictedFromDragDrop || [],
53+
hideFilterBoxOfUnusedAttributes: initialProps.hideFilterBoxOfUnusedAttributes || false,
54+
tableOptions: initialProps.tableOptions || {},
55+
showRowTotal: initialProps.showRowTotal ?? true,
56+
showColTotal: initialProps.showColTotal ?? true,
57+
attributes: initialProps.attributes || [],
58+
sorters: initialProps.sorters || {},
59+
derivedAttributes: initialProps.derivedAttributes || {},
60+
tableMaxWidth: initialProps.tableMaxWidth || 0
3061
}) as UnwrapRef<T>
3162

3263
let previousModel: PivotModelInterface | null = null
@@ -36,7 +67,7 @@ export function usePropsState<T extends UsePropsStateProps> (
3667
)
3768

3869
const buildPivotModel = (): PivotModelInterface => {
39-
const model = {
70+
return {
4071
rows: state.rows || [],
4172
cols: state.cols || [],
4273
vals: state.vals || [],
@@ -45,10 +76,10 @@ export function usePropsState<T extends UsePropsStateProps> (
4576
valueFilter: state.valueFilter || {},
4677
rowOrder: state.rowOrder || 'key_a_to_z',
4778
colOrder: state.colOrder || 'key_a_to_z',
48-
heatmapMode: state.heatmapMode || ''
79+
heatmapMode: state.heatmapMode || '',
80+
timestamp: Date.now(),
81+
version: '1.0'
4982
}
50-
console.log('buildPivotModel - valueFilter:', model.valueFilter)
51-
return model
5283
}
5384

5485
const emitPivotModel = () => {
@@ -74,23 +105,26 @@ export function usePropsState<T extends UsePropsStateProps> (
74105
emitPivotModelDebounced()
75106
}
76107

77-
const updateMultiple = (updates: Partial<T>) => {
108+
const updateMultiple = (updates: Partial<T> & { allFilters?: any, materializedInput?: any, data?: any }) => {
78109
Object.entries(updates).forEach(([key, value]) => {
79-
if (key in state) {
110+
if (key in state || key === 'allFilters' || key === 'materializedInput' || key === 'data') {
80111
(state as any)[key] = value
81112
}
82113
})
83-
emitPivotModel()
114+
if (updates.rows || updates.cols || updates.vals || updates.aggregatorName ||
115+
updates.rendererName || updates.valueFilter || updates.rowOrder ||
116+
updates.colOrder || updates.heatmapMode) {
117+
emitPivotModel()
118+
}
84119
}
85120

86121
const onUpdateValueFilter = ({ key, value }: { key: string; value: any }) => {
87-
console.log('usePropsState onUpdateValueFilter:', key, value)
88-
const newFilter = {
89-
...(state.valueFilter || {}),
90-
[key]: value
91-
}
92-
console.log('New valueFilter state:', newFilter)
93-
updateState('valueFilter' as keyof T, newFilter)
122+
updateMultiple({
123+
valueFilter: {
124+
...(state.valueFilter || {}),
125+
[key]: value
126+
}
127+
} as Partial<T>)
94128
}
95129

96130
const onUpdateRendererName = (rendererName: string) => {

src/utils/pivotModel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function pivotModelsEqual(
2121
'vals',
2222
'aggregatorName',
2323
'rendererName',
24+
'valueFilter',
2425
'rowOrder',
2526
'colOrder',
2627
'heatmapMode'

0 commit comments

Comments
 (0)