Skip to content

Commit

Permalink
refactor location management
Browse files Browse the repository at this point in the history
  • Loading branch information
rithviknishad committed Sep 12, 2023
1 parent 9757218 commit 4d0e3c4
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 97 deletions.
137 changes: 137 additions & 0 deletions src/CAREUI/misc/PaginatedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { createContext, useContext } from "react";
import { QueryRoute } from "../../Utils/request/types";
import { PaginatedResponse } from "../../Utils/request/usePaginatedQuery";
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";

const DEFAULT_PER_PAGE_LIMIT = 14;

interface PaginatedListContext<TItem>
extends ReturnType<typeof useQuery<PaginatedResponse<TItem>>> {
items: TItem[];
perPage: number;
}

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 items = query.data?.results ?? [];

return (
<context.Provider value={{ ...query, items, perPage }}>
<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;
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
161 changes: 69 additions & 92 deletions src/Components/Facility/LocationManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,109 +3,86 @@ import ButtonV2 from "../Common/components/ButtonV2";
import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
import CareIcon from "../../CAREUI/icons/CareIcon";
import Page from "../Common/components/Page";
import useQuery from "../../Utils/request/useQuery";
import routes from "../../Redux/api";
import PaginatedList from "../../CAREUI/misc/PaginatedList";
import { LocationModel } from "./models";

const Loading = lazy(() => import("../Common/Loading"));

interface LocationRowProps {
id: string;
interface Props {
facilityId: string;
name: string;
description: string;
}

const LocationRow = (props: LocationRowProps) => {
const { id, facilityId, name, description } = props;

export default function LocationManagement({ facilityId }: Props) {
return (
<div
key={id}
className="w-full items-center justify-between border-b py-4 lg:flex"
<PaginatedList
route={routes.listFacilityAssetLocation}
pathParams={{ facility_external_id: facilityId }}
>
<div className="px-4 lg:w-3/4">
<div className="w-full items-baseline lg:flex">
<p className="break-words text-xl lg:mr-4 lg:w-1/4">{name}</p>
<p className="break-all text-sm lg:w-3/4">{description}</p>
</div>
</div>
<div className="mt-4 flex flex-col gap-2 lg:mt-0 lg:flex-row">
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`/facility/${facilityId}/location/${id}/update`}
authorizeFor={NonReadOnlyUsers}
>
<CareIcon className="care-l-pen text-lg" />
Edit
</ButtonV2>
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`/facility/${facilityId}/location/${id}/beds`}
>
<CareIcon className="care-l-bed text-lg" />
Manage Beds
</ButtonV2>
</div>
</div>
);
};
{() => (
<Page
title="Location Management"
backUrl={`/facility/${facilityId}`}
options={
<div className="flex justify-end gap-2">
<PaginatedList.Refresh />

export const LocationManagement = ({ facilityId }: { facilityId: string }) => {
const { data, loading } = useQuery(routes.listFacilityAssetLocation, {
query: { limit: "14", offset: "0" },
pathParams: { facility_external_id: facilityId },
});

console.log(data);
<ButtonV2
href={`/facility/${facilityId}/location/add`}
authorizeFor={NonReadOnlyUsers}
>
<CareIcon icon="l-plus" className="text-lg" />
Add New Location
</ButtonV2>
</div>
}
>
<PaginatedList.WhenEmpty className="flex w-full justify-center border-b border-gray-200 bg-white p-5 text-center text-2xl font-bold text-gray-500">
<span>No locations available</span>
</PaginatedList.WhenEmpty>

if (loading) {
return <Loading />;
}
<PaginatedList.WhenLoading>
<Loading />
</PaginatedList.WhenLoading>

return (
<Page
title="Location Management"
crumbsReplacements={{
[facilityId]: { name: data?.results[0]?.facility?.name || "" },
}}
backUrl={`/facility/${facilityId}`}
>
<div className="container mx-auto px-4 py-2 sm:px-8">
<div className="flex justify-end">
<ButtonV2
href={`/facility/${facilityId}/location/add`}
// hello
authorizeFor={NonReadOnlyUsers}
>
<CareIcon className="care-l-plus text-lg" />
Add New Location
</ButtonV2>
</div>
<PaginatedList.Items<LocationModel> className="m-8 flex grow flex-col gap-3">
{(item) => <Location {...item} />}
</PaginatedList.Items>
</Page>
)}
</PaginatedList>
);
}

{loading ? (
<Loading />
) : data?.results.length ? (
<div className="mt-5 flex grow flex-wrap bg-white p-4">
{data?.results.map((item) => (
<LocationRow
key={item.id}
id={item.id || ""}
facilityId={facilityId}
name={item.name || ""}
description={item.description || ""}
/>
))}
</div>
) : (
<p className="flex w-full justify-center border-b border-gray-200 bg-white p-5 text-center text-2xl font-bold text-gray-500">
No locations available
</p>
)}
const Location = ({ name, description, id }: LocationModel) => (
<div className="w-full items-center justify-between rounded border border-gray-300 bg-white p-6 shadow-sm transition-all duration-200 ease-in-out hover:border-primary-400 lg:flex">
<div className="lg:w-3/4">
<div className="w-full items-baseline lg:flex lg:items-center">
<p className="break-words text-xl lg:mr-4 lg:w-1/4">{name}</p>
<p className="break-all text-sm lg:w-3/4">{description}</p>
</div>
</Page>
);
};
</div>

<div className="mt-4 flex flex-col gap-2 lg:mt-0 lg:flex-row">
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`location/${id}/update`}
authorizeFor={NonReadOnlyUsers}
>
<CareIcon className="care-l-pen text-lg" />
Edit
</ButtonV2>
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`location/${id}/beds`}
>
<CareIcon className="care-l-bed text-lg" />
Manage Beds
</ButtonV2>
</div>
</div>
);
2 changes: 1 addition & 1 deletion src/Router/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import ShowPushNotification from "../Components/Notifications/ShowPushNotificati
import { NoticeBoard } from "../Components/Notifications/NoticeBoard";
import { AddLocationForm } from "../Components/Facility/AddLocationForm";
import { AddBedForm } from "../Components/Facility/AddBedForm";
import { LocationManagement } from "../Components/Facility/LocationManagement";
import LocationManagement from "../Components/Facility/LocationManagement";
import { BedManagement } from "../Components/Facility/BedManagement";
import AssetsList from "../Components/Assets/AssetsList";
import AssetManage from "../Components/Assets/AssetManage";
Expand Down
4 changes: 2 additions & 2 deletions src/Utils/request/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { QueryRoute, RequestOptions } from "./types";
import request from "./request";
import { mergeRequestOptions } from "./utils";

interface QueryOptions extends RequestOptions {
export interface QueryOptions extends RequestOptions {
prefetch?: boolean;
refetchOnWindowFocus?: boolean;
}
Expand Down Expand Up @@ -57,7 +57,7 @@ export default function useQuery<TData>(
}, [runQuery, options?.prefetch]);

useEffect(() => {
if (options?.refetchOnWindowFocus ?? true) {
if (options?.refetchOnWindowFocus) {
const onFocus = () => runQuery();

window.addEventListener("focus", onFocus);
Expand Down
2 changes: 1 addition & 1 deletion src/Utils/request/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function makeUrl(
pathParams?: Record<string, string>
) {
if (pathParams) {
Object.entries(pathParams).reduce(
path = Object.entries(pathParams).reduce(
(acc, [key, value]) => acc.replace(`{${key}}`, value),
path
);
Expand Down

0 comments on commit 4d0e3c4

Please sign in to comment.