Skip to content

Commit

Permalink
Merge pull request #84 from EyeSeeTea/feat/pwa-support
Browse files Browse the repository at this point in the history
[feature]: Integrate Vite-PWA for offline support
  • Loading branch information
MiquelAdell authored Feb 5, 2025
2 parents 4c28a37 + ac1ce92 commit 3892559
Show file tree
Hide file tree
Showing 19 changed files with 2,379 additions and 47 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ cypress/fixtures/
.idea/*

docs/

# build output
dist/
dev-dist/
18 changes: 16 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2025-01-17T13:50:55.771Z\n"
"PO-Revision-Date: 2025-01-17T13:50:55.771Z\n"
"POT-Creation-Date: 2025-01-28T16:57:55.539Z\n"
"PO-Revision-Date: 2025-01-28T16:57:55.539Z\n"

msgid "There was a problem with \"{{name}}\" - {{prop}} is not set"
msgstr ""

msgid "There was an error processing the form"
msgstr ""

msgid "There was an error processing the form"
msgstr ""

msgid "Facilities"
msgstr ""

Expand Down Expand Up @@ -59,6 +62,11 @@ msgstr ""
msgid "Log Out"
msgstr ""

msgid ""
"You cannot carry out this action because you are not connected to the "
"internet. Please try again later."
msgstr ""

msgid "Stage - Profile"
msgstr ""

Expand Down Expand Up @@ -183,6 +191,12 @@ msgstr ""
msgid "No"
msgstr ""

msgid "Online"
msgstr ""

msgid "Offline"
msgstr ""

msgid "Profile"
msgstr ""

Expand Down
17 changes: 16 additions & 1 deletion i18n/es.po
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2025-01-17T13:50:55.771Z\n"
"POT-Creation-Date: 2025-01-28T16:57:55.539Z\n"
"PO-Revision-Date: 2018-10-25T09:02:35.143Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"


msgid "There was a problem with \"{{name}}\" - {{prop}} is not set"
msgstr ""

msgid "There was an error processing the form"
msgstr ""

msgid "There was an error processing the form"
msgstr ""

msgid "Facilities"
msgstr ""

Expand Down Expand Up @@ -59,6 +63,11 @@ msgstr ""
msgid "Log Out"
msgstr ""

msgid ""
"You cannot carry out this action because you are not connected to the "
"internet. Please try again later."
msgstr ""

msgid "Stage - Profile"
msgstr ""

Expand Down Expand Up @@ -183,6 +192,12 @@ msgstr ""
msgid "No"
msgstr ""

msgid "Online"
msgstr ""

msgid "Offline"
msgstr ""

msgid "Profile"
msgstr ""

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"vite-plugin-checker": "^0.6.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-node-stdlib-browser": "^0.2.1",
"vite-plugin-pwa": "^0.21.1",
"vitest": "^0.32.2"
},
"scripts": {
Expand Down
8 changes: 4 additions & 4 deletions src/webapp/components/content-loader/ContentLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import { Backdrop, CircularProgress } from "@material-ui/core";
import React, { ReactNode, useEffect } from "react";
import styled from "styled-components";
import { useOfflineSnackbar } from "../../hooks/useOfflineSnackbar";

export interface ContentLoaderProps {
loading: boolean;
Expand All @@ -18,17 +18,17 @@ export const ContentLoader: React.FC<ContentLoaderProps> = ({
showErrorAsSnackbar,
onError,
}) => {
const snackbar = useSnackbar();
const { offlineError } = useOfflineSnackbar();

useEffect(() => {
if (error && showErrorAsSnackbar) {
snackbar.error(error);
offlineError(error);
}

if (error && onError) {
onError();
}
}, [error, snackbar, showErrorAsSnackbar, onError]);
}, [error, offlineError, showErrorAsSnackbar, onError]);

if (loading) {
return (
Expand Down
16 changes: 16 additions & 0 deletions src/webapp/components/offline-message/OfflineMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styled from "styled-components";
import i18n from "../../../utils/i18n";

export const OfflineMessage: React.FC = () => {
return (
<CenteredText>
{i18n.t(
"You cannot carry out this action because you are not connected to the internet. Please try again later."
)}
</CenteredText>
);
};

const CenteredText = styled.p`
text-align: center;
`;
8 changes: 4 additions & 4 deletions src/webapp/components/survey-list/hook/usePatientSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { SURVEY_FORM_TYPES, Survey } from "../../../../domain/entities/Survey";
import { useAppContext } from "../../../contexts/app-context";
import { useCurrentSurveys } from "../../../contexts/current-surveys-context";
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import i18n from "../../../../utils/i18n";
import { useOfflineSnackbar } from "../../../hooks/useOfflineSnackbar";

export const usePatientSearch = (
filteredSurveys: Survey[] | undefined,
Expand All @@ -18,7 +18,7 @@ export const usePatientSearch = (
currentFacilityLevelForm,
currentPrevalenceSurveyForm,
} = useCurrentSurveys();
const snackbar = useSnackbar();
const { offlineError } = useOfflineSnackbar();

const [patientIdSearchKeyword, setPatientIdSearchKeyword] = useState("");
const [patientCodeSearchKeyword, setPatientCodeSearchKeyword] = useState("");
Expand Down Expand Up @@ -65,7 +65,7 @@ export const usePatientSearch = (
setIsLoading(false);
},
() => {
snackbar.error(i18n.t("Error fetching surveys"));
offlineError(i18n.t("Error fetching surveys"));
setIsLoading(false);
}
);
Expand All @@ -84,7 +84,7 @@ export const usePatientSearch = (
setIsLoading(false);
},
() => {
snackbar.error(i18n.t("Error fetching surveys"));
offlineError(i18n.t("Error fetching surveys"));
setIsLoading(false);
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
SURVEY_FORM_TYPES,
SURVEYS_WITH_CHILD_COUNT,
} from "../../../../domain/entities/Survey";
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import styled from "styled-components";
import {
TableBody,
Expand All @@ -28,6 +27,7 @@ import { useSurveyListActions } from "../hook/useSurveyListActions";
import { getChildrenName } from "../../../../domain/utils/getChildrenName";
import { useMultipleChildCount } from "../hook/useMultipleChildCount";
import { isPrevalencePatientChild } from "../../../../domain/utils/PPSProgramsHelper";
import { useOfflineSnackbar } from "../../../hooks/useOfflineSnackbar";

interface PaginatedSurveyListTableProps {
surveys: Survey[] | undefined;
Expand All @@ -49,7 +49,7 @@ export const PaginatedSurveyListTable: React.FC<PaginatedSurveyListTableProps> =
pageSize,
total,
}) => {
const snackbar = useSnackbar();
const { snackbar, offlineError } = useOfflineSnackbar();
//states for column sort
const [surveyNameSortDirection, setSurveyNameSortDirection] = useState<SortDirection>("asc");
const [patientIdSortDirection, setPatientIdSortDirection] = useState<SortDirection>("asc");
Expand Down Expand Up @@ -80,9 +80,9 @@ export const PaginatedSurveyListTable: React.FC<PaginatedSurveyListTableProps> =
snackbar.success(deleteCompleteState.message);
}
if (deleteCompleteState?.status === "error") {
snackbar.error(deleteCompleteState.message);
offlineError(deleteCompleteState.message);
}
}, [deleteCompleteState, snackbar, surveys, setSortedSurveys]);
}, [deleteCompleteState, snackbar, surveys, offlineError, setSortedSurveys]);

return (
<ContentLoader loading={loading} error="" showErrorAsSnackbar={false}>
Expand Down
8 changes: 4 additions & 4 deletions src/webapp/components/survey-list/table/SurveyListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
SURVEY_FORM_TYPES,
SURVEYS_WITH_CHILD_COUNT,
} from "../../../../domain/entities/Survey";
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import styled from "styled-components";
import {
TableBody,
Expand All @@ -25,6 +24,7 @@ import { useDeleteSurvey } from "../hook/useDeleteSurvey";
import { ContentLoader } from "../../content-loader/ContentLoader";
import { SortDirection, useSurveyListActions } from "../hook/useSurveyListActions";
import { getChildrenName } from "../../../../domain/utils/getChildrenName";
import { useOfflineSnackbar } from "../../../hooks/useOfflineSnackbar";

interface SurveyListTableProps {
surveys: Survey[] | undefined;
Expand All @@ -38,7 +38,7 @@ export const SurveyListTable: React.FC<SurveyListTableProps> = ({
surveyFormType,
refreshSurveys,
}) => {
const snackbar = useSnackbar();
const { snackbar, offlineError } = useOfflineSnackbar();

//states for column sort
const [surveyNameSortDirection, setSurveyNameSortDirection] = useState<SortDirection>("asc");
Expand Down Expand Up @@ -73,9 +73,9 @@ export const SurveyListTable: React.FC<SurveyListTableProps> = ({
snackbar.success(deleteCompleteState.message);
}
if (deleteCompleteState?.status === "error") {
snackbar.error(deleteCompleteState.message);
offlineError(deleteCompleteState.message);
}
}, [deleteCompleteState, snackbar, surveys, setSortedSurveys]);
}, [deleteCompleteState, snackbar, surveys, offlineError, setSortedSurveys]);

return (
<ContentLoader loading={loading} error="" showErrorAsSnackbar={false}>
Expand Down
12 changes: 8 additions & 4 deletions src/webapp/components/survey/SurveyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import i18n from "@eyeseetea/d2-ui-components/locales";
import { useSurveyForm } from "./hook/useSurveyForm";
import { red300 } from "material-ui/styles/colors";
import { Id } from "../../../domain/entities/Ref";
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import { SURVEY_FORM_TYPES } from "../../../domain/entities/Survey";
import { ContentLoader } from "../content-loader/ContentLoader";
import { useSaveSurvey } from "./hook/useSaveSurvey";
Expand All @@ -15,6 +14,11 @@ import { SurveySection } from "./SurveySection";
import { SurveyStageSection } from "./SurveyStageSection";
import { useHistory } from "react-router-dom";
import useReadOnlyAccess from "./hook/useReadOnlyAccess";
import { GridSection } from "./GridSection";
import _c from "../../../domain/entities/generic/Collection";
import { TableSection } from "./TableSection";
import { useOfflineSnackbar } from "../../hooks/useOfflineSnackbar";


export interface SurveyFormProps {
hideForm: () => void;
Expand All @@ -34,7 +38,7 @@ export const CancelButton = withStyles(() => ({
}))(Button);

export const SurveyForm: React.FC<SurveyFormProps> = props => {
const snackbar = useSnackbar();
const { snackbar, offlineError } = useOfflineSnackbar();
const history = useHistory();
const { hasReadOnlyAccess } = useReadOnlyAccess();

Expand Down Expand Up @@ -65,15 +69,15 @@ export const SurveyForm: React.FC<SurveyFormProps> = props => {
}

if (saveCompleteState && saveCompleteState.status === "error") {
snackbar.error(saveCompleteState.message);
offlineError(saveCompleteState.message);
setLoading(false);
}

//If error fetching survey, redirect to homepage.
if (error) {
history.push(`/`);
}
}, [error, saveCompleteState, snackbar, history, props, setLoading]);
}, [error, saveCompleteState, snackbar, history, props, setLoading, offlineError]);

const saveSurveyForm = () => {
setLoading(true);
Expand Down
9 changes: 5 additions & 4 deletions src/webapp/components/survey/SurveyFormOUSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OrgUnitsSelector, useSnackbar } from "@eyeseetea/d2-ui-components";
import { OrgUnitsSelector } from "@eyeseetea/d2-ui-components";
import { useEffect } from "react";
import { COUNTRY_OU_LEVEL, HOSPITAL_OU_LEVELS } from "../../../data/repositories/UserD2Repository";
import { Id } from "../../../domain/entities/Ref";
Expand All @@ -9,6 +9,7 @@ import { getParentOUIdFromPath } from "../../../domain/utils/PPSProgramsHelper";
import { useAppContext } from "../../contexts/app-context";
import { useCurrentSurveys } from "../../contexts/current-surveys-context";
import { useSurveyFormOUSelector } from "./hook/useSurveyFormOUSelector";
import { useOfflineSnackbar } from "../../hooks/useOfflineSnackbar";

export interface SurveyFormOUSelectorProps {
formType: SURVEY_FORM_TYPES;
Expand All @@ -31,13 +32,13 @@ export const SurveyFormOUSelector: React.FC<SurveyFormOUSelectorProps> = ({
setCurrentOrgUnit,
currentSurveyId
);
const snackbar = useSnackbar();
const { snackbar, offlineError } = useOfflineSnackbar();

useEffect(() => {
if (ouSelectorErrMsg) {
snackbar.error(ouSelectorErrMsg);
offlineError(ouSelectorErrMsg);
}
}, [ouSelectorErrMsg, snackbar, shouldRefresh]);
}, [ouSelectorErrMsg, snackbar, shouldRefresh, offlineError]);

return (
<>
Expand Down
25 changes: 25 additions & 0 deletions src/webapp/components/top-app-bar/TopAppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import PersonOutlineIcon from "@material-ui/icons/PersonOutline";
import i18n from "@eyeseetea/feedback-component/locales";
import SettingsIcon from "@material-ui/icons/Settings";
import styled from "styled-components";
import { palette } from "../../pages/app/themes/dhis2.theme";
import useOnlineStatus from "../../hooks/useOnlineStatus";

const useStyles = makeStyles(theme => ({
root: {
Expand Down Expand Up @@ -48,6 +50,7 @@ interface TopAppBarProps {
export const TopAppBar: React.FC<TopAppBarProps> = ({ toggleShowMenu }) => {
const classes = useStyles();
const history = useHistory();
const isOnline = useOnlineStatus();

const { currentUser } = useAppContext();

Expand Down Expand Up @@ -89,6 +92,11 @@ export const TopAppBar: React.FC<TopAppBarProps> = ({ toggleShowMenu }) => {

<Box className={classes.title} />

<OnlineStatusContainer>
<StatusIndicator isOnline={isOnline}>&#9679;</StatusIndicator>
<p>{isOnline ? i18n.t("Online") : i18n.t("Offline")}</p>
</OnlineStatusContainer>

<SelectContainer>
<AvatarContainer id="demo-positioned-button" onClick={handleClick}>
<Avatar style={{ backgroundColor: "#0099DE" }}>
Expand Down Expand Up @@ -140,3 +148,20 @@ const AvatarContainer = styled.div`
cursor: pointer;
}
`;

const OnlineStatusContainer = styled.div`
margin: 16px 16px;
display: flex;
align-items: center;
background-color: #0099de;
padding: 0 16px;
font-size: 14px;
border-radius: 5px;
gap: 8px;
`;

const StatusIndicator = styled.p<{
isOnline: boolean;
}>`
color: ${props => (props.isOnline ? palette.status.positive : palette.status.negative)};
`;
Loading

0 comments on commit 3892559

Please sign in to comment.