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

Enhanced the Asset Page header layout #9472

Closed
Closed
Changes from all 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
239 changes: 142 additions & 97 deletions src/components/Assets/AssetsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import CountBlock from "@/CAREUI/display/Count";
import CareIcon from "@/CAREUI/icons/CareIcon";
import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover";

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

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 SearchByMultipleFields from "@/components/Common/SearchByMultipleFields";
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 @@ -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",
},
];
Comment on lines +323 to +348
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistency in search option key naming

The asset_qr_id key in searchOptions doesn't match the parameter name qr_code_id used in the params object (line 64). This mismatch could cause search functionality to break.

Apply this fix:

-      key: "asset_qr_id",
+      key: "qr_code_id",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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",
},
];
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: "qr_code_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 })}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider updating search callback to utilize multiple fields

The current onSearch implementation updates a single 'search' query parameter, which doesn't fully utilize the multiple field search capability. Consider updating the search fields individually.

Consider this approach:

-          onSearch={(key, value) => updateQuery({ search: value })}
+          onSearch={(key, value) => updateQuery({ [key]: value })}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSearch={(key, value) => updateQuery({ search: value })}
onSearch={(key, value) => updateQuery({ [key]: value })}

clearSearch={clearSearch}
className="w-full"
/>
</div>
<AssetFilter {...advancedFilter} key={window.location.search} />
{isLoading ? (
Expand Down
Loading