160 lines
3.6 KiB
TypeScript
160 lines
3.6 KiB
TypeScript
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
|
import { produce } from 'immer';
|
|
|
|
import type { BoxerProps, BoxerStoreState } from './Boxer.types';
|
|
|
|
const { Provider: BoxerProvider, useStore: useBoxerStore } = createSyncStore<
|
|
BoxerStoreState,
|
|
BoxerProps
|
|
>(
|
|
(set, get) => ({
|
|
boxerData: [],
|
|
// Data Actions
|
|
fetchData: async (search?: string, reset?: boolean) => {
|
|
const state = get();
|
|
|
|
// Handle local data
|
|
if (state.dataSource === 'local' || !state.onAPICall) {
|
|
const localData = state.data ?? [];
|
|
|
|
if (!search) {
|
|
set({ boxerData: localData, hasMore: false, total: localData.length });
|
|
return;
|
|
}
|
|
|
|
// Filter local data based on search
|
|
const filtered = localData.filter((item) =>
|
|
item.label.toLowerCase().includes(search.toLowerCase())
|
|
);
|
|
set({ boxerData: filtered, hasMore: false, total: filtered.length });
|
|
return;
|
|
}
|
|
|
|
// Handle server-side data
|
|
if (state.onAPICall) {
|
|
try {
|
|
set({ isFetching: true });
|
|
|
|
const currentPage = reset ? 0 : state.page;
|
|
|
|
const result = await state.onAPICall({
|
|
page: currentPage,
|
|
pageSize: state.pageSize,
|
|
search,
|
|
});
|
|
|
|
set(
|
|
produce((draft) => {
|
|
if (reset) {
|
|
draft.boxerData = result.data;
|
|
draft.page = 0;
|
|
} else {
|
|
draft.boxerData = [...(draft.boxerData ?? []), ...result.data];
|
|
}
|
|
draft.total = result.total;
|
|
draft.hasMore = draft.boxerData.length < result.total;
|
|
draft.isFetching = false;
|
|
})
|
|
);
|
|
} catch (error) {
|
|
console.error('Boxer fetchData error:', error);
|
|
set({ isFetching: false });
|
|
}
|
|
}
|
|
},
|
|
fetchMoreOnBottomReached: (target: HTMLDivElement) => {
|
|
const state = get();
|
|
|
|
if (!state.hasMore || state.isFetching) {
|
|
return;
|
|
}
|
|
|
|
const scrollPercentage =
|
|
(target.scrollTop + target.clientHeight) / target.scrollHeight;
|
|
|
|
// Load more when scrolled past 80%
|
|
if (scrollPercentage > 0.8) {
|
|
state.loadMore();
|
|
}
|
|
},
|
|
// State Management
|
|
getState: (key) => {
|
|
const current = get();
|
|
return current?.[key];
|
|
},
|
|
hasMore: true,
|
|
input: '',
|
|
isFetching: false,
|
|
loadMore: async () => {
|
|
const state = get();
|
|
|
|
if (!state.hasMore || state.isFetching) {
|
|
return;
|
|
}
|
|
|
|
set(
|
|
produce((draft) => {
|
|
draft.page = draft.page + 1;
|
|
})
|
|
);
|
|
|
|
await state.fetchData(state.search);
|
|
},
|
|
// Initial State
|
|
opened: false,
|
|
page: 0,
|
|
|
|
pageSize: 50,
|
|
|
|
search: '',
|
|
|
|
selectedOptionIndex: -1,
|
|
|
|
setInput: (input: string) => {
|
|
set({ input });
|
|
},
|
|
|
|
// Actions
|
|
setOpened: (opened: boolean) => {
|
|
set({ opened });
|
|
},
|
|
|
|
setSearch: (search: string) => {
|
|
set({ search });
|
|
},
|
|
|
|
setSelectedOptionIndex: (index: number) => {
|
|
set({ selectedOptionIndex: index });
|
|
},
|
|
|
|
setState: (key, value) => {
|
|
set(
|
|
produce((state) => {
|
|
state[key] = value;
|
|
})
|
|
);
|
|
},
|
|
|
|
total: 0,
|
|
}),
|
|
({
|
|
data = [],
|
|
dataSource = 'local',
|
|
pageSize = 50,
|
|
...props
|
|
}) => {
|
|
return {
|
|
...props,
|
|
boxerData: data, // Initialize with local data if provided
|
|
data,
|
|
dataSource,
|
|
hasMore: dataSource === 'server',
|
|
pageSize,
|
|
total: data.length,
|
|
};
|
|
}
|
|
);
|
|
|
|
export { BoxerProvider };
|
|
export { useBoxerStore };
|