Skip to content

Commit

Permalink
feat: save/restore filters in URL
Browse files Browse the repository at this point in the history
  • Loading branch information
aldbr committed Apr 8, 2024
1 parent 592d7ff commit 6972417
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 19 deletions.
82 changes: 78 additions & 4 deletions src/components/ui/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import Chip from "@mui/material/Chip";
import Button from "@mui/material/Button";
import SendIcon from "@mui/icons-material/Send";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { usePathname, useRouter, useSearchParams } from "next/navigation";

/**
* Descending comparator function
Expand Down Expand Up @@ -243,7 +244,6 @@ function FilterForm(props: FilterFormProps) {
selectedFilterId,
} = props;
const [tempFilter, setTempFilter] = React.useState<Filter | null>(null);
console.log("selectedFilterId", selectedFilterId);

// Find the index using the filter ID
const filterIndex = filters.findIndex((f) => f.id === selectedFilterId);
Expand Down Expand Up @@ -490,7 +490,7 @@ function FilterToolbar(props: FilterToolbarProps) {
<Button
variant="text"
startIcon={<SendIcon />}
onClick={handleApplyFilters}
onClick={() => handleApplyFilters()}
>
<span>Apply filters</span>
</Button>
Expand Down Expand Up @@ -648,7 +648,7 @@ interface DataTableProps {
setSelected: React.Dispatch<React.SetStateAction<readonly number[]>>;
filters: Filter[];
setFilters: React.Dispatch<React.SetStateAction<Filter[]>>;
handleApplyFilters: () => void;
setSearchBody: (searchBody: any) => void;
columns: HeadCell[];
rows: any[];
error: string | null;
Expand All @@ -674,7 +674,7 @@ export function DataTable(props: DataTableProps) {
setSelected,
filters,
setFilters,
handleApplyFilters,
setSearchBody,
columns,
rows,
error,
Expand All @@ -692,6 +692,80 @@ export function DataTable(props: DataTableProps) {
mouseY: number | null;
id: number | null;
}>({ mouseX: null, mouseY: null, id: null });
// NextJS router and params
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

// Manage URL search params
const createQueryString = React.useCallback(
(filters: Filter[]) => {
const params = new URLSearchParams(searchParams.toString());
// Clear existing filters
params.delete("filter");

// Append new filters
filters.forEach((filter) => {
params.append(
"filter",
`${filter.id}_${filter.column}_${filter.operator}_${filter.value}`,
);
});

return params.toString();
},
[searchParams],
);

const updateFiltersAndUrl = React.useCallback(
(newFilters: Filter[]) => {
// Generate the new query string with all filters
const queryString = createQueryString(newFilters);

// Push new URL to history without reloading the page
router.push(pathname + "?" + queryString);
},
[createQueryString, pathname, router],
);

// Handle the application of filters
const handleApplyFilters = () => {
// Transform list of filters into a json object
const jsonFilters = filters.map((filter) => ({
parameter: filter.column,
operator: filter.operator,
value: filter.value,
}));
setSearchBody({ search: jsonFilters });

// Update the filters in the URL
updateFiltersAndUrl(filters);
};

React.useEffect(() => {
// Function to parse the filters from the URL search params
const parseFiltersFromUrl = () => {
const filterStrings = searchParams.getAll("filter");
return filterStrings.map((filterString: string) => {
const [id, column, operator, value] = filterString.split("_");
return { id: Number(id), column, operator, value };
});
};

if (searchParams.has("filter")) {
// Parse the filters when the component mounts or when the searchParams change
const initialFilters = parseFiltersFromUrl();
// Set the filters (they will be displayed in the UI)
setFilters(initialFilters);
// Apply the filters to get the filtered data
const jsonFilters = initialFilters.map((filter) => ({
parameter: filter.column,
operator: filter.operator,
value: filter.value,
}));
setSearchBody({ search: jsonFilters });
}
}, [searchParams, setFilters, setSearchBody]);

// Manage sorting
const handleRequestSort = (
Expand Down
15 changes: 1 addition & 14 deletions src/components/ui/JobDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,19 +197,6 @@ export function JobDataTable() {
const columns = isMobile ? mobileHeadCells : headCells;
const clearSelected = () => setSelected([]);

/**
* Handle the application of filters
*/
const handleApplyFilters = () => {
// Transform list of filters into a json objects
const jsonFilters = filters.map((filter) => ({
parameter: filter.column,
operator: filter.operator,
value: filter.value,
}));
setSearchBody({ search: jsonFilters });
};

/**
* Handle the deletion of the selected jobs
*/
Expand Down Expand Up @@ -413,7 +400,7 @@ export function JobDataTable() {
setSelected={setSelected}
filters={filters}
setFilters={setFilters}
handleApplyFilters={handleApplyFilters}
setSearchBody={setSearchBody}
columns={columns}
rows={data}
error={error}
Expand Down
17 changes: 16 additions & 1 deletion test/unit-tests/JobDataTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ import { JobDataTable } from "@/components/ui/JobDataTable";
import useSWR from "swr";
import { useOidcAccessToken } from "@axa-fr/react-oidc";

// Mock the module
// Mock modules
jest.mock("@axa-fr/react-oidc", () => ({
useOidcAccessToken: jest.fn(),
}));

jest.mock("next/navigation", () => {
return {
usePathname: () => ({
pathname: "",
}),
useRouter: () => ({
push: jest.fn(),
}),
useSearchParams: () => ({
has: () => false,
getAll: () => [],
}),
};
});

jest.mock("swr", () => jest.fn());

describe("<JobDataTable />", () => {
Expand Down

0 comments on commit 6972417

Please sign in to comment.