-
Notifications
You must be signed in to change notification settings - Fork 543
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
Changes from all commits
778f379
f601b89
1832378
4d9be38
6e57357
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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"; | ||||||
|
||||||
|
@@ -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); | ||||||
|
@@ -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 || "", | ||||||
|
@@ -114,6 +120,9 @@ const AssetsList = () => { | |||||
}, | ||||||
); | ||||||
|
||||||
const authUser = useAuthUser(); | ||||||
const isDisabled = !NonReadOnlyUsers(authUser.user_type); | ||||||
|
||||||
function isValidURL(url: string) { | ||||||
try { | ||||||
new URL(url); | ||||||
|
@@ -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`); | ||||||
|
@@ -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 })} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
|
||||||
clearSearch={clearSearch} | ||||||
className="w-full" | ||||||
/> | ||||||
</div> | ||||||
<AssetFilter {...advancedFilter} key={window.location.search} /> | ||||||
{isLoading ? ( | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix inconsistency in search option key naming
The
asset_qr_id
key in searchOptions doesn't match the parameter nameqr_code_id
used in the params object (line 64). This mismatch could cause search functionality to break.Apply this fix:
📝 Committable suggestion