mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix: add draft useUrlQuery and useQueryParam hooks
This commit is contained in:
parent
0db40680dc
commit
bc1d75af88
@ -43,7 +43,7 @@ export const Categories = () => {
|
||||
const { toast } = useToast();
|
||||
const dialogs = useDialogs();
|
||||
const hasPermission = useHasPermission();
|
||||
const { onSearch, searchPayload } = useSearch<ICategory>({
|
||||
const { searchPayload, textFieldProps } = useSearch<ICategory>({
|
||||
$iLike: ["label"],
|
||||
});
|
||||
const { dataGridProps } = useFind(
|
||||
@ -142,7 +142,7 @@ export const Categories = () => {
|
||||
width="max-content"
|
||||
>
|
||||
<Grid item>
|
||||
<FilterTextfield onChange={onSearch} />
|
||||
<FilterTextfield {...textFieldProps} />
|
||||
</Grid>
|
||||
{hasPermission(EntityType.CATEGORY, PermissionAction.CREATE) ? (
|
||||
<Grid item>
|
||||
|
@ -6,13 +6,15 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { MainContainer, Search, Sidebar } from "@chatscope/chat-ui-kit-react";
|
||||
import { MainContainer, Sidebar } from "@chatscope/chat-ui-kit-react";
|
||||
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
|
||||
import { Grid, MenuItem } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
import AutoCompleteEntitySelect from "@/app-components/inputs/AutoCompleteEntitySelect";
|
||||
import { FilterTextfield } from "@/app-components/inputs/FilterTextfield";
|
||||
import { Input } from "@/app-components/inputs/Input";
|
||||
import { useQueryParam } from "@/hooks/useQueryParam";
|
||||
import { useSearch } from "@/hooks/useSearch";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { EntityType, Format } from "@/services/types";
|
||||
@ -26,11 +28,16 @@ import { AssignedTo } from "./types";
|
||||
|
||||
export const Inbox = () => {
|
||||
const { t } = useTranslate();
|
||||
const { onSearch, searchPayload, searchText } = useSearch<ISubscriber>({
|
||||
const [assignment, setAssignment] = useState<AssignedTo>(AssignedTo.ALL);
|
||||
const { searchPayload, textFieldProps } = useSearch<ISubscriber>({
|
||||
$or: ["first_name", "last_name"],
|
||||
});
|
||||
const [channels, setChannels] = useState<string[]>([]);
|
||||
const [assignment, setAssignment] = useState<AssignedTo>(AssignedTo.ALL);
|
||||
|
||||
useQueryParam("assigned_to", assignment, AssignedTo.ALL, setAssignment);
|
||||
useQueryParam("channel", channels, [], () => {
|
||||
// setChannels((channels) => [...channels, channel] as any);
|
||||
});
|
||||
|
||||
return (
|
||||
<ChatProvider>
|
||||
@ -46,14 +53,8 @@ export const Inbox = () => {
|
||||
<Grid item width="100%" height="100%" overflow="hidden">
|
||||
<MainContainer style={{ height: "100%" }}>
|
||||
<Sidebar position="left">
|
||||
<Grid paddingX={1} paddingTop={1}>
|
||||
<Search
|
||||
value={searchText}
|
||||
onClearClick={() => onSearch("")}
|
||||
className="changeColor"
|
||||
onChange={(v) => onSearch(v)}
|
||||
placeholder="Search..."
|
||||
/>
|
||||
<Grid paddingX={1} pt={2} pb={1} mx={1}>
|
||||
<FilterTextfield placeholder="Search..." {...textFieldProps} />
|
||||
</Grid>
|
||||
<Grid
|
||||
display="flex"
|
||||
@ -80,7 +81,7 @@ export const Inbox = () => {
|
||||
onChange={(e) => setAssignment(e.target.value as AssignedTo)}
|
||||
label={t("label.assigned_to")}
|
||||
select
|
||||
defaultValue={AssignedTo.ALL}
|
||||
value={assignment}
|
||||
sx={{ marginTop: 1 }}
|
||||
>
|
||||
<MenuItem value={AssignedTo.ALL}>All</MenuItem>
|
||||
|
50
frontend/src/hooks/useQueryParam.ts
Normal file
50
frontend/src/hooks/useQueryParam.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { QueryParamCallback, useUrlQuery } from "./useUrlQuery";
|
||||
|
||||
export const useQueryParam = <
|
||||
T extends string | string[] | undefined = string | undefined,
|
||||
>(
|
||||
key: string,
|
||||
value: T,
|
||||
defaultValue: T,
|
||||
callback?: QueryParamCallback<T>,
|
||||
) => {
|
||||
const router = useRouter();
|
||||
const { queryParams, setQueryParam } = useUrlQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
setQueryParam(key, value, defaultValue);
|
||||
}
|
||||
}, [value, router.isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (callback && router.isReady) {
|
||||
if (Array.isArray(defaultValue)) {
|
||||
if (typeof queryParams[key] === "string") {
|
||||
callback([queryParams[key]] as T);
|
||||
} else {
|
||||
if (value?.length === 0) {
|
||||
} else if (
|
||||
value?.length &&
|
||||
value?.length !== queryParams[key]?.length
|
||||
) {
|
||||
callback((queryParams[key] || defaultValue) as T);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callback((queryParams[key] || defaultValue) as T);
|
||||
}
|
||||
}
|
||||
}, [key, queryParams[key], router.isReady]);
|
||||
};
|
@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
import { debounce } from "@mui/material";
|
||||
import { useRouter } from "next/router";
|
||||
import { ChangeEvent, useCallback, useEffect, useState } from "react";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
|
||||
import {
|
||||
TBuildInitialParamProps,
|
||||
@ -16,6 +15,9 @@ import {
|
||||
TParamItem,
|
||||
} from "@/types/search.types";
|
||||
|
||||
import { useQueryParam } from "./useQueryParam";
|
||||
import { useUrlQuery } from "./useUrlQuery";
|
||||
|
||||
const buildOrParams = <T,>({ params, searchText }: TBuildParamProps<T>) => ({
|
||||
or: params?.map((field) => ({
|
||||
[field]: { contains: searchText },
|
||||
@ -53,48 +55,29 @@ const buildNeqInitialParams = <T,>({
|
||||
);
|
||||
|
||||
export const useSearch = <T,>(params: TParamItem<T>) => {
|
||||
const router = useRouter();
|
||||
const { getQueryParam } = useUrlQuery();
|
||||
const [searchText, setSearchText] = useState<string>(
|
||||
(router.query.search as string) || "",
|
||||
!!getQueryParam("search") ? String(getQueryParam("search")) : "",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.query.search !== searchText) {
|
||||
setSearchText((router.query.search as string) || "");
|
||||
}
|
||||
}, [router.query.search]);
|
||||
|
||||
const updateQueryParams = useCallback(
|
||||
debounce(async (newSearchText: string) => {
|
||||
await router.replace(
|
||||
{
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, search: newSearchText || undefined },
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true },
|
||||
);
|
||||
}, 300),
|
||||
[router],
|
||||
);
|
||||
const onSearch = (
|
||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | string,
|
||||
) => {
|
||||
const newSearchText = typeof e === "string" ? e : e.target.value;
|
||||
|
||||
setSearchText(newSearchText);
|
||||
updateQueryParams(newSearchText);
|
||||
};
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
const {
|
||||
$eq: eqInitialParams,
|
||||
$iLike: iLikeParams,
|
||||
$neq: neqInitialParams,
|
||||
$or: orParams,
|
||||
$iLike: iLikeParams,
|
||||
} = params;
|
||||
const onSearch = debounce(
|
||||
(e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | string) => {
|
||||
setSearchText(typeof e === "string" ? e : e.target.value);
|
||||
},
|
||||
300,
|
||||
);
|
||||
|
||||
useQueryParam("search", searchText, "", setSearchText);
|
||||
|
||||
return {
|
||||
searchText,
|
||||
onSearch,
|
||||
searchText,
|
||||
searchPayload: {
|
||||
where: {
|
||||
...buildEqInitialParams({ initialParams: eqInitialParams }),
|
||||
@ -105,5 +88,15 @@ export const useSearch = <T,>(params: TParamItem<T>) => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
textFieldProps: {
|
||||
value: isActive ? undefined : searchText,
|
||||
onChange: onSearch,
|
||||
onMouseOver: () => {
|
||||
setIsActive(true);
|
||||
},
|
||||
onMouseLeave: () => {
|
||||
setIsActive(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
84
frontend/src/hooks/useUrlQuery.ts
Normal file
84
frontend/src/hooks/useUrlQuery.ts
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
export type QueryParams = Record<string, string | string[] | undefined>;
|
||||
export type QueryParamCallback<T> = (value: T) => void;
|
||||
|
||||
export const useUrlQuery = () => {
|
||||
const router = useRouter();
|
||||
const { pathname, query } = router;
|
||||
const queryParams: QueryParams = useMemo(() => {
|
||||
return { ...query };
|
||||
}, [query]);
|
||||
const updateUrl = useCallback(
|
||||
async (newParams: QueryParams, defaultValue?: string | string[]) => {
|
||||
const updatedQuery: QueryParams = { ...query };
|
||||
|
||||
Object.entries(newParams).forEach(([key, value]) => {
|
||||
if (value === defaultValue) {
|
||||
// delete updatedQuery[key];
|
||||
} else {
|
||||
updatedQuery[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
await router.push(
|
||||
{
|
||||
pathname,
|
||||
query: updatedQuery,
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true },
|
||||
);
|
||||
},
|
||||
[pathname, query, router],
|
||||
);
|
||||
const setQueryParam = useCallback(
|
||||
async (
|
||||
key: string,
|
||||
value: string | string[] | undefined,
|
||||
defaultValue?: string | string[],
|
||||
) => {
|
||||
await updateUrl({ [key]: value }, defaultValue);
|
||||
},
|
||||
[updateUrl],
|
||||
);
|
||||
const removeQueryParam = useCallback(
|
||||
(key: string) => {
|
||||
updateUrl({ [key]: undefined });
|
||||
},
|
||||
[updateUrl],
|
||||
);
|
||||
const getQueryParam = useCallback(
|
||||
(key: string): string | string[] | undefined => {
|
||||
return queryParams[key];
|
||||
},
|
||||
[queryParams],
|
||||
);
|
||||
const clearQueryParams = useCallback(async () => {
|
||||
await router.push(
|
||||
{
|
||||
pathname,
|
||||
query: {},
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true },
|
||||
);
|
||||
}, [pathname, router]);
|
||||
|
||||
return {
|
||||
queryParams,
|
||||
setQueryParam,
|
||||
getQueryParam,
|
||||
removeQueryParam,
|
||||
clearQueryParams,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user