mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 23:35:45 +00:00
Merge branch 'fix/project-flock' into 'development'
[FIX/FE] Project Flock See merge request mbugroup/lti-web-client!412
This commit is contained in:
@@ -523,7 +523,7 @@ const useSelect = <T,>(
|
|||||||
|
|
||||||
const qs = new URLSearchParams({
|
const qs = new URLSearchParams({
|
||||||
...(params ?? {}),
|
...(params ?? {}),
|
||||||
[searchKey]: inputValue ?? '',
|
[searchKey ? searchKey : 'search']: inputValue ?? '',
|
||||||
[pageKey]: String(pageIndex + 1),
|
[pageKey]: String(pageIndex + 1),
|
||||||
[limitKey]: String(limit),
|
[limitKey]: String(limit),
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { Icon } from '@iconify/react';
|
|||||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
||||||
import { useRouter, usePathname } from 'next/navigation';
|
import { useRouter, usePathname } from 'next/navigation';
|
||||||
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react';
|
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react';
|
||||||
import { useUiStore } from '@/stores/ui/ui.store';
|
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
@@ -148,7 +147,6 @@ const RowOptionsMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||||
const { searchValue, setSearchValue, setTableState } = useUiStore();
|
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const isSuccess = useProjectFlockStore((s) => s.isSuccess);
|
const isSuccess = useProjectFlockStore((s) => s.isSuccess);
|
||||||
@@ -185,7 +183,11 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
category: 'category',
|
category: 'category',
|
||||||
period: 'period',
|
period: 'period',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
persist: true,
|
||||||
|
storeName: 'project-flock-table',
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// ===== State =====
|
// ===== State =====
|
||||||
@@ -425,18 +427,11 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
|
||||||
updateFilter('search', searchValue);
|
|
||||||
}, [searchValue, updateFilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTableState('project-flock-table', pathname);
|
|
||||||
}, [pathname, setTableState]);
|
|
||||||
|
|
||||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
setSearchValue(e.target.value);
|
|
||||||
updateFilter('search', e.target.value);
|
updateFilter('search', e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmApprovalHandler = async (
|
const confirmApprovalHandler = async (
|
||||||
notes: string,
|
notes: string,
|
||||||
approvalAction: 'APPROVED' | 'REJECTED'
|
approvalAction: 'APPROVED' | 'REJECTED'
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ const ProjectFlockForm = ({
|
|||||||
isLoadingOptions: isLoadingFlocks,
|
isLoadingOptions: isLoadingFlocks,
|
||||||
options: optionsFlock,
|
options: optionsFlock,
|
||||||
loadMore: loadMoreFlock,
|
loadMore: loadMoreFlock,
|
||||||
} = useSelect(FlockApi.basePath, 'id', 'name', '', {
|
} = useSelect(FlockApi.basePath, 'id', 'name', 'search', {
|
||||||
project_category: selectedCategory,
|
project_category: selectedCategory,
|
||||||
location_id: selectedLocation,
|
location_id: selectedLocation,
|
||||||
area_id: selectedArea,
|
area_id: selectedArea,
|
||||||
@@ -279,7 +279,7 @@ const ProjectFlockForm = ({
|
|||||||
isLoadingOptions: isLoadingLocations,
|
isLoadingOptions: isLoadingLocations,
|
||||||
setInputValue: setInputValueLocation,
|
setInputValue: setInputValueLocation,
|
||||||
loadMore: loadMoreLocation,
|
loadMore: loadMoreLocation,
|
||||||
} = useSelect(LocationApi.basePath, 'id', 'name', '', {
|
} = useSelect(LocationApi.basePath, 'id', 'name', 'search', {
|
||||||
area_id:
|
area_id:
|
||||||
selectedArea != ''
|
selectedArea != ''
|
||||||
? selectedArea
|
? selectedArea
|
||||||
@@ -291,7 +291,7 @@ const ProjectFlockForm = ({
|
|||||||
isLoadingOptions: isLoadingProductionStandards,
|
isLoadingOptions: isLoadingProductionStandards,
|
||||||
setInputValue: setInputValueProductionStandard,
|
setInputValue: setInputValueProductionStandard,
|
||||||
loadMore: loadMoreProductionStandard,
|
loadMore: loadMoreProductionStandard,
|
||||||
} = useSelect(ProductionStandardApi.basePath, 'id', 'name', '', {
|
} = useSelect(ProductionStandardApi.basePath, 'id', 'name', 'search', {
|
||||||
project_category: selectedCategory,
|
project_category: selectedCategory,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ const ProjectFlockForm = ({
|
|||||||
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
|
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
|
||||||
|
|
||||||
const { data: periodFlocks, mutate: refreshPeriodFlocks } = useSWR(
|
const { data: periodFlocks, mutate: refreshPeriodFlocks } = useSWR(
|
||||||
`${selectedFlock?.toString()}/periods`,
|
selectedFlock ? `${selectedFlock?.toString()}/periods` : undefined,
|
||||||
() => ProjectFlockApi.getNextPeriod(parseInt(selectedLocation as string))
|
() => ProjectFlockApi.getNextPeriod(parseInt(selectedLocation as string))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -793,6 +793,7 @@ const ProjectFlockForm = ({
|
|||||||
formik.values.kandang_ids?.includes(kandang.id)
|
formik.values.kandang_ids?.includes(kandang.id)
|
||||||
)?.period
|
)?.period
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const inputPeriod =
|
const inputPeriod =
|
||||||
(initialValues?.period ?? selectedPeriod == 0) ? 1 : selectedPeriod;
|
(initialValues?.period ?? selectedPeriod == 0) ? 1 : selectedPeriod;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useMemo, useReducer } from 'react';
|
import { useCallback, useEffect, useMemo, useReducer } from 'react';
|
||||||
|
import { useTableFilterStore } from '@/stores/table/table-filter.store';
|
||||||
|
|
||||||
/** Core filter shape (page + pageSize) extended by your custom fields */
|
/** Core filter shape (page + pageSize) extended by your custom fields */
|
||||||
export type TableFilterState<TExtra extends Record<string, unknown>> = {
|
export type TableFilterState<TExtra extends Record<string, unknown>> = {
|
||||||
@@ -30,6 +31,9 @@ export type UseTableFilterOptions<TExtra extends Record<string, unknown>> = {
|
|||||||
paramMap?: Partial<Record<keyof TableFilterState<TExtra>, string>>;
|
paramMap?: Partial<Record<keyof TableFilterState<TExtra>, string>>;
|
||||||
/** If true, `toSearchParams`/`toQueryString` will omit values equal to defaults */
|
/** If true, `toSearchParams`/`toQueryString` will omit values equal to defaults */
|
||||||
omitDefaultsInUrl?: boolean;
|
omitDefaultsInUrl?: boolean;
|
||||||
|
|
||||||
|
persist?: boolean;
|
||||||
|
storeName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function clampToInt(n: number, min = 1) {
|
function clampToInt(n: number, min = 1) {
|
||||||
@@ -90,9 +94,37 @@ function shallowEqual<T extends Record<string, unknown>>(
|
|||||||
export function useTableFilter<TExtra extends Record<string, unknown>>(
|
export function useTableFilter<TExtra extends Record<string, unknown>>(
|
||||||
options?: UseTableFilterOptions<TExtra>
|
options?: UseTableFilterOptions<TExtra>
|
||||||
) {
|
) {
|
||||||
const defaults = useMemo(
|
if (options?.persist && !options?.storeName) {
|
||||||
() => createInitialState<TExtra>(options),
|
throw new Error(
|
||||||
[options]
|
'storeName is required if persist is true in useTableFilter!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const storeName = options?.storeName ?? '';
|
||||||
|
const persistedState = useTableFilterStore(
|
||||||
|
useCallback(
|
||||||
|
(storeState) =>
|
||||||
|
storeName
|
||||||
|
? (storeState.data[storeName] as Partial<TableFilterState<TExtra>>)
|
||||||
|
: undefined,
|
||||||
|
[storeName]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const setTableData = useTableFilterStore(
|
||||||
|
(storeState) => storeState.setTableData
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaults = useMemo(() => {
|
||||||
|
return createInitialState<TExtra>(options);
|
||||||
|
}, [options]);
|
||||||
|
|
||||||
|
const initialState = useMemo(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
...defaults,
|
||||||
|
...(persistedState as object),
|
||||||
|
}) as TableFilterState<TExtra>,
|
||||||
|
[defaults, persistedState]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [state, dispatch] = useReducer(
|
const [state, dispatch] = useReducer(
|
||||||
@@ -106,15 +138,22 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
|
|||||||
case 'SET_PAGE_SIZE': {
|
case 'SET_PAGE_SIZE': {
|
||||||
const pageSize = clampToInt(a.pageSize);
|
const pageSize = clampToInt(a.pageSize);
|
||||||
const page = a.resetPage ? 1 : s.page;
|
const page = a.resetPage ? 1 : s.page;
|
||||||
|
|
||||||
return { ...s, pageSize, page };
|
return { ...s, pageSize, page };
|
||||||
}
|
}
|
||||||
case 'SET_FILTERS': {
|
case 'SET_FILTERS': {
|
||||||
const page = a.resetPage ? 1 : s.page;
|
const page = a.resetPage ? 1 : s.page;
|
||||||
|
|
||||||
return { ...s, ...a.filters, page };
|
return { ...s, ...a.filters, page };
|
||||||
}
|
}
|
||||||
case 'UPDATE_FILTER': {
|
case 'UPDATE_FILTER': {
|
||||||
const page = a.resetPage ? 1 : s.page;
|
const page = a.resetPage ? 1 : s.page;
|
||||||
return { ...s, [a.key]: a.value, page } as TableFilterState<TExtra>;
|
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
[a.key]: a.value,
|
||||||
|
page,
|
||||||
|
} as TableFilterState<TExtra>;
|
||||||
}
|
}
|
||||||
case 'REPLACE_ALL':
|
case 'REPLACE_ALL':
|
||||||
return {
|
return {
|
||||||
@@ -128,12 +167,19 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaults
|
initialState
|
||||||
);
|
);
|
||||||
|
|
||||||
// Notify consumer on change (stable ref)
|
useEffect(() => {
|
||||||
|
if (!options?.persist || !storeName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTableData(storeName, state);
|
||||||
|
}, [options?.persist, setTableData, state, storeName]);
|
||||||
|
|
||||||
const onChange = options?.onChange;
|
const onChange = options?.onChange;
|
||||||
useMemo(() => {
|
useEffect(() => {
|
||||||
if (onChange) onChange(state);
|
if (onChange) onChange(state);
|
||||||
}, [state, onChange]);
|
}, [state, onChange]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { createJSONStorage, devtools, persist } from 'zustand/middleware';
|
||||||
|
import { TableFilterStore } from '@/types/stores';
|
||||||
|
|
||||||
|
type TableFilterStoreState = TableFilterStore<
|
||||||
|
Record<string, Record<string, unknown>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useTableFilterStore = create<TableFilterStoreState>()(
|
||||||
|
devtools(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
data: {},
|
||||||
|
|
||||||
|
setData: (newData) => {
|
||||||
|
set({ data: newData });
|
||||||
|
},
|
||||||
|
|
||||||
|
setTableData: (key, tableData) => {
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[key]: tableData,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
setTableDataField: (key, field, value) => {
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[key]: {
|
||||||
|
...state.data[key],
|
||||||
|
[field]: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
setSearchValue: (key, searchValue) => {
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[key]: {
|
||||||
|
...state.data[key],
|
||||||
|
|
||||||
|
// search key
|
||||||
|
search: searchValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'table-filter-store',
|
||||||
|
storage: createJSONStorage(() => sessionStorage),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { devtools, persist } from 'zustand/middleware';
|
import { createJSONStorage, devtools, persist } from 'zustand/middleware';
|
||||||
|
|
||||||
import { UIStore } from '@/types/stores';
|
import { UIStore } from '@/types/stores';
|
||||||
import { createMainUiSlice } from '@/stores/ui/slices/main.slice';
|
import { createMainUiSlice } from '@/stores/ui/slices/main.slice';
|
||||||
@@ -20,6 +20,7 @@ export const useUiStore = create<UIStore>()(
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'search-store',
|
name: 'search-store',
|
||||||
|
storage: createJSONStorage(() => sessionStorage),
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
key: state.key,
|
key: state.key,
|
||||||
path: state.path,
|
path: state.path,
|
||||||
|
|||||||
Vendored
+8
@@ -117,3 +117,11 @@ export type ProjectFlockSlice = {
|
|||||||
setCreatedProjectFlock: (data: ProjectFlock | null) => void;
|
setCreatedProjectFlock: (data: ProjectFlock | null) => void;
|
||||||
resetProjectFlock: () => void;
|
resetProjectFlock: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TableFilterStore<T = Record<string, Record<string, unknown>>> = {
|
||||||
|
data: T;
|
||||||
|
setData: (newData: T) => void;
|
||||||
|
setTableData: (key: string, tableData: Record<string, unknown>) => void;
|
||||||
|
setTableDataField: (key: string, field: string, value: unknown) => void;
|
||||||
|
setSearchValue: (key: string, searchValue: string) => void;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user