Skip to content

Commit

Permalink
Phani | BAH-3117 | Create React components for OT Notes Save, Edit & …
Browse files Browse the repository at this point in the history
…Delete Popups using carbon components (#954)

* Phani | Include next-ui in OT Moulde

* Phani | Create Notes Save & Delete popups

* Phani | Add update flow & integrate BE APIs

* Phani | Fix bug in delete flow

* Phani | Remove unused Angular code

* Phani | Move notes save & delete components into components directory

* Phani | Update translations

* Phani | Add tests for Save & Delete Popups

* Phani | Fix uglify

* Phani | Fix uglify by refactoring the imports

* Phani | Rename folder otNotes in components
  • Loading branch information
phanindra28 authored May 31, 2024
1 parent 9978734 commit 185ae88
Show file tree
Hide file tree
Showing 22 changed files with 1,088 additions and 322 deletions.
1 change: 1 addition & 0 deletions micro-frontends/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
},
"scripts": {
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"test": "jest",
"test:ci": "yarn test --ci --coverage",
"lint": "eslint src/**/*.{js,jsx}"
Expand Down
17 changes: 16 additions & 1 deletion micro-frontends/public/i18n/locale_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,20 @@
"SEARCH_REACTION": "Search Reaction",
"COMMON_REACTIONS": "Common Reactions",
"ALLERGEN": "Allergen",
"EDIT_FORM_ERROR_MESSAGE": "Please enter a value in the mandatory fields or correct the value in the highlighted fields to proceed"
"EDIT_FORM_ERROR_MESSAGE": "Please enter a value in the mandatory fields or correct the value in the highlighted fields to proceed",
"EMPTY_NOTES_ERROR": "Note cannot be empty",
"OT_NOTES": "Notes",
"FROM": "From",
"TO": "To",
"FROM_DATE_BEFORE_TO_DATE_ERROR": "From date should be before To date",
"DATE_OUT_OF_RANGE_ERROR": "Please select date within the valid range",
"DELETE_NOTE_TITLE": "Delete Note",
"YES_KEY": "Yes",
"NO_KEY": "No",
"DELETE_NOTE_MESSAGE": "Are you sure you want to delete this note?",
"NOTES_PLACEHOLDER": "Enter a maximum of 150 characters",
"UPDATE_NOTE_TITLE": "Update Note",
"ADD_NOTE_TITLE": "Add Note",
"LOADING": "Loading...",
"UPDATE": "Update"
}
36 changes: 36 additions & 0 deletions micro-frontends/src/next-ui/Components/OtNotes/DeletePopup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { Modal } from "carbon-components-react";
import "../../../styles/carbon-conflict-fixes.scss";
import "../../../styles/carbon-theme.scss";
import { deleteOtNote } from "./utils";
import {FormattedMessage} from "react-intl";
import "./OtNotes.scss";
export function DeletePopup(props) {
const { hostData, hostApi} = props;
const [isLoading, setIsLoading] = useState(false);
return <Modal
open
danger
className="next-ui ot-notes-popup"
modalHeading={<FormattedMessage id={"DELETE_NOTE_TITLE"} defaultMessage={"Delete Note"}/>}
primaryButtonText={isLoading? <FormattedMessage id={"LOADING"} defaultMessage={"Loading..."}/> : <FormattedMessage id={"YES"} defaultMessage={"Yes"}/>}
secondaryButtonText={<FormattedMessage id={"NO"} defaultMessage={"No"}/>}
onRequestClose={hostApi?.onClose}
onSecondarySubmit={hostApi?.onClose}
onRequestSubmit={() => {
setIsLoading(true);
deleteOtNote(hostData?.noteId).then(() => {
setIsLoading(false);
hostApi?.onSuccess();
});
}}
>
<FormattedMessage id={"DELETE_NOTE_MESSAGE"} defaultMessage={"Are you sure you want to delete this OT Note?"}/>
</Modal>
}

DeletePopup.propTypes = {
hostData: PropTypes.Object,
hostApi: PropTypes.Object,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import {render, screen, waitFor} from "@testing-library/react";
import { DeletePopup } from "./DeletePopup";
import {I18nProvider} from "../i18n/I18nProvider";

const mockDeletePopup = jest.fn();

jest.mock('../i18n/utils');
jest.mock("./utils", () => ({
deleteOtNote : () => mockDeletePopup(),
}));
describe("DeletePopup", () => {
it("should render", async () => {
const {container, getByText} = render(<I18nProvider><DeletePopup hostData={{noteId: 10}}/></I18nProvider>);
await waitFor(() => {
expect(getByText("Delete Note")).toBeTruthy();
expect(container).toMatchSnapshot();
});
});
it("should call deleteOtNote on submit", async () => {
const {getByText}= render(<I18nProvider><DeletePopup hostData={{noteId: 10}}/></I18nProvider>);
await waitFor(() => {
expect(getByText("Delete Note")).toBeTruthy();
});
mockDeletePopup.mockResolvedValue(() => {})
screen.getByText("Yes").click();
expect(mockDeletePopup).toHaveBeenCalled();
});
});
22 changes: 22 additions & 0 deletions micro-frontends/src/next-ui/Components/OtNotes/OtNotes.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.ot-notes-popup{
.bx--modal-close{
top: 0;
right: 0;
&:hover{
background-color: #e5e5e5 !important;
}
}
.date-range{
display: flex;
justify-content: space-between;
}
.bx--date-picker.bx--date-picker--single .bx--date-picker__input{
width: 230px;
}
.error-text{
color: #da1e28;
}
.bx--date-picker__input:disabled{
color: #8d8d8d;
}
}
145 changes: 145 additions & 0 deletions micro-frontends/src/next-ui/Components/OtNotes/SavePopup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, {useEffect, useState} from "react";
import PropTypes from "prop-types";
import {Modal, TextArea, DatePicker, DatePickerInput} from "carbon-components-react";
import "../../../styles/carbon-conflict-fixes.scss";
import "../../../styles/carbon-theme.scss";
import "./OtNotes.scss";
import {FormattedMessage, useIntl} from "react-intl";
import {I18nProvider} from '../i18n/I18nProvider';
import moment from "moment";
import {saveNote, updateNoteForADay} from "./utils";

export function SavePopup(props) {
const {hostData, hostApi} = props;
const {
notes,
weekEndDateTime = moment().endOf('day'),
weekStartDateTime = moment().startOf('day'),
isDayView,
noteId,
noteDate,
providerUuid
} = hostData;
const [modalNotes, setModalNotes] = useState(notes || "");
const [startDate, setStartDate] = useState(new Date(noteDate));
const [endDate, setEndDate] = useState(new Date(noteDate));
const [shouldShowErrors, setShouldShowErrors] = useState(false);
const validMinStartDate = noteId || isDayView ? new Date(noteDate) : new Date(weekStartDateTime);
const [validMinEndDate, setValidMinEndDate] = useState(noteId || isDayView ? new Date(noteDate) : new Date(weekEndDateTime));
const [isLoading, setIsLoading] = useState(false);
const intl = useIntl();

const hasActiveErrors = () => {
return !modalNotes || !startDate || !endDate || startDate > endDate;
}

const handleSave = () => {
if (noteId) {
updateNoteForADay(noteId, modalNotes, providerUuid).then(() => {
setIsLoading(false);
hostApi?.onSuccess();
});
} else if (isDayView) {
saveNote(modalNotes, startDate).then(() => {
setIsLoading(false);
hostApi?.onSuccess();
});
} else {
saveNote(modalNotes, startDate, endDate).then(() => {
setIsLoading(false);
hostApi?.onSuccess();
});
}
}
useEffect(() => {
setValidMinEndDate(startDate);
}, [startDate]);
return (
<I18nProvider>
<Modal
open
className={"next-ui ot-notes-popup"}
modalHeading={noteId ? <FormattedMessage id={'UPDATE_NOTE_TITLE'} defaultMessage={"Update Notes"}/> :
<FormattedMessage id={'ADD_NOTE_TITLE'} defaultMessage={"Add Notes"}/>}
primaryButtonText={isLoading ?
<FormattedMessage id={"LOADING"} defaultMessage={"Loading..."}/> : noteId ?
<FormattedMessage id={"UPDATE"} defaultMessage={"Update"}/> :
<FormattedMessage id={"SAVE"} defaultMessage={"Save"}/>}
secondaryButtonText={<FormattedMessage id={"CANCEL"} defaultMessage={"Cancel"}/>}
onRequestClose={hostApi?.onClose}
onRequestSubmit={() => {
setShouldShowErrors(true);
if (!hasActiveErrors()) {
setIsLoading(true);
handleSave();
}
}}
>
<TextArea labelText={<FormattedMessage id={"OT_NOTES"} defaultMessage={"Notes"}/>}
value={modalNotes}
placeholder={intl.formatMessage({
id: "NOTES_PLACEHOLDER",
defaultMessage: "Enter a maximum of 150 characters"
})} maxCount={150} onChange={e => {
setModalNotes(e?.target?.value);
}}/>
{shouldShowErrors && !modalNotes ?
<p className={"error-text"}><FormattedMessage id={"EMPTY_NOTES_ERROR"}
defaultMessage={"Note cannot be empty"}/>
</p> : <div style={{height: "20px"}}></div>}
<div className={"date-range"}>
<div>
<DatePicker datePickerType="single" maxDate={new Date(weekEndDateTime)} dateFormat={"d/m/Y"}
minDate={validMinStartDate} value={startDate}
onChange={(e) => {
if (e.length === 1) {
setStartDate(e[0]);
}
}}>
<DatePickerInput
id="date-picker-input-id-start"
placeholder="dd/mm/yyyy"
labelText="Start date"
disabled={Boolean(isDayView || noteId)}
/>
</DatePicker>
{shouldShowErrors && !startDate ?
<div className={"error-text"}><FormattedMessage id={'DATE_OUT_OF_RANGE_ERROR'}
defaultMessage={"Please select date within the valid range"}/>
</div> : startDate > endDate ?
<div className={"error-text"}><FormattedMessage id={'FROM_DATE_BEFORE_TO_DATE_ERROR'}
defaultMessage={"From date should be before To date"}/>
</div> :
<div style={{height: "20px"}}></div>
}
</div>
<div>
<DatePicker datePickerType="single" maxDate={new Date(weekEndDateTime)} dateFormat={"d/m/Y"}
minDate={validMinEndDate} value={endDate}
onChange={(e) => {
if (e.length === 1) {
setEndDate(e[0]);
}
}}>
<DatePickerInput
id="date-picker-input-id-finish"
placeholder="dd/mm/yyyy"
labelText="End date"
disabled={Boolean(isDayView || noteId)}
/>
</DatePicker>
{shouldShowErrors && !endDate ?
<div className={"error-text"}><FormattedMessage id={'DATE_OUT_OF_RANGE_ERROR'}
defaultMessage={"Please select date within the valid range"}/>
</div> : <div style={{height: "20px"}}></div>}
</div>
</div>
</Modal>
</I18nProvider>
);
}

SavePopup.propTypes = {
hostData: PropTypes.any,
hostApi: PropTypes.any,
}
Loading

0 comments on commit 185ae88

Please sign in to comment.