Skip to content

Commit

Permalink
Implements useQuery & request data fetching utilities (used in Lo…
Browse files Browse the repository at this point in the history
…cation Management page) (#6269)

* implement `useQuery` and `useMutation` hooks

* replace usages of `useSelector` with `useAuth` and `useConfig`

* 🧪 refine `useQuery` implementation

* refactor

* implement `useQuery` and `useMutation` hooks

* 🧪 refine `useQuery` implementation

* refactor

* refactor location management

* add paginator

* remove unintended implementations

* Update src/Components/Facility/LocationManagement.tsx

* apply changes based on QA

---------

Co-authored-by: Khavin Shankar <[email protected]>
Co-authored-by: Mohammed Nihal <[email protected]>
  • Loading branch information
3 people authored Sep 14, 2023
1 parent 2956fa9 commit c5f3b79
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 170 deletions.
167 changes: 167 additions & 0 deletions src/CAREUI/misc/PaginatedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { createContext, useContext, useState } from "react";
import { PaginatedResponse, QueryRoute } from "../../Utils/request/types";
import useQuery, { QueryOptions } from "../../Utils/request/useQuery";
import ButtonV2, {
CommonButtonProps,
} from "../../Components/Common/components/ButtonV2";
import CareIcon from "../icons/CareIcon";
import { classNames } from "../../Utils/utils";
import Pagination from "../../Components/Common/Pagination";

const DEFAULT_PER_PAGE_LIMIT = 14;

interface PaginatedListContext<TItem>
extends ReturnType<typeof useQuery<PaginatedResponse<TItem>>> {
items: TItem[];
perPage: number;
currentPage: number;
setPage: (page: number) => void;
}

const context = createContext<PaginatedListContext<object> | null>(null);

function useContextualized<TItem>() {
const ctx = useContext(context);

if (ctx === null) {
throw new Error("PaginatedList must be used within a PaginatedList");
}

return ctx as PaginatedListContext<TItem>;
}

interface Props<TItem> extends QueryOptions {
route: QueryRoute<PaginatedResponse<TItem>>;
perPage?: number;
children: (ctx: PaginatedListContext<TItem>) => JSX.Element | JSX.Element[];
}

export default function PaginatedList<TItem extends object>({
children,
route,
perPage = DEFAULT_PER_PAGE_LIMIT,
...queryOptions
}: Props<TItem>) {
const query = useQuery(route, {
...queryOptions,
query: { ...queryOptions.query, limit: perPage },
});
const [currentPage, setPage] = useState(1);

const items = query.data?.results ?? [];

return (
<context.Provider
value={{ ...query, items, perPage, currentPage, setPage }}
>
<context.Consumer>
{(ctx) => children(ctx as PaginatedListContext<TItem>)}
</context.Consumer>
</context.Provider>
);
}

interface WhenEmptyProps {
className?: string;
children: JSX.Element | JSX.Element[];
}

const WhenEmpty = <TItem extends object>(props: WhenEmptyProps) => {
const { items, loading } = useContextualized<TItem>();

if (loading || items.length > 0) {
return null;
}

return <div className={props.className}>{props.children}</div>;
};

PaginatedList.WhenEmpty = WhenEmpty;

const WhenLoading = <TItem extends object>(props: WhenEmptyProps) => {
const { loading } = useContextualized<TItem>();

if (!loading) {
return null;
}

return <div className={props.className}>{props.children}</div>;
};

PaginatedList.WhenLoading = WhenLoading;

const Refresh = ({ label = "Refresh", ...props }: CommonButtonProps) => {
const { loading, refetch } = useContextualized<object>();

return (
<ButtonV2
variant="secondary"
border
{...props}
onClick={() => refetch()}
disabled={loading}
>
<CareIcon
icon="l-sync"
className={classNames("text-lg", loading && "animate-spin")}
/>
<span>{label}</span>
</ButtonV2>
);
};

PaginatedList.Refresh = Refresh;

interface ItemsProps<TItem> {
className?: string;
children: (item: TItem) => JSX.Element | JSX.Element[];
shimmer?: JSX.Element;
shimmerCount?: number;
}

const Items = <TItem extends object>(props: ItemsProps<TItem>) => {
const { loading, items } = useContextualized<TItem>();

return (
<ul className={props.className}>
{loading && props.shimmer
? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => (
<li key={i} className="w-full">
{props.shimmer}
</li>
))
: items.map((item, index) => (
<li key={index} className="w-full">
{props.children(item)}
</li>
))}
</ul>
);
};

PaginatedList.Items = Items;

interface PaginatorProps {
className?: string;
hideIfSinglePage?: boolean;
}

const Paginator = ({ className, hideIfSinglePage }: PaginatorProps) => {
const { data, perPage, currentPage, setPage } = useContextualized<object>();

if (hideIfSinglePage && (data?.count ?? 0) <= perPage) {
return null;
}

return (
<Pagination
className={className}
cPage={currentPage}
data={{ totalCount: data?.count ?? 0 }}
defaultPerPage={perPage}
onChange={setPage}
/>
);
};

PaginatedList.Paginator = Paginator;
2 changes: 1 addition & 1 deletion src/Components/Common/components/ButtonV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export default ButtonV2;

// Common buttons

type CommonButtonProps = ButtonProps & { label?: string };
export type CommonButtonProps = ButtonProps & { label?: string };

export const Submit = ({ label = "Submit", ...props }: CommonButtonProps) => {
const { t } = useTranslation();
Expand Down
Loading

0 comments on commit c5f3b79

Please sign in to comment.