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

U4X-337 : Make the menu app configurable #7

Merged
Merged
Show file tree
Hide file tree
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
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
}
]
}
}
Loading