Skip to content

Commit

Permalink
U4X-337 : Make the menu app configurable (#7)
Browse files Browse the repository at this point in the history
* update logo click link

* improve build

* add app menu setup

* app menu configurable
  • Loading branch information
jabahum authored Jan 25, 2024
1 parent 40bf6c2 commit 64a5fb1
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 113 deletions.
145 changes: 67 additions & 78 deletions src/components/app-search-bar/app-search-bar.component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Search } from "@carbon/react";
import styles from "./app-search-bar.scss";
Expand All @@ -16,7 +16,17 @@ import {
User,
Report,
UserActivity,
Grid,
} from "@carbon/react/icons";
import {
AssignedExtension,
ExtensionSlot,
attach,
detachAll,
useConnectedExtensions,
} from "@openmrs/esm-framework";

const appMenuItemSlot = "app-menu-item-slot";

interface AppSearchBarProps {
onChange?: (searchTerm) => void;
Expand All @@ -31,93 +41,72 @@ const AppSearchBar = React.forwardRef<
>(({ onChange, onClear, onSubmit, small }, ref) => {
const { t } = useTranslation();

// items
const openmrsSpaBase = window["getOpenmrsSpaBase"]();

const initialItems = useMemo(() => {
const items = [
{
app: "Data Visualiser",
link: `${openmrsSpaBase}home/data-visualizer`,
icon: <Analytics size={24} />,
},
{
app: "Dispensing ",
link: `${openmrsSpaBase}dispensing`,
icon: <Medication size={24} />,
},
{
app: "Stock Management ",
link: `${openmrsSpaBase}stock-management`,
icon: <Report size={24} />,
},
{
app: "Bed Management ",
link: `${openmrsSpaBase}bed-management`,
icon: <HospitalBed size={24} />,
},
{
app: "Health Exchange ",
link: `${openmrsSpaBase}health-exchange`,
icon: <Db2DataSharingGroup size={24} />,
},
{
app: "Form Builder ",
link: `${openmrsSpaBase}form-builder`,
icon: <DocumentAdd size={24} />,
},
{
app: "Form Render Test ",
link: `${openmrsSpaBase}form-render-test`,
icon: <DocumentImport size={24} />,
},
{
app: "Legacy Admin ",
link: `/openmrs/index.htm`,
icon: <User size={24} />,
},
{
app: "Cohort Builder ",
link: `${openmrsSpaBase}cohort-builder`,
icon: <Events size={24} />,
},
// {
// app: "Theatre ",
// link: `${openmrsSpaBase}theatre`,
// icon: <UserActivity size={24} />,
// },
{
app: "System Info ",
link: `${openmrsSpaBase}about`,
icon: <VolumeFileStorage size={24} />,
},
{
app: "Data Entry Statistics ",
link: `${openmrsSpaBase}statistics`,
icon: <AnalyticsCustom size={24} />,
},
];

return items;
}, [openmrsSpaBase]);
// State for item extensions
const [derivedSlots, setDerivedSlots] = useState<
{ slot: string; extension: string; name: string }[]
>([]);

// State for search term and filtered items
const [searchTerm, setSearchTerm] = useState("");
const [items, setItems] = useState(initialItems);
const [items, setItems] = useState(derivedSlots);

// Fetch item extensions
const menuItemExtensions = useConnectedExtensions(
appMenuItemSlot
) as AssignedExtension[];

// UseEffect for processing item extensions and attaching/detaching slots
useEffect(() => {
// Filter and process extensions
const filteredExtensions = menuItemExtensions
.filter((extension) => Object.keys(extension).length > 0)
.map((extension, index) => ({
slot: `${appMenuItemSlot}-${index}`,
extension: extension.name,
name: extension.meta.name,
}));
setDerivedSlots(filteredExtensions);

// Attach/detach slots
filteredExtensions.forEach(({ slot, extension }) => {
attach(slot, extension);
});

return () => {
filteredExtensions.forEach(({ slot }) => {
detachAll(slot);
});
};
}, [menuItemExtensions]);

// UseEffect for updating items based on derivedSlots
useEffect(() => {
setItems(derivedSlots);
}, [derivedSlots]);

// UseMemo for rendering ExtensionSlots
const extraPanels = useMemo(() => {
return items.map(({ slot }) => <ExtensionSlot key={slot} name={slot} />);
}, [items]);

// Callback for handling search term changes
const handleChange = useCallback(
(val) => {
if (typeof onChange === "function") {
onChange(val);
}
setSearchTerm(val);
const filteredItems = initialItems.filter((item) =>
item.app.toLowerCase().includes(val)

// Filter items based on the search term
const filteredItems = derivedSlots.filter((item) =>
item.name.toLowerCase().includes(val.toLowerCase())
);
setItems(filteredItems);
},
[initialItems, onChange]
[derivedSlots, onChange]
);

// Handle form submission
const handleSubmit = (evt) => {
evt.preventDefault();
onSubmit(searchTerm);
Expand All @@ -126,6 +115,7 @@ const AppSearchBar = React.forwardRef<
return (
<>
<form onSubmit={handleSubmit} className={styles.searchArea}>
{/* Search component */}
<Search
autoFocus
className={styles.appSearchInput}
Expand All @@ -135,17 +125,16 @@ const AppSearchBar = React.forwardRef<
onClear={onClear}
placeholder={t(
"searchForApp",
"Search for a application or module by name"
"Search for an application or module by name"
)}
size={small ? "sm" : "lg"}
value={searchTerm}
ref={ref}
data-testid="appSearchBar"
/>
</form>
<div className={styles.searchItems}>
<MenuItems items={items} />
</div>
{/* Render ExtensionSlots */}
<div className={styles.searchItems}>{extraPanels}</div>
</>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ interface AppSearchOverlayProps {
onClose: () => void;
query?: string;
header?: string;
selectAppAction?: (PatientUuid) => void;
}

const AppSearchOverlay: React.FC<AppSearchOverlayProps> = ({
onClose,
query = "",
header,
selectAppAction,
}) => {
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState(query);
const handleClear = useCallback(() => setSearchTerm(""), [setSearchTerm]);
const showSearchResults = useMemo(() => !!searchTerm?.trim(), [searchTerm]);

useEffect(() => {
if (query) {
Expand Down
2 changes: 1 addition & 1 deletion src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const configSchema = {
},
link: {
_type: Type.String,
_default: "${openmrsSpaBase}/home",
_default: "${openmrsSpaBase}/home/patient-queues",
_description: "The link to redirect to when the logo is clicked",
},
},
Expand Down
37 changes: 15 additions & 22 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import {
defineConfigSchema,
defineExtensionConfigSchema,
getAsyncLifecycle,
getSyncLifecycle,
setupOfflineSync,
} from "@openmrs/esm-framework";
import { Application, navigateToUrl } from "single-spa";
import { configSchema } from "./config-schema";
import { moduleName, userPropertyChange } from "./constants";
import { syncUserLanguagePreference } from "./offline";
import { genericLinkConfigSchema } from "./components/generic-link/generic-link.component";
import userPanelComponent from "./components/user-panel-switcher-item/user-panel-switcher.component";
import changeLanguageLinkComponent from "./components/choose-locale/change-locale.component";
import genericLinkComponent, {
genericLinkConfigSchema,
} from "./components/generic-link/generic-link.component";
import primaryNavRootComponent from "./root.component";
import offlineBannerComponent from "./components/offline-banner/offline-banner.component";

export const importTranslation = require.context(
"../translations",
Expand All @@ -29,10 +35,7 @@ export function startupApp() {
setupOfflineSync(userPropertyChange, [], syncUserLanguagePreference);
}

export const root = getAsyncLifecycle(
() => import("./root.component"),
options
);
export const root = getSyncLifecycle(primaryNavRootComponent, options);

export const redirect: Application = async () => ({
bootstrap: async () =>
Expand All @@ -41,23 +44,13 @@ export const redirect: Application = async () => ({
unmount: async () => undefined,
});

export const userPanel = getAsyncLifecycle(
() =>
import(
"./components/user-panel-switcher-item/user-panel-switcher.component"
),
options
);
export const userPanel = getSyncLifecycle(userPanelComponent, options);

export const localeChanger = getAsyncLifecycle(
() => import("./components/choose-locale/change-locale.component"),
export const localeChanger = getSyncLifecycle(
changeLanguageLinkComponent,
options
);

export const linkComponent = getAsyncLifecycle(
() => import("./components/generic-link/generic-link.component"),
{
featureName: "Link",
moduleName,
}
);
export const linkComponent = getSyncLifecycle(genericLinkComponent, options);

export const offlineBanner = getSyncLifecycle(offlineBannerComponent, options);
8 changes: 5 additions & 3 deletions src/menu/item/item.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ const Item = ({ item }) => {
<ClickableTile
className={styles.customTile}
id="menu-item"
href={item.link}
href={item.meta.link}
>
{item.icon && <div className="customTileTitle">{item.icon}</div>}
{item.app && <div>{item.app}</div>}
{item.meta.icon && (
<div className="customTileTitle">{item.meta.icon}</div>
)}
{item.meta.app && <div>{item.app}</div>}
</ClickableTile>
);
};
Expand Down
12 changes: 7 additions & 5 deletions src/menu/menu.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { Grid, Column } from "@carbon/react";
const MenuItems = ({ items }) => {
return (
<Grid style={{ gap: "2px" }}>
{items.map((item) => (
<Column lg={5} md={5} sm={5} style={{ margin: "2px" }}>
<Item item={item} />
</Column>
))}
{items
.filter((extension) => Object.keys(extension.meta).length > 0)
.map((extension, index) => {
<Column lg={5} md={5} sm={5} style={{ margin: "2px" }}>
<Item key={index} item={extension} />
</Column>;
})}
</Grid>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@
"offline": true
}
]
}
}

0 comments on commit 64a5fb1

Please sign in to comment.