Commit 4752248f authored by Fachri's avatar Fachri

add validate toolbar rules

parent b7ff97de
import { CONFIG } from 'src/global-config';
// eslint-disable-next-line import/no-unresolved
import { NrListView } from 'src/sections/bupot-unifikasi/bupot-nr/view';
// import { NrListView } from 'src/sections/bupot-unifikasi/bupot-nr/view';
const metadata = { title: `E-Bupot Unifikasi- ${CONFIG.appName}` };
export default function Page() {
......
// import { create } from 'zustand';
// type TableKey = string;
// interface TablePagination {
// page: number;
// pageSize: number;
// }
// interface PaginationState {
// tables: Record<TableKey, TablePagination>;
// setPagination: (table: TableKey, next: Partial<TablePagination>) => void;
// resetPagination: (table: TableKey) => void;
// }
// export const usePaginationStore = create<PaginationState>((set) => ({
// tables: {},
// setPagination: (table, next) =>
// set((state) => ({
// tables: {
// ...state.tables,
// [table]: {
// page: next.page ?? state.tables[table]?.page ?? 0,
// pageSize: next.pageSize ?? state.tables[table]?.pageSize ?? 10,
// },
// },
// })),
// resetPagination: (table) =>
// set((state) => ({
// tables: {
// ...state.tables,
// [table]: { page: 0, pageSize: state.tables[table]?.pageSize ?? 10 },
// },
// })),
// }));
import { create } from 'zustand';
console.log('✅ pagination store created');
type TableKey = string;
interface TablePagination {
......@@ -43,14 +9,22 @@ interface TablePagination {
pageSize: number;
}
interface TableFilter {
items: any[];
}
interface PaginationState {
tables: Record<TableKey, TablePagination>;
filters: Record<TableKey, TableFilter>;
setPagination: (table: TableKey, next: Partial<TablePagination>) => void;
resetPagination: (table: TableKey) => void;
setFilter: (table: TableKey, next: Partial<TableFilter>) => void;
resetFilter: (table: TableKey) => void;
}
export const usePaginationStore = create<PaginationState>((set) => ({
tables: {},
filters: {},
setPagination: (table, next) =>
set((state) => {
const prev = state.tables[table] ?? { page: 0, pageSize: 10 };
......@@ -71,4 +45,20 @@ export const usePaginationStore = create<PaginationState>((set) => ({
[table]: { page: 0, pageSize: state.tables[table]?.pageSize ?? 10 },
},
})),
setFilter: (table, next) =>
set((state) => ({
filters: {
...state.filters,
[table]: {
items: next.items ?? state.filters[table]?.items ?? [],
},
},
})),
resetFilter: (table) =>
set((state) => ({
filters: {
...state.filters,
[table]: { items: [] },
},
})),
}));
......@@ -84,14 +84,21 @@ export function DnListView() {
const page = usePaginationStore((s) => s.tables[tableKey]?.page ?? 0);
const pageSize = usePaginationStore((s) => s.tables[tableKey]?.pageSize ?? 10);
const setPagination = usePaginationStore((s) => s.setPagination);
const resetPagination = usePaginationStore((s) => s.resetPagination);
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
const { tables, filters, setPagination, resetPagination, setFilter } =
usePaginationStore.getState();
const [filterModel, setFilterModel] = useState<GridFilterModel>({
items: filters[tableKey]?.items ?? [],
});
const [sortModel, setSortModel] = useState<GridSortModel>([]);
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel | undefined>(
undefined
);
const [localPagination, setLocalPagination] = useState({
page: tables[tableKey]?.page ?? 0,
pageSize: tables[tableKey]?.pageSize ?? 10,
});
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
const [isUploadModalOpen, setIsUploadModalOpen] = useState<boolean>(false);
......@@ -107,6 +114,49 @@ export function DnListView() {
const { buildAdvancedFilter, buildRequestParams } = useAdvancedFilter();
const isSyncingRef = useRef(false);
const isEqual = (a: any, b: any) => a === b || JSON.stringify(a) === JSON.stringify(b);
// 🔁 Sync store -> local
useEffect(() => {
const unsub = usePaginationStore.subscribe((state) => {
const newStoreItems = state.filters[tableKey]?.items ?? [];
const localItems = filterModel.items ?? [];
if (!isEqual(newStoreItems, localItems)) {
isSyncingRef.current = true;
setFilterModel({ items: newStoreItems });
queueMicrotask(() => (isSyncingRef.current = false));
}
});
return () => unsub();
}, [filterModel.items]);
useEffect(() => {
const unsub = usePaginationStore.subscribe((state) => {
const storePage = state.tables[tableKey]?.page ?? 0;
const storePageSize = state.tables[tableKey]?.pageSize ?? 10;
setLocalPagination((prev) =>
prev.page !== storePage || prev.pageSize !== storePageSize
? { page: storePage, pageSize: storePageSize }
: prev
);
});
return () => unsub();
}, []);
// 🔁 Sync local -> store
useEffect(() => {
if (isSyncingRef.current) return;
const currentStore = usePaginationStore.getState().filters[tableKey]?.items ?? [];
if (!isEqual(currentStore, filterModel.items)) {
setFilter(tableKey, { items: filterModel.items });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterModel]);
const params = useMemo(() => {
const advanced = buildAdvancedFilter(filterModel.items);
......@@ -147,17 +197,19 @@ export function DnListView() {
}
}, [rows]);
const throttledPaginationChange = useThrottle((model: GridPaginationModel) => {
if (model.pageSize !== pageSize) {
setPagination(tableKey, { page: 0, pageSize: model.pageSize });
} else {
setPagination(tableKey, { page: model.page });
}
}, 250);
const handlePaginationChange = (model: GridPaginationModel) => {
// Update UI langsung (instan)
setLocalPagination(model);
// Sinkronisasi Zustand (tanpa delay visual)
setPagination(tableKey, {
page: model.page,
pageSize: model.pageSize,
});
};
const debouncedFilterChange = useDebounce((model: GridFilterModel) => {
setFilterModel(model);
resetPagination(tableKey);
}, 400);
const debouncedSortChange = useDebounce((model: GridSortModel) => {
......@@ -165,10 +217,10 @@ export function DnListView() {
resetPagination(tableKey);
}, 400);
const paginationModel: GridPaginationModel = {
page,
pageSize,
};
// const paginationModel: GridPaginationModel = {
// page,
// pageSize,
// };
// ---------- status options and columns (kept identical to your original) ----------
type Status = 'draft' | 'normal' | 'cancelled' | 'amended';
......@@ -281,6 +333,33 @@ export function DnListView() {
}, [apiRef]);
// ---------- memoized toolbar validation (avoid recompute heavy every click) ----------
// const validatedActions = useMemo(() => {
// const dataSelected = dataSelectedRef.current;
// const count = dataSelected.length;
// const hasSelection = count > 0;
// if (!hasSelection) {
// return {
// canDetail: false,
// canEdit: false,
// canDelete: false,
// canUpload: false,
// canReplacement: false,
// canCancel: false,
// };
// }
// const allDraft = dataSelected.every((d) => d.fgStatus === FG_STATUS_DN.DRAFT);
// const allNormal = dataSelected.every((d) => d.fgStatus === FG_STATUS_DN.NORMAL_DONE);
// return {
// canDetail: count === 1,
// canEdit: count === 1 && allDraft,
// canDelete: hasSelection && allDraft,
// canUpload: hasSelection && allDraft,
// canReplacement: count === 1 && dataSelected[0].fgStatus === FG_STATUS_DN.NORMAL_DONE,
// canCancel: hasSelection && allNormal,
// };
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [selectionVersion]);
const validatedActions = useMemo(() => {
const dataSelected = dataSelectedRef.current;
const count = dataSelected.length;
......@@ -295,14 +374,23 @@ export function DnListView() {
canCancel: false,
};
}
const allDraft = dataSelected.every((d) => d.fgStatus === FG_STATUS_DN.DRAFT);
const allNormal = dataSelected.every((d) => d.fgStatus === FG_STATUS_DN.NORMAL_DONE);
// 🟢 ambil data pertama saja, karena canReplacement hanya berlaku kalau count === 1
const first = dataSelected[0];
return {
canDetail: count === 1,
canEdit: count === 1 && allDraft,
canDelete: hasSelection && allDraft,
canUpload: hasSelection && allDraft,
canReplacement: count === 1 && dataSelected[0].fgStatus === FG_STATUS_DN.NORMAL_DONE,
// 🔽 tambahkan logika baru di sini
canReplacement:
count === 1 && first.fgStatus === FG_STATUS_DN.NORMAL_DONE && first.has_draft !== true, // ❌ disable kalau sudah ada draft
canCancel: hasSelection && allNormal,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
......@@ -452,7 +540,7 @@ export function DnListView() {
rowCount={totalRows}
pagination
paginationMode="server"
paginationModel={paginationModel}
paginationModel={localPagination}
initialState={{
pagination: {
paginationModel: {
......@@ -461,15 +549,13 @@ export function DnListView() {
},
},
}}
// onPaginationModelChange={handlePaginationChange}
onPaginationModelChange={throttledPaginationChange}
onPaginationModelChange={handlePaginationChange}
pageSizeOptions={[5, 10, 15, 25, 50, 100]}
filterMode="server"
onFilterModelChange={debouncedFilterChange}
sortingMode="server"
onSortModelChange={debouncedSortChange}
rowSelectionModel={rowSelectionModel}
// onRowSelectionModelChange={handleRowSelectionChange}
onRowSelectionModelChange={throttledSelectionChange}
pinnedColumns={pinnedColumns}
cellSelection
......
// import { create } from 'zustand';
// type TableKey = string;
// interface TablePagination {
// page: number;
// pageSize: number;
// }
// interface PaginationState {
// tables: Record<TableKey, TablePagination>;
// setPagination: (table: TableKey, next: Partial<TablePagination>) => void;
// resetPagination: (table: TableKey) => void;
// }
// export const usePaginationStore = create<PaginationState>((set) => ({
// tables: {},
// setPagination: (table, next) =>
// set((state) => ({
// tables: {
// ...state.tables,
// [table]: {
// page: next.page ?? state.tables[table]?.page ?? 0,
// pageSize: next.pageSize ?? state.tables[table]?.pageSize ?? 10,
// },
// },
// })),
// resetPagination: (table) =>
// set((state) => ({
// tables: {
// ...state.tables,
// [table]: { page: 0, pageSize: state.tables[table]?.pageSize ?? 10 },
// },
// })),
// }));
import { create } from 'zustand';
console.log('✅ pagination store created');
type TableKey = string;
interface TablePagination {
......@@ -43,14 +9,22 @@ interface TablePagination {
pageSize: number;
}
interface TableFilter {
items: any[];
}
interface PaginationState {
tables: Record<TableKey, TablePagination>;
filters: Record<TableKey, TableFilter>;
setPagination: (table: TableKey, next: Partial<TablePagination>) => void;
resetPagination: (table: TableKey) => void;
setFilter: (table: TableKey, next: Partial<TableFilter>) => void;
resetFilter: (table: TableKey) => void;
}
export const usePaginationStore = create<PaginationState>((set) => ({
tables: {},
filters: {},
setPagination: (table, next) =>
set((state) => {
const prev = state.tables[table] ?? { page: 0, pageSize: 10 };
......@@ -71,4 +45,20 @@ export const usePaginationStore = create<PaginationState>((set) => ({
[table]: { page: 0, pageSize: state.tables[table]?.pageSize ?? 10 },
},
})),
setFilter: (table, next) =>
set((state) => ({
filters: {
...state.filters,
[table]: {
items: next.items ?? state.filters[table]?.items ?? [],
},
},
})),
resetFilter: (table) =>
set((state) => ({
filters: {
...state.filters,
[table]: { items: [] },
},
})),
}));
import Button from '@mui/material/Button';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { DashboardContent } from 'src/layouts/dashboard';
import { RouterLink } from 'src/routes/components';
// import { RouterLink } from 'src/routes/components';
import { Link as RouterLink } from 'react-router-dom';
import { paths } from 'src/routes/paths';
import React, { useCallback, useEffect, useMemo, useRef, useState, startTransition } from 'react';
import { useNavigate } from 'react-router';
......@@ -87,12 +88,13 @@ export function NrListView() {
const tableKey = 'nr';
const page = usePaginationStore((s) => s.tables[tableKey]?.page ?? 0);
const pageSize = usePaginationStore((s) => s.tables[tableKey]?.pageSize ?? 10);
const setPagination = usePaginationStore((s) => s.setPagination);
const resetPagination = usePaginationStore((s) => s.resetPagination);
const { tables, filters, setPagination, resetPagination, setFilter } =
usePaginationStore.getState();
const [filterModel, setFilterModel] = useState<GridFilterModel>({
items: filters[tableKey]?.items ?? [],
});
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
const [sortModel, setSortModel] = useState<GridSortModel>([]);
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel | undefined>(
undefined
......@@ -107,19 +109,69 @@ export function NrListView() {
const dataSelectedRef = useRef<any[]>([]);
const [selectionVersion, setSelectionVersion] = useState(0);
const [kodeObjekPajaks, setKodeObjekPajaks] = useState<TKodeObjekPajak[]>([]);
const [localPagination, setLocalPagination] = useState({
page: tables[tableKey]?.page ?? 0,
pageSize: tables[tableKey]?.pageSize ?? 10,
});
const { data: kodeObjekPajak } = useGetKodeObjekPajak();
const signer = useSelector((state: RootState) => state.user.data.signer);
const { buildAdvancedFilter, buildRequestParams } = useAdvancedFilter();
const page = tables[tableKey]?.page ?? 0;
const pageSize = tables[tableKey]?.pageSize ?? 10;
const isSyncingRef = useRef(false);
const isEqual = (a: any, b: any) => a === b || JSON.stringify(a) === JSON.stringify(b);
// 🔁 Sync store -> local
useEffect(() => {
const unsub = usePaginationStore.subscribe((state) => {
const newStoreItems = state.filters[tableKey]?.items ?? [];
const localItems = filterModel.items ?? [];
if (!isEqual(newStoreItems, localItems)) {
isSyncingRef.current = true;
setFilterModel({ items: newStoreItems });
queueMicrotask(() => (isSyncingRef.current = false));
}
});
return () => unsub();
}, [filterModel.items]);
useEffect(() => {
const unsub = usePaginationStore.subscribe((state) => {
const storePage = state.tables[tableKey]?.page ?? 0;
const storePageSize = state.tables[tableKey]?.pageSize ?? 10;
setLocalPagination((prev) =>
prev.page !== storePage || prev.pageSize !== storePageSize
? { page: storePage, pageSize: storePageSize }
: prev
);
});
return () => unsub();
}, []);
// 🔁 Sync local -> store
useEffect(() => {
if (isSyncingRef.current) return;
const currentStore = usePaginationStore.getState().filters[tableKey]?.items ?? [];
if (!isEqual(currentStore, filterModel.items)) {
setFilter(tableKey, { items: filterModel.items });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterModel]);
const params = useMemo(() => {
const advanced = buildAdvancedFilter(filterModel.items);
const baseParams = {
page,
limit: pageSize,
noBupot: '', // tetap pakai noBupot di UI; buildRequestParams akan rename jadi nomorBupot
noBupot: '',
idDipotong: '',
namaDipotong: '',
msPajak: '',
......@@ -128,7 +180,6 @@ export function NrListView() {
sortingMethod: sortModel[0]?.sort ?? '',
};
// buildRequestParams akan menambahkan field advanced dan mengganti noBupot -> nomorBupot
return buildRequestParams(baseParams, advanced);
}, [page, pageSize, sortModel, filterModel.items, buildAdvancedFilter, buildRequestParams]);
......@@ -153,17 +204,19 @@ export function NrListView() {
}
}, [rows]);
const throttledPaginationChange = useThrottle((model: GridPaginationModel) => {
if (model.pageSize !== pageSize) {
setPagination(tableKey, { page: 0, pageSize: model.pageSize });
} else {
setPagination(tableKey, { page: model.page });
}
}, 250);
const handlePaginationChange = (model: GridPaginationModel) => {
// Update UI langsung (instan)
setLocalPagination(model);
// Sinkronisasi Zustand (tanpa delay visual)
setPagination(tableKey, {
page: model.page,
pageSize: model.pageSize,
});
};
const debouncedFilterChange = useDebounce((model: GridFilterModel) => {
setFilterModel(model);
resetPagination(tableKey);
}, 400);
const debouncedSortChange = useDebounce((model: GridSortModel) => {
......@@ -171,11 +224,6 @@ export function NrListView() {
resetPagination(tableKey);
}, 400);
const paginationModel: GridPaginationModel = {
page,
pageSize,
};
// ---------- status options and columns (kept identical to your original) ----------
type Status = 'draft' | 'normal' | 'cancelled' | 'amended';
type StatusOption = { value: Status; label: string };
......@@ -371,6 +419,7 @@ export function NrListView() {
icon: <AutorenewTwoTone sx={{ width: 26, height: 26 }} />,
func: () =>
startTransition(() => {
resetPagination(tableKey);
void refetch();
}),
},
......@@ -459,7 +508,7 @@ export function NrListView() {
{ name: 'e-Bupot Unifikasi Non Residen' },
]}
action={
<Button component={RouterLink} href={paths.unifikasi.nrNew} variant="contained">
<Button component={RouterLink} to={paths.unifikasi.nrNew} variant="contained">
Rekam Data
</Button>
}
......@@ -478,7 +527,7 @@ export function NrListView() {
rowCount={totalRows}
pagination
paginationMode="server"
paginationModel={paginationModel}
paginationModel={localPagination}
initialState={{
pagination: {
paginationModel: {
......@@ -487,15 +536,13 @@ export function NrListView() {
},
},
}}
// onPaginationModelChange={handlePaginationChange}
onPaginationModelChange={throttledPaginationChange}
onPaginationModelChange={handlePaginationChange}
pageSizeOptions={[5, 10, 15, 25, 50, 100]}
filterMode="server"
onFilterModelChange={debouncedFilterChange}
sortingMode="server"
onSortModelChange={debouncedSortChange}
rowSelectionModel={rowSelectionModel}
// onRowSelectionModelChange={handleRowSelectionChange}
onRowSelectionModelChange={throttledSelectionChange}
pinnedColumns={pinnedColumns}
cellSelection
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment