@superbright/indexeddb-orm 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +9 -0
- package/.prettierrc.cjs +13 -0
- package/CONSUMING_APP_GUIDE.md +164 -0
- package/MIGRATION_SUMMARY.md +157 -0
- package/README.md +45 -0
- package/docs/app-store-guide.md +160 -0
- package/docs/property-store.md +192 -0
- package/docs/structured-store-migration.md +129 -0
- package/examples/property-store-migration.ts +164 -0
- package/index.html +29 -0
- package/package.json +53 -0
- package/playground/main.ts +38 -0
- package/src/adapters/dexie.ts +28 -0
- package/src/adapters/structured-store.ts +85 -0
- package/src/adapters/zustand-app.ts +221 -0
- package/src/adapters/zustand-unified.ts +342 -0
- package/src/adapters/zustand.ts +142 -0
- package/src/api/app.ts +270 -0
- package/src/api/favorites.ts +64 -0
- package/src/api/properties.ts +293 -0
- package/src/api/users.ts +66 -0
- package/src/db.ts +185 -0
- package/src/debug.ts +25 -0
- package/src/errors.ts +13 -0
- package/src/index.ts +34 -0
- package/src/schema.ts +48 -0
- package/src/storage.ts +71 -0
- package/src/stores/property.ts +133 -0
- package/src/stores/unified.ts +507 -0
- package/src/units/favorites.ts +19 -0
- package/src/validation.ts +32 -0
- package/test-export.js +6 -0
- package/tests/orm.spec.ts +17 -0
- package/tests/setup.ts +2 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +29 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ZustandUnifiedStoreState,
|
|
3
|
+
createZustandUnifiedStore,
|
|
4
|
+
type Filters,
|
|
5
|
+
type UnitData,
|
|
6
|
+
type TourContactData,
|
|
7
|
+
type QueryParams
|
|
8
|
+
} from "./zustand-unified";
|
|
9
|
+
|
|
10
|
+
export interface StructuredStoreActions {
|
|
11
|
+
property: {
|
|
12
|
+
unit: {
|
|
13
|
+
favorites: {
|
|
14
|
+
toggle: (unitId: string) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
viewed: {
|
|
17
|
+
mark: (unitId: string, slug: string) => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
questionnaire: {
|
|
21
|
+
setResults: (results: unknown) => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
tour: {
|
|
24
|
+
setContactedOn: () => Promise<void>;
|
|
25
|
+
getContactedOn: () => Promise<string | null>;
|
|
26
|
+
setContactData: (data: TourContactData) => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
filters: {
|
|
31
|
+
set: (filters: Partial<Filters>) => Promise<void>;
|
|
32
|
+
setTemp: (filters: Partial<Filters>) => Promise<void>;
|
|
33
|
+
setToDefault: () => Promise<void>;
|
|
34
|
+
commitTemp: <K extends keyof Filters>(key: K, defaultValue: Filters[K]) => Promise<void>;
|
|
35
|
+
commitAvailability: () => Promise<void>;
|
|
36
|
+
submit: () => Promise<void>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createStructuredStoreActions(store: ZustandUnifiedStoreState): StructuredStoreActions {
|
|
41
|
+
return {
|
|
42
|
+
property: {
|
|
43
|
+
unit: {
|
|
44
|
+
favorites: {
|
|
45
|
+
toggle: store.toggleFavorite.bind(store),
|
|
46
|
+
},
|
|
47
|
+
viewed: {
|
|
48
|
+
mark: store.markUnitAsViewed.bind(store),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
questionnaire: {
|
|
52
|
+
setResults: store.setQuestionnaireResults.bind(store),
|
|
53
|
+
},
|
|
54
|
+
tour: {
|
|
55
|
+
setContactedOn: store.setTourContactedOn.bind(store),
|
|
56
|
+
getContactedOn: store.getTourContactedOn.bind(store),
|
|
57
|
+
setContactData: store.setTourContactData.bind(store),
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
filters: {
|
|
61
|
+
set: store.setFilters.bind(store),
|
|
62
|
+
setTemp: store.setTempFilters.bind(store),
|
|
63
|
+
setToDefault: store.setFiltersToDefault.bind(store),
|
|
64
|
+
commitTemp: store.commitTempFilterChange.bind(store),
|
|
65
|
+
commitAvailability: store.commitAvailabilityChange.bind(store),
|
|
66
|
+
submit: store.submitFilterUpdate.bind(store),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type StructuredStore = ZustandUnifiedStoreState & StructuredStoreActions;
|
|
72
|
+
|
|
73
|
+
export function createStructuredStore(options?: {
|
|
74
|
+
onFilterUpdate?: (apiParams: QueryParams) => void;
|
|
75
|
+
}) {
|
|
76
|
+
return (set: any, get: any): StructuredStore => {
|
|
77
|
+
const baseStore = createZustandUnifiedStore(options)(set, get);
|
|
78
|
+
const actions = createStructuredStoreActions(baseStore);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...baseStore,
|
|
82
|
+
...actions,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { appStore, type AppStoreData, type UnitData, type Filters, type QueryParams, type ResultsMode, type SortBy } from "../api/app";
|
|
2
|
+
|
|
3
|
+
// Zustand-compatible app store interface
|
|
4
|
+
export interface ZustandAppStoreState {
|
|
5
|
+
data: Record<string, UnitData>;
|
|
6
|
+
filters: Filters;
|
|
7
|
+
tempFilters: Filters;
|
|
8
|
+
apiFilters: QueryParams;
|
|
9
|
+
resultsMode: ResultsMode;
|
|
10
|
+
propertySlug: string | null;
|
|
11
|
+
resolvedQuestionnaireValues: Record<string, string[]>;
|
|
12
|
+
sortBy: SortBy;
|
|
13
|
+
filtersLoaded: boolean;
|
|
14
|
+
|
|
15
|
+
// Async actions
|
|
16
|
+
setData: (key: string, value: UnitData) => Promise<void>;
|
|
17
|
+
removeData: (key: string) => Promise<void>;
|
|
18
|
+
clearData: () => Promise<void>;
|
|
19
|
+
setFilters: (filters: Partial<Filters>) => Promise<void>;
|
|
20
|
+
setTempFilters: (filters: Partial<Filters>) => Promise<void>;
|
|
21
|
+
setFiltersToDefault: () => Promise<void>;
|
|
22
|
+
setApiFilters: (filters: Partial<QueryParams>) => Promise<void>;
|
|
23
|
+
setResultsMode: (mode: ResultsMode) => Promise<void>;
|
|
24
|
+
setSortBy: (sortBy: SortBy) => Promise<void>;
|
|
25
|
+
setPropertySlug: (slug: string) => Promise<void>;
|
|
26
|
+
getResultsUrl: () => Promise<string | null>;
|
|
27
|
+
setResolvedQuestionnaireValues: (name: string, values: string[]) => Promise<void>;
|
|
28
|
+
handleTempFilterChange: <K extends keyof Filters>(key: K, value: Filters[K]) => Promise<void>;
|
|
29
|
+
commitTempFilterChange: <K extends keyof Filters>(key: K, defaultValue: Filters[K]) => Promise<void>;
|
|
30
|
+
handleFilterCommitIndexDB: (newFilters: Partial<Filters>) => Promise<void>;
|
|
31
|
+
commitAvailabilityChange: () => Promise<void>;
|
|
32
|
+
submitFilterUpdate: () => Promise<void>;
|
|
33
|
+
loadPersistedFilters: () => Promise<void>;
|
|
34
|
+
|
|
35
|
+
// Internal hydration
|
|
36
|
+
_hydrate: () => Promise<void>;
|
|
37
|
+
_initialize: () => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createZustandAppStore(options?: {
|
|
41
|
+
onFilterUpdate?: (apiParams: QueryParams) => void;
|
|
42
|
+
}) {
|
|
43
|
+
return (set: any, get: any): ZustandAppStoreState => {
|
|
44
|
+
// Helper to update local state after ORM operations
|
|
45
|
+
const syncState = async () => {
|
|
46
|
+
const ormState = await appStore.getFullState();
|
|
47
|
+
set({
|
|
48
|
+
data: ormState.data,
|
|
49
|
+
filters: ormState.filters,
|
|
50
|
+
tempFilters: ormState.tempFilters,
|
|
51
|
+
apiFilters: ormState.apiFilters,
|
|
52
|
+
resultsMode: ormState.resultsMode,
|
|
53
|
+
propertySlug: ormState.propertySlug,
|
|
54
|
+
resolvedQuestionnaireValues: ormState.resolvedQuestionnaireValues,
|
|
55
|
+
sortBy: ormState.sortBy,
|
|
56
|
+
filtersLoaded: ormState.filtersLoaded,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
// Initial state
|
|
62
|
+
data: {},
|
|
63
|
+
filters: {
|
|
64
|
+
availability: undefined,
|
|
65
|
+
bedrooms: undefined,
|
|
66
|
+
cost: undefined,
|
|
67
|
+
highlights: undefined,
|
|
68
|
+
},
|
|
69
|
+
tempFilters: {
|
|
70
|
+
availability: undefined,
|
|
71
|
+
bedrooms: undefined,
|
|
72
|
+
cost: undefined,
|
|
73
|
+
highlights: undefined,
|
|
74
|
+
},
|
|
75
|
+
apiFilters: {
|
|
76
|
+
limit: 10,
|
|
77
|
+
page: 1,
|
|
78
|
+
},
|
|
79
|
+
resultsMode: "all",
|
|
80
|
+
propertySlug: null,
|
|
81
|
+
resolvedQuestionnaireValues: {},
|
|
82
|
+
sortBy: "relevance",
|
|
83
|
+
filtersLoaded: false,
|
|
84
|
+
|
|
85
|
+
// Unit data actions
|
|
86
|
+
async setData(key, value) {
|
|
87
|
+
await appStore.setData(key, value);
|
|
88
|
+
const state = get();
|
|
89
|
+
set({ data: { ...state.data, [key]: value } });
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async removeData(key) {
|
|
93
|
+
await appStore.removeData(key);
|
|
94
|
+
await syncState();
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async clearData() {
|
|
98
|
+
await appStore.clearData();
|
|
99
|
+
set({ data: {} });
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Filter actions
|
|
103
|
+
async setFilters(filters) {
|
|
104
|
+
await appStore.setFilters(filters);
|
|
105
|
+
const state = get();
|
|
106
|
+
set({ filters: { ...state.filters, ...filters } });
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async setTempFilters(filters) {
|
|
110
|
+
await appStore.setTempFilters(filters);
|
|
111
|
+
const state = get();
|
|
112
|
+
set({ tempFilters: { ...state.tempFilters, ...filters } });
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async setFiltersToDefault() {
|
|
116
|
+
await appStore.setFiltersToDefault();
|
|
117
|
+
const defaultFilters = {
|
|
118
|
+
availability: undefined,
|
|
119
|
+
bedrooms: undefined,
|
|
120
|
+
cost: undefined,
|
|
121
|
+
highlights: undefined,
|
|
122
|
+
};
|
|
123
|
+
set({ filters: defaultFilters });
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
async setApiFilters(filters) {
|
|
127
|
+
await appStore.setApiFilters(filters);
|
|
128
|
+
const state = get();
|
|
129
|
+
set({ apiFilters: { ...state.apiFilters, ...filters } });
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// Results and sorting actions
|
|
133
|
+
async setResultsMode(mode) {
|
|
134
|
+
await appStore.setResultsMode(mode);
|
|
135
|
+
set({ resultsMode: mode });
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
async setSortBy(sortBy) {
|
|
139
|
+
await appStore.setSortBy(sortBy);
|
|
140
|
+
set({ sortBy });
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Property actions
|
|
144
|
+
async setPropertySlug(slug) {
|
|
145
|
+
await appStore.setPropertySlug(slug);
|
|
146
|
+
set({ propertySlug: slug });
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
getResultsUrl: appStore.getResultsUrl.bind(appStore),
|
|
150
|
+
|
|
151
|
+
// Questionnaire actions
|
|
152
|
+
async setResolvedQuestionnaireValues(name, values) {
|
|
153
|
+
await appStore.setResolvedQuestionnaireValues(name, values);
|
|
154
|
+
const state = get();
|
|
155
|
+
set({
|
|
156
|
+
resolvedQuestionnaireValues: {
|
|
157
|
+
...state.resolvedQuestionnaireValues,
|
|
158
|
+
[name]: values,
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// Complex filter operations
|
|
164
|
+
async handleTempFilterChange(key, value) {
|
|
165
|
+
await appStore.handleTempFilterChange(key, value);
|
|
166
|
+
const state = get();
|
|
167
|
+
set({ tempFilters: { ...state.tempFilters, [key]: value } });
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async commitTempFilterChange(key, defaultValue) {
|
|
171
|
+
await appStore.commitTempFilterChange(key, defaultValue);
|
|
172
|
+
await syncState();
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
async handleFilterCommitIndexDB(newFilters) {
|
|
176
|
+
await appStore.handleFilterCommitIndexDB(newFilters);
|
|
177
|
+
await syncState();
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
async commitAvailabilityChange() {
|
|
181
|
+
await appStore.commitAvailabilityChange();
|
|
182
|
+
await syncState();
|
|
183
|
+
|
|
184
|
+
// Notify consuming app of filter update if callback provided
|
|
185
|
+
if (options?.onFilterUpdate) {
|
|
186
|
+
const apiFilters = await appStore.getApiFilters();
|
|
187
|
+
options.onFilterUpdate(apiFilters);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
async submitFilterUpdate() {
|
|
192
|
+
await appStore.submitFilterUpdate();
|
|
193
|
+
await syncState();
|
|
194
|
+
|
|
195
|
+
// Notify consuming app of filter update if callback provided
|
|
196
|
+
if (options?.onFilterUpdate) {
|
|
197
|
+
const apiFilters = await appStore.getApiFilters();
|
|
198
|
+
options.onFilterUpdate(apiFilters);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
async loadPersistedFilters() {
|
|
203
|
+
await appStore.loadPersistedFilters();
|
|
204
|
+
set({ filtersLoaded: true });
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
// Internal methods
|
|
208
|
+
async _hydrate() {
|
|
209
|
+
await syncState();
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
async _initialize() {
|
|
213
|
+
await appStore.initialize();
|
|
214
|
+
await syncState();
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Export types for consuming apps
|
|
221
|
+
export type { UnitData, Filters, QueryParams, ResultsMode, SortBy } from "../api/app";
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { store, type UnifiedStoreData, type UnitData, type PropertyData, type Filters, type QueryParams, type ResultsMode, type SortBy, type TourContactData } from "../stores/unified";
|
|
2
|
+
|
|
3
|
+
// Unified Zustand store interface
|
|
4
|
+
export interface ZustandUnifiedStoreState {
|
|
5
|
+
// Property data
|
|
6
|
+
properties: Record<string, PropertyData>;
|
|
7
|
+
currentPropertyId: string | null;
|
|
8
|
+
currentPropertySlug: string | null;
|
|
9
|
+
hasPreviouslySearched: string[];
|
|
10
|
+
|
|
11
|
+
// App data
|
|
12
|
+
units: Record<string, UnitData>;
|
|
13
|
+
filters: Filters;
|
|
14
|
+
tempFilters: Filters;
|
|
15
|
+
apiFilters: QueryParams;
|
|
16
|
+
resultsMode: ResultsMode;
|
|
17
|
+
resolvedQuestionnaireValues: Record<string, string[]>;
|
|
18
|
+
sortBy: SortBy;
|
|
19
|
+
filtersLoaded: boolean;
|
|
20
|
+
|
|
21
|
+
// === PROPERTY OPERATIONS ===
|
|
22
|
+
initializeProperty: (propertyId: string, slug: string) => Promise<void>;
|
|
23
|
+
setCurrentProperty: (propertyId: string, slug?: string) => Promise<void>;
|
|
24
|
+
setCurrentPropertySlug: (slug: string) => Promise<void>;
|
|
25
|
+
setHasPreviouslySearched: (slug: string) => Promise<void>;
|
|
26
|
+
toggleFavorite: (unitId: string) => Promise<void>;
|
|
27
|
+
markUnitAsViewed: (unitId: string, slug: string) => Promise<void>;
|
|
28
|
+
setTourContactedOn: () => Promise<void>;
|
|
29
|
+
getTourContactedOn: () => Promise<string | null>;
|
|
30
|
+
setQuestionnaireResults: (results: unknown) => Promise<void>;
|
|
31
|
+
setTourContactData: (data: TourContactData) => Promise<void>;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// === FILTER OPERATIONS ===
|
|
35
|
+
setFilters: (filters: Partial<Filters>) => Promise<void>;
|
|
36
|
+
setTempFilters: (filters: Partial<Filters>) => Promise<void>;
|
|
37
|
+
setFiltersToDefault: () => Promise<void>;
|
|
38
|
+
setApiFilters: (filters: Partial<QueryParams>) => Promise<void>;
|
|
39
|
+
handleTempFilterChange: <K extends keyof Filters>(key: K, value: Filters[K]) => Promise<void>;
|
|
40
|
+
commitTempFilterChange: <K extends keyof Filters>(key: K, defaultValue: Filters[K]) => Promise<void>;
|
|
41
|
+
handleFilterCommitIndexDB: (newFilters: Partial<Filters>) => Promise<void>;
|
|
42
|
+
commitAvailabilityChange: () => Promise<void>;
|
|
43
|
+
submitFilterUpdate: () => Promise<void>;
|
|
44
|
+
|
|
45
|
+
// === RESULTS AND SORTING ===
|
|
46
|
+
setResultsMode: (mode: ResultsMode) => Promise<void>;
|
|
47
|
+
setSortBy: (sortBy: SortBy) => Promise<void>;
|
|
48
|
+
|
|
49
|
+
// === QUESTIONNAIRE ===
|
|
50
|
+
setResolvedQuestionnaireValues: (name: string, values: string[]) => Promise<void>;
|
|
51
|
+
|
|
52
|
+
// === UTILITY METHODS ===
|
|
53
|
+
getUnitState: (unitId: string) => {
|
|
54
|
+
isFavorite: boolean;
|
|
55
|
+
viewedDate: string;
|
|
56
|
+
};
|
|
57
|
+
getResultsUrl: () => Promise<string | null>;
|
|
58
|
+
getCurrentProperty: () => Promise<PropertyData | null>;
|
|
59
|
+
getPropertyData: (propertyId?: string) => Promise<PropertyData | null>;
|
|
60
|
+
|
|
61
|
+
// === LEGACY COMPATIBILITY ===
|
|
62
|
+
setData: (value: Record<string, PropertyData>) => Promise<void>;
|
|
63
|
+
setPropertySlug: (slug: string) => Promise<void>;
|
|
64
|
+
setPropertyId: (id: string) => Promise<void>;
|
|
65
|
+
removeData: (key: string) => Promise<void>;
|
|
66
|
+
clearData: () => Promise<void>;
|
|
67
|
+
loadPersistedFilters: () => Promise<void>;
|
|
68
|
+
|
|
69
|
+
// === INTERNAL ===
|
|
70
|
+
_hydrate: () => Promise<void>;
|
|
71
|
+
_initialize: () => Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createZustandUnifiedStore(options?: {
|
|
75
|
+
onFilterUpdate?: (apiParams: QueryParams) => void;
|
|
76
|
+
}) {
|
|
77
|
+
return (set: any, get: any): ZustandUnifiedStoreState => {
|
|
78
|
+
// Helper to update local state after ORM operations
|
|
79
|
+
const syncState = async () => {
|
|
80
|
+
const ormState = await store.getFullState();
|
|
81
|
+
set({
|
|
82
|
+
properties: ormState.properties,
|
|
83
|
+
currentPropertyId: ormState.currentPropertyId,
|
|
84
|
+
currentPropertySlug: ormState.currentPropertySlug,
|
|
85
|
+
hasPreviouslySearched: ormState.hasPreviouslySearched,
|
|
86
|
+
filters: ormState.filters,
|
|
87
|
+
tempFilters: ormState.tempFilters,
|
|
88
|
+
apiFilters: ormState.apiFilters,
|
|
89
|
+
resultsMode: ormState.resultsMode,
|
|
90
|
+
resolvedQuestionnaireValues: ormState.resolvedQuestionnaireValues,
|
|
91
|
+
sortBy: ormState.sortBy,
|
|
92
|
+
filtersLoaded: ormState.filtersLoaded,
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Helper to notify of filter updates
|
|
97
|
+
const notifyFilterUpdate = async () => {
|
|
98
|
+
if (options?.onFilterUpdate) {
|
|
99
|
+
const apiFilters = (await store.getFullState()).apiFilters;
|
|
100
|
+
options.onFilterUpdate(apiFilters);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
// Initial state
|
|
106
|
+
properties: {},
|
|
107
|
+
currentPropertyId: null,
|
|
108
|
+
currentPropertySlug: null,
|
|
109
|
+
hasPreviouslySearched: [],
|
|
110
|
+
units: {},
|
|
111
|
+
filters: {
|
|
112
|
+
availability: undefined,
|
|
113
|
+
bedrooms: undefined,
|
|
114
|
+
cost: undefined,
|
|
115
|
+
highlights: undefined,
|
|
116
|
+
},
|
|
117
|
+
tempFilters: {
|
|
118
|
+
availability: undefined,
|
|
119
|
+
bedrooms: undefined,
|
|
120
|
+
cost: undefined,
|
|
121
|
+
highlights: undefined,
|
|
122
|
+
},
|
|
123
|
+
apiFilters: {
|
|
124
|
+
limit: 10,
|
|
125
|
+
page: 1,
|
|
126
|
+
},
|
|
127
|
+
resultsMode: "all",
|
|
128
|
+
resolvedQuestionnaireValues: {},
|
|
129
|
+
sortBy: "relevance",
|
|
130
|
+
filtersLoaded: false,
|
|
131
|
+
|
|
132
|
+
// === PROPERTY OPERATIONS ===
|
|
133
|
+
async initializeProperty(propertyId, slug) {
|
|
134
|
+
await store.initializeProperty(propertyId, slug);
|
|
135
|
+
await syncState();
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
async setCurrentProperty(propertyId, slug) {
|
|
139
|
+
await store.setCurrentProperty(propertyId, slug);
|
|
140
|
+
set({
|
|
141
|
+
currentPropertyId: propertyId,
|
|
142
|
+
...(slug && { currentPropertySlug: slug })
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
async setCurrentPropertySlug(slug) {
|
|
147
|
+
await store.setCurrentPropertySlug(slug);
|
|
148
|
+
set({ currentPropertySlug: slug });
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
async setHasPreviouslySearched(slug) {
|
|
152
|
+
await store.setHasPreviouslySearched(slug);
|
|
153
|
+
await syncState();
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
async toggleFavorite(unitId) {
|
|
157
|
+
await store.toggleFavorite(unitId);
|
|
158
|
+
await syncState();
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
async markUnitAsViewed(unitId, slug) {
|
|
162
|
+
await store.markUnitAsViewed(unitId, slug);
|
|
163
|
+
await syncState();
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async setTourContactedOn() {
|
|
167
|
+
await store.setTourContactedOn();
|
|
168
|
+
await syncState();
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
getTourContactedOn: store.getTourContactedOn.bind(store),
|
|
172
|
+
|
|
173
|
+
async setQuestionnaireResults(results) {
|
|
174
|
+
await store.setQuestionnaireResults(results);
|
|
175
|
+
await syncState();
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
async setTourContactData(data) {
|
|
179
|
+
await store.setTourContactData(data);
|
|
180
|
+
await syncState();
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
// === FILTER OPERATIONS ===
|
|
185
|
+
async setFilters(filters) {
|
|
186
|
+
await store.setFilters(filters);
|
|
187
|
+
const state = get();
|
|
188
|
+
set({ filters: { ...state.filters, ...filters } });
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
async setTempFilters(filters) {
|
|
192
|
+
await store.setTempFilters(filters);
|
|
193
|
+
const state = get();
|
|
194
|
+
set({ tempFilters: { ...state.tempFilters, ...filters } });
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
async setFiltersToDefault() {
|
|
198
|
+
await store.setFiltersToDefault();
|
|
199
|
+
const defaultFilters = {
|
|
200
|
+
availability: undefined,
|
|
201
|
+
bedrooms: undefined,
|
|
202
|
+
cost: undefined,
|
|
203
|
+
highlights: undefined,
|
|
204
|
+
};
|
|
205
|
+
set({ filters: defaultFilters });
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
async setApiFilters(filters) {
|
|
209
|
+
await store.setApiFilters(filters);
|
|
210
|
+
const state = get();
|
|
211
|
+
set({ apiFilters: { ...state.apiFilters, ...filters } });
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
async handleTempFilterChange(key, value) {
|
|
215
|
+
await store.handleTempFilterChange(key, value);
|
|
216
|
+
const state = get();
|
|
217
|
+
set({ tempFilters: { ...state.tempFilters, [key]: value } });
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
async commitTempFilterChange(key, defaultValue) {
|
|
221
|
+
await store.commitTempFilterChange(key, defaultValue);
|
|
222
|
+
await syncState();
|
|
223
|
+
await notifyFilterUpdate();
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
async handleFilterCommitIndexDB(newFilters) {
|
|
227
|
+
await store.handleFilterCommitIndexDB(newFilters);
|
|
228
|
+
await syncState();
|
|
229
|
+
await notifyFilterUpdate();
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
async commitAvailabilityChange() {
|
|
233
|
+
await store.commitAvailabilityChange();
|
|
234
|
+
await syncState();
|
|
235
|
+
await notifyFilterUpdate();
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
async submitFilterUpdate() {
|
|
239
|
+
await store.submitFilterUpdate();
|
|
240
|
+
await syncState();
|
|
241
|
+
await notifyFilterUpdate();
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// === RESULTS AND SORTING ===
|
|
245
|
+
async setResultsMode(mode) {
|
|
246
|
+
await store.setResultsMode(mode);
|
|
247
|
+
set({ resultsMode: mode });
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
async setSortBy(sortBy) {
|
|
251
|
+
await store.setSortBy(sortBy);
|
|
252
|
+
set({ sortBy });
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// === QUESTIONNAIRE ===
|
|
256
|
+
async setResolvedQuestionnaireValues(name, values) {
|
|
257
|
+
await store.setResolvedQuestionnaireValues(name, values);
|
|
258
|
+
const state = get();
|
|
259
|
+
set({
|
|
260
|
+
resolvedQuestionnaireValues: {
|
|
261
|
+
...state.resolvedQuestionnaireValues,
|
|
262
|
+
[name]: values,
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// === UTILITY METHODS ===
|
|
268
|
+
getUnitState(unitId: string) {
|
|
269
|
+
const state = get();
|
|
270
|
+
const property = state.currentPropertyId ? state.properties[state.currentPropertyId] : null;
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
isFavorite: property?.favoritedUnits.includes(unitId) ?? false,
|
|
274
|
+
viewedDate:
|
|
275
|
+
property?.viewedUnits.find((u: any) => u.unitId === unitId)?.viewedDate ?? "",
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
getResultsUrl: store.getResultsUrl.bind(store),
|
|
280
|
+
getCurrentProperty: store.getCurrentProperty.bind(store),
|
|
281
|
+
getPropertyData: store.getPropertyData.bind(store),
|
|
282
|
+
|
|
283
|
+
// === LEGACY COMPATIBILITY ===
|
|
284
|
+
async setData(value) {
|
|
285
|
+
await store.setData(value);
|
|
286
|
+
set({ properties: value });
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
async setPropertySlug(slug) {
|
|
290
|
+
await store.setPropertySlug(slug);
|
|
291
|
+
set({ currentPropertySlug: slug });
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
async setPropertyId(id) {
|
|
295
|
+
await store.setPropertyId(id);
|
|
296
|
+
set({ currentPropertyId: id });
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
async removeData(key) {
|
|
300
|
+
await store.removeData(key);
|
|
301
|
+
await syncState();
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
async clearData() {
|
|
305
|
+
await store.clearData();
|
|
306
|
+
set({ properties: {} });
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
async loadPersistedFilters() {
|
|
310
|
+
await store.loadPersistedFilters();
|
|
311
|
+
set({ filtersLoaded: true });
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
// === INTERNAL ===
|
|
315
|
+
async _hydrate() {
|
|
316
|
+
await syncState();
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
async _initialize() {
|
|
320
|
+
await store.initialize();
|
|
321
|
+
await syncState();
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Helper hook factory for unit state
|
|
328
|
+
export function createUseUnitState() {
|
|
329
|
+
return (useStore: any) => (unitId: string) =>
|
|
330
|
+
useStore((state: ZustandUnifiedStoreState) => state.getUnitState(unitId));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Export types for consuming apps
|
|
334
|
+
export type {
|
|
335
|
+
UnitData,
|
|
336
|
+
PropertyData,
|
|
337
|
+
Filters,
|
|
338
|
+
QueryParams,
|
|
339
|
+
ResultsMode,
|
|
340
|
+
SortBy,
|
|
341
|
+
TourContactData
|
|
342
|
+
} from "../stores/unified";
|