Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Update the button layout in Assets Page #9392

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/CAREUI/interactive/FiltersSlideover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next";
import CareIcon from "@/CAREUI/icons/CareIcon";
import SlideOver from "@/CAREUI/interactive/SlideOver";

import { Button } from "@/components/ui/button";

import ButtonV2 from "@/components/Common/ButtonV2";

import useFilters from "@/hooks/useFilters";
Expand Down Expand Up @@ -58,15 +60,15 @@ export default function FiltersSlideover({
export const AdvancedFilterButton = ({ onClick }: { onClick: () => void }) => {
const { t } = useTranslation();
return (
<ButtonV2
ghost
border
className="w-full bg-white md:w-auto"
<Button
variant={"outline_primary"}
size={"lg"}
className="w-full bg-white md:w-auto gap-2"
onClick={onClick}
id="advanced-filter"
>
<CareIcon icon="l-filter" />
<span className="py-0.5">{t("advanced_filters")}</span>
</ButtonV2>
</Button>
);
};
239 changes: 142 additions & 97 deletions src/components/Assets/AssetsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover";
import AssetFilter from "@/components/Assets/AssetFilter";
import AssetImportModal from "@/components/Assets/AssetImportModal";
import { AssetData, assetClassProps } from "@/components/Assets/AssetTypes";
import ButtonV2 from "@/components/Common/ButtonV2";
import ExportMenu from "@/components/Common/Export";
import Loading from "@/components/Common/Loading";
import Page from "@/components/Common/Page";
import FacilitiesSelectDialogue from "@/components/ExternalResult/FacilitiesSelectDialogue";
import { FacilityModel } from "@/components/Facility/models";
import SearchInput from "@/components/Form/SearchInput";

import useAuthUser from "@/hooks/useAuthUser";
import useFilters from "@/hooks/useFilters";
import { useIsAuthorized } from "@/hooks/useIsAuthorized";

Expand All @@ -29,6 +28,9 @@ import routes from "@/Utils/request/api";
import request from "@/Utils/request/request";
import useTanStackQueryInstead from "@/Utils/request/useQuery";

import SearchByMultipleFields from "../Common/SearchByMultipleFields";
import { Button } from "../ui/button";
rithviknishad marked this conversation as resolved.
Show resolved Hide resolved

const AssetsList = () => {
const { t } = useTranslation();
const {
Expand All @@ -38,9 +40,10 @@ const AssetsList = () => {
FilterBadges,
advancedFilter,
resultsPerPage,
clearSearch,
} = useFilters({
limit: 18,
cacheBlacklist: ["search"],
cacheBlacklist: ["name", "serial_number", "qr_code_id"],
});
const [assets, setAssets] = useState([{} as AssetData]);
const [isLoading, setIsLoading] = useState(false);
Expand All @@ -56,6 +59,9 @@ const AssetsList = () => {
const params = {
limit: resultsPerPage,
page: qParams.page,
name: qParams.name || "",
serial_number: qParams.serial_number || "",
qr_code_id: qParams.qr_code_id || "",
offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage,
search_text: qParams.search || "",
facility: qParams.facility || "",
Expand Down Expand Up @@ -114,6 +120,9 @@ const AssetsList = () => {
},
);

const authUser = useAuthUser();
const isDisabled = !NonReadOnlyUsers(authUser.user_type);

function isValidURL(url: string) {
try {
new URL(url);
Expand Down Expand Up @@ -311,108 +320,50 @@ const AssetsList = () => {
);
}

const searchOptions = [
{
key: "name",
label: "Name",
type: "text" as const,
placeholder: "Search by Name",
value: qParams.name || "",
shortcutKey: "n",
},
{
key: "serial_number",
label: "Serial No.",
type: "text" as const,
placeholder: "Search by Serial No.",
value: qParams.serial_number || "",
shortcutKey: "u",
},
{
key: "asset_qr_id",
label: "QR Code ID",
type: "text" as const,
placeholder: "Search by QR Code ID",
value: qParams.qr_code_id || "",
shortcutKey: "p",
},
];

return (
<Page
title="Assets"
breadcrumbs={false}
className="px-4 md:px-6"
hideBack
options={
<>
{authorizedForImportExport && (
<div className="tooltip" data-testid="import-asset-button">
<ExportMenu
label={importAssetModalOpen ? "Importing..." : "Import/Export"}
exportItems={[
{
label: "Import Assets",
options: {
icon: (
<CareIcon
icon="l-import"
className="import-assets-button"
/>
),
onClick: () => setImportAssetModalOpen(true),
},
},
{
label: "Export Assets (JSON)",
action: async () => {
const { data } = await request(routes.listAssets, {
query: { ...qParams, json: true, limit: totalCount },
});
return data ?? null;
},
type: "json",
filePrefix: `assets_${facility?.name ?? "all"}`,
options: {
icon: <CareIcon icon="l-export" />,
disabled: totalCount === 0 || !authorizedForImportExport,
id: "export-json-option",
},
},
{
label: "Export Assets (CSV)",
action: async () => {
const { data } = await request(routes.listAssets, {
query: { ...qParams, csv: true, limit: totalCount },
});
return data ?? null;
},
type: "csv",
filePrefix: `assets_${facility?.name ?? "all"}`,
options: {
icon: <CareIcon icon="l-export" />,
disabled: totalCount === 0 || !authorizedForImportExport,
id: "export-csv-option",
},
},
]}
/>
</div>
)}
</>
}
>
<div className="mt-5 gap-3 space-y-2 lg:flex">
<CountBlock
text="Total Assets"
count={totalCount}
loading={loading}
icon="d-folder"
className="flex-1"
/>
<div className="flex-1">
<SearchInput
id="asset-search"
name="search"
value={qParams.search}
onChange={(e) => updateQuery({ [e.name]: e.value })}
placeholder="Search by name/serial no./QR code ID"
/>
</div>
<div className="flex flex-col items-start justify-start gap-2 lg:ml-2">
<div className="flex w-full flex-col gap-2 md:flex-row lg:w-auto">
<div className="w-full">
<AdvancedFilterButton
onClick={() => advancedFilter.setShow(true)}
/>
</div>
<ButtonV2
className="w-full py-[11px]"
onClick={() => setIsScannerActive(true)}
>
<CareIcon icon="l-search" className="mr-1 text-base" /> Scan Asset
QR
</ButtonV2>
</div>
<div className="flex w-full flex-col items-center justify-between lg:flex-row">
<div
className="flex w-full flex-col md:flex-row"
className="mb-2 flex w-full flex-col items-center lg:mb-0 lg:w-fit lg:flex-row lg:gap-5"
data-testid="create-asset-buttom"
>
<ButtonV2
authorizeFor={NonReadOnlyUsers}
className="inline-flex w-full items-center justify-center"
<Button
disabled={isDisabled}
variant={"primary"}
size={"lg"}
className="gap-2 w-full lg:w-fit"
onClick={() => {
if (qParams.facility) {
navigate(`/facility/${qParams.facility}/assets/new`);
Expand All @@ -423,9 +374,103 @@ const AssetsList = () => {
>
<CareIcon icon="l-plus-circle" className="text-lg" />
<span>{t("create_asset")}</span>
</ButtonV2>
</Button>
</div>
<div className="flex w-full flex-col items-center justify-end gap-2 lg:ml-3 lg:w-fit lg:flex-row lg:gap-3">
<AdvancedFilterButton
onClick={() => advancedFilter.setShow(true)}
/>

<Button
variant={"primary"}
size={"lg"}
className="gap-2 w-full"
onClick={() => setIsScannerActive(true)}
>
<CareIcon icon="l-search" className="mr-1 text-base" />
Scan Asset QR
</Button>

<div
className="tooltip w-full md:w-auto"
data-testid="import-asset-button"
>
{authorizedForImportExport && (
<ExportMenu
label={
importAssetModalOpen ? "Importing..." : "Import/Export"
}
exportItems={[
{
label: "Import Assets",
options: {
icon: (
<CareIcon
icon="l-import"
className="import-assets-button mr-5 w-full lg:w-fit"
/>
),
onClick: () => setImportAssetModalOpen(true),
},
},
{
label: "Export Assets (JSON)",
action: async () => {
const { data } = await request(routes.listAssets, {
query: { ...qParams, json: true, limit: totalCount },
});
return data ?? null;
},
type: "json",
filePrefix: `assets_${facility?.name ?? "all"}`,
options: {
icon: <CareIcon icon="l-export" />,
disabled:
totalCount === 0 || !authorizedForImportExport,
id: "export-json-option",
},
},
{
label: "Export Assets (CSV)",
action: async () => {
const { data } = await request(routes.listAssets, {
query: { ...qParams, csv: true, limit: totalCount },
});
return data ?? null;
},
type: "csv",
filePrefix: `assets_${facility?.name ?? "all"}`,
options: {
icon: <CareIcon icon="l-export" />,
disabled:
totalCount === 0 || !authorizedForImportExport,
id: "export-csv-option",
},
},
]}
/>
)}
</div>
</div>
</div>
}
>
<div className="mt-4 gap-4 lg:gap-16 flex flex-col lg:flex-row lg:items-center">
<CountBlock
text="Total Assets"
count={totalCount}
loading={loading}
icon="d-folder"
className="flex-1"
/>

<SearchByMultipleFields
id="asset-search"
options={searchOptions}
onSearch={(key, value) => updateQuery({ search: value })}
clearSearch={clearSearch}
className="w-full"
/>
</div>
<AssetFilter {...advancedFilter} key={window.location.search} />
{isLoading ? (
Expand Down
Loading
Loading