Skip to content

Commit

Permalink
Merge pull request #1205 from Isabirye1515/develop
Browse files Browse the repository at this point in the history
updated search functionality
  • Loading branch information
mozzy11 authored Jul 25, 2024
2 parents 50546b6 + cf374f6 commit e495ce5
Show file tree
Hide file tree
Showing 6 changed files with 559 additions and 11 deletions.
28 changes: 17 additions & 11 deletions frontend/src/components/layout/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
} from "@carbon/react";
import SlideOverNotifications from "../notifications/SlideOverNotifications";
import { getFromOpenElisServer, putToOpenElisServer } from "../utils/Utils";

import SearchBar from "./search/searchBar";
function OEHeader(props) {
const { configurationProperties } = useContext(ConfigurationContext);
const { userSessionDetails, logout } = useContext(UserSessionDetailsContext);
Expand All @@ -63,7 +63,7 @@ function OEHeader(props) {
const [showRead, setShowRead] = useState(false);
const [unReadNotifications, setUnReadNotifications] = useState([]);
const [readNotifications, setReadNotifications] = useState([]);

const [searchBar, setSearchBar] = useState(false);
scrollRef.current = window.scrollY;
useLayoutEffect(() => {
window.scrollTo(0, scrollRef.current);
Expand Down Expand Up @@ -177,16 +177,19 @@ function OEHeader(props) {
</>
);
};

const handleSearch = () => {
setSearchBar(!searchBar);
};
const generateMenuItems = (menuItem, index, level, path) => {
if (menuItem.menu.isActive) {
if (level === 0 && menuItem.childMenus.length > 0) {
return (
<React.Fragment key={path}>
<span id={menuItem.menu.elementId}
onClick={(e) => {
setMenuItemExpanded(e, menuItem, path);
}}
<span
id={menuItem.menu.elementId}
onClick={(e) => {
setMenuItemExpanded(e, menuItem, path);
}}
>
<SideNavMenu
aria-label={intl.formatMessage({
Expand Down Expand Up @@ -443,13 +446,16 @@ function OEHeader(props) {
<HeaderGlobalBar>
{userSessionDetails.authenticated && (
<>
{searchBar && <SearchBar />}
<HeaderGlobalAction
aria-label="Search"
onClick={() => {
/*TODO add search functionality*/
}}
onClick={handleSearch}
>
<Search size={20} />
{!searchBar ? (
<Search size={20} />
) : (
<Close size={20} />
)}
</HeaderGlobalAction>
<HeaderGlobalAction
aria-label="Notifications"
Expand Down
93 changes: 93 additions & 0 deletions frontend/src/components/layout/search/searchBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
.main {
position: absolute;
right: 175px;
width: 700px;
}

.search-bar-container {
position: absolute;
display: flex;
width: 430px;
top: 10px;
right: -30px;
box-shadow: 0em 0.1em 0.05em #ccc;
}

.search-input {
width: 100%;
position: relative;
}

.search-bar-container:hover {
border: 2px solid #110f74;
}

.suggestions {
right: -10px;
position: relative;
top: 45px;
}

.patients {
overflow-y: auto;
width: 700px;
max-height: 250px;
position: relative;
background-color: #f0e7e7;
top: 45px;
box-shadow: 0em 0.1em 0.5em #ccc;
}

.patientHead {
width: 680px;
position: relative;
border-bottom: 2px solid #eee3e3;
background-color: #fcfbfb;
height: 52px;
box-shadow: 0em 0.01em 0.05em #ccc;
}
.tags {
margin-left: -100px;
}
.patientHead:hover {
background-color: #bbadad;
cursor: pointer;
}

@media (max-width: 768px) {
.patientHead {
flex-direction: column;
}

.search-bar-container {
flex-direction: column;
align-items: stretch;
}

.search-input {
width: 100%;
}

.suggestions {
width: 100%;
}
}

@media (min-width: 769px) and (max-width: 1024px) {
.patientHead {
flex-direction: row;
}

.search-bar-container {
flex-direction: row;
align-items: center;
}

.search-input {
width: 75%;
}

.suggestions {
width: 75%;
}
}
192 changes: 192 additions & 0 deletions frontend/src/components/layout/search/searchBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import React, { useState } from "react";
import {
Button,
Search,
Tile,
Layer,
Grid,
Column,
Loading,
Tag,
} from "@carbon/react";
import { FormattedMessage, useIntl } from "react-intl";
import SearchOutput from "./searchOutput";
import { fetchPatientData, useAutocomplete } from "./searchService";
import "./searchBar.css";

const SearchBar = (props) => {
const [searchInput, setSearchInput] = useState("");
const [loading, setLoading] = useState(false);
const [patientData, setPatientData] = useState([]);
const intl = useIntl();

const validPatients = patientData.filter(
(patient) => patient.patientID && patient.firstName && patient.lastName,
);

const {
textValue,
filteredSuggestions,
showSuggestions,
onChange: handleAutocompleteChange,
onClick: handleAutocompleteClick,
onKeyDown: handleAutocompleteKeyDown,
activeSuggestion,
onDelete: handleAutocompleteDelete,
setTextValue,
} = useAutocomplete({
value: searchInput,
suggestions: validPatients.map((patient) => ({
id: patient.patientID,
value: `${patient.firstName} ${patient.lastName}`,
})),
allowFreeText: true,
onDelete: (id) => {
setPatientData((prevData) =>
prevData.filter((patient) => patient.patientID !== id),
);
},
});

const handleClearSearch = () => {
setSearchInput("");
setTextValue("");
setPatientData([]);
};

const handleSearch = () => {
if (searchInput.trim()) {
setLoading(true);
fetchPatientData(searchInput.trim(), (results) => {
const uniqueResults = results.filter(
(result, index, self) =>
result.patientID &&
result.firstName &&
result.lastName &&
index === self.findIndex((t) => t.patientID === result.patientID),
);
setPatientData(uniqueResults);
setLoading(false);
});
}
};

const handleChange = (e) => {
const userInput = e?.target?.value || "";
setSearchInput(userInput);
handleAutocompleteChange(e);
setLoading(true);

if (userInput.trim()) {
fetchPatientData(userInput.trim(), (results) => {
const uniqueResults = results.filter(
(result, index, self) =>
result.patientID &&
result.firstName &&
result.lastName &&
index === self.findIndex((t) => t.patientID === result.patientID),
);
setPatientData(uniqueResults);
setLoading(false);
});
} else {
setLoading(false);
}
};

const handleSuggestionClick = (e, id, suggestion) => {
setSearchInput(suggestion.value);
handleAutocompleteClick(e, id, suggestion);
};

const handleSuggestionDelete = (id) => {
handleAutocompleteDelete(id);
};

return (
<Layer className="main">
<Grid>
<Column sm={4} md={6} lg={8} xlg={12}>
<div className="search-bar-container">
<Search
size="sm"
placeholder={intl.formatMessage({ id: "label.button.search" })}
labelText={intl.formatMessage({ id: "label.button.search" })}
closeButtonLabelText={intl.formatMessage({
id: "label.button.clear",
})}
id="searchItem"
value={textValue}
onChange={handleChange}
onKeyDown={handleAutocompleteKeyDown}
onClear={handleClearSearch}
className="search-input"
autoComplete
/>
<Button
size="sm"
style={{ width: 50 }}
onClick={handleSearch}
aria-label={intl.formatMessage({ id: "label.button.search" })}
>
<FormattedMessage id="label.button.search" />
</Button>
</div>
{showSuggestions && (
<ul className="suggestions">
{filteredSuggestions.map((suggestion, index) => (
<p
key={suggestion.id}
className={
index === activeSuggestion ? "suggestion-active" : ""
}
>
<span
onClick={(e) =>
handleSuggestionClick(e, suggestion.id, suggestion)
}
>
{suggestion.value}
</span>
</p>
))}
</ul>
)}
</Column>

<Column sm={4} md={6} lg={8} xlg={12}>
{(loading || patientData.length > 0) && (
<div className="patients">
{loading ? (
<Loading
description={intl.formatMessage({ id: "label.loading" })}
withOverlay={false}
/>
) : (
<>
<div>
<em
style={{
fontFamily: "serif",
color: "#000",
marginLeft: "10px",
}}
>
<FormattedMessage id="sidenav.label.results" />:
</em>{" "}
<Tag size="sm" type="blue">
{patientData.length}
</Tag>
</div>
<SearchOutput loading={loading} patientData={patientData} />
</>
)}
</div>
)}
</Column>
</Grid>
</Layer>
);
};

export default SearchBar;
Loading

0 comments on commit e495ce5

Please sign in to comment.