Skip to content

Commit

Permalink
Tear down settings dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
p3rcypj committed Sep 17, 2024
1 parent 6bd94dd commit 808c68b
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 168 deletions.
205 changes: 37 additions & 168 deletions src/webapp/components/settings-dialog/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,81 +15,24 @@ import {
createStyles,
Theme,
} from "@material-ui/core";
import { useAppContext } from "$/webapp/contexts/app-context";
import { useBooleanState } from "$/webapp/utils/use-boolean";
import { useSnackbar } from "@eyeseetea/d2-ui-components/snackbar";
import { useSettingsDialog } from "$/webapp/components/settings-dialog/useSettingsDialog";
import { Maybe } from "$/utils/ts-utils";
import i18n from "$/utils/i18n";
import _c from "$/domain/entities/generic/Collection";

interface SettingsDialogProps {
export interface SettingsDialogProps {
open: boolean;
onClose: () => void;
}

export const SettingsDialog: React.FC<SettingsDialogProps> = ({ open, onClose }) => {
const { config, compositionRoot } = useAppContext();

const theme = useTheme();
const snackbar = useSnackbar();
const styles = useStyles();

const [loading, { enable: startLoading, disable: stopLoading }] = useBooleanState(false);
const [reloading, { enable: reloadPage }] = useBooleanState(false);
const [settings, setSettings] = React.useState<Settings>({
...config,
administratorGroups: config.administratorGroups.join(", "),
});

const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setSettings(prevSettings => ({
...prevSettings,
[name]: value,
}));
}, []);

const handleSave = React.useCallback(() => {
startLoading();

const config = {
...settings,
administratorGroups: _c(settings.administratorGroups.split(","))
.map(id => id.trim())
.compact()
.value(),
};

compositionRoot.config.update.execute(config).run(
() => {
onClose();
snackbar.success(i18n.t("Settings saved. Reloading page..."));
stopLoading();
reloadPage();
setTimeout(() => window.location.reload(), 1000);
},
err => {
snackbar.error(err.message);
stopLoading();
}
);
}, [
compositionRoot.config.update,
const { loading, reloading, fields, handleSave, close } = useSettingsDialog({
open,
onClose,
reloadPage,
settings,
snackbar,
startLoading,
stopLoading,
]);

const close = React.useCallback(() => {
onClose();
setSettings({
...config,
administratorGroups: config.administratorGroups.join(", "),
});
}, [config, onClose]);
});

return (
<>
Expand All @@ -103,104 +46,9 @@ export const SettingsDialog: React.FC<SettingsDialogProps> = ({ open, onClose })
marginBottom={theme.spacing(0.125)}
gridRowGap={theme.spacing(1)}
>
<Tooltip
title={i18n.t(
"The sheet name of the zip file that will be created when more than one dataset is selected"
)}
enterDelay={500}
>
<TextField
label={i18n.t("Sheet name")}
name="sheetName"
margin="dense"
variant="standard"
value={settings.sheetName}
onChange={handleChange}
fullWidth
/>
</Tooltip>
<Tooltip
title={i18n.t(
"The filename of the zip file that will be created when more than one dataset is selected"
)}
enterDelay={500}
>
<TextField
label={i18n.t("Filename")}
name="fileName"
margin="dense"
variant="standard"
value={settings.fileName}
onChange={handleChange}
fullWidth
/>
</Tooltip>
<Tooltip
title={i18n.t("User Group IDs separated by commas")}
enterDelay={500}
>
<TextField
label={i18n.t("Administrator Groups")}
name="administratorGroups"
margin="dense"
variant="standard"
value={settings.administratorGroups}
onChange={handleChange}
fullWidth
/>
</Tooltip>
<Tooltip
title={i18n.t(
"The placeholder label that will be added next to '{{healthFacility}}: '",
{ healthFacility: i18n.t("Health Facility"), nsSeparator: false }
)}
enterDelay={500}
>
<TextField
label={i18n.t("OU label")}
name="ouLabel"
margin="dense"
variant="standard"
value={settings.ouLabel}
onChange={handleChange}
fullWidth
/>
</Tooltip>
<Tooltip
title={i18n.t(
"The placeholder label that will be added next to '{{reportingPeriod}}: '",
{ reportingPeriod: i18n.t("Reporting Period"), nsSeparator: false }
)}
enterDelay={500}
>
<TextField
label={i18n.t("Period label")}
name="periodLabel"
margin="dense"
variant="standard"
value={settings.periodLabel}
onChange={handleChange}
fullWidth
/>
</Tooltip>
<Tooltip
title={i18n.t(
"The placeholder message that will be shown to users at the top of the app. You can use this field to provide instructions or other information. To hide this message, leave this field empty."
)}
enterDelay={500}
>
<TextField
label={i18n.t("Message placeholder")}
name="infoPlaceholder"
margin="dense"
variant="standard"
value={settings.infoPlaceholder}
onChange={handleChange}
minRows={4}
fullWidth
multiline
/>
</Tooltip>
{fields.map((fieldProps, idx) => (
<TooltipTextField key={idx} {...fieldProps} />
))}
</Box>
</DialogContent>
<LinearProgress hidden={!loading} />
Expand All @@ -218,14 +66,35 @@ export const SettingsDialog: React.FC<SettingsDialogProps> = ({ open, onClose })
);
};

type Settings = {
sheetName: string;
fileName: string;
administratorGroups: string;
ouLabel: string;
periodLabel: string;
infoPlaceholder: Maybe<string>;
};
export interface TooltipTextFieldProps {
title: string;
label: string;
name: string;
value: Maybe<string>;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
minRows?: number;
multiline?: boolean;
}

const TooltipTextField: React.FC<TooltipTextFieldProps> = React.memo(props => {
const { title, label, name, value, onChange, minRows, multiline } = props;

return (
<Tooltip title={title} enterDelay={500}>
<TextField
label={label}
name={name}
margin="dense"
variant="standard"
value={value}
onChange={onChange}
fullWidth
minRows={minRows}
multiline={multiline}
/>
</Tooltip>
);
});

const useStyles = makeStyles((theme: Theme) =>
createStyles({
Expand Down
147 changes: 147 additions & 0 deletions src/webapp/components/settings-dialog/useSettingsDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React from "react";
import { useSnackbar } from "@eyeseetea/d2-ui-components/snackbar";
import { useAppContext } from "$/webapp/contexts/app-context";
import { useBooleanState } from "$/webapp/utils/use-boolean";
import {
SettingsDialogProps,
TooltipTextFieldProps,
} from "$/webapp/components/settings-dialog/SettingsDialog";
import { Maybe } from "$/utils/ts-utils";
import i18n from "$/utils/i18n";
import _c from "$/domain/entities/generic/Collection";

type Settings = {
sheetName: string;
fileName: string;
administratorGroups: string;
ouLabel: string;
periodLabel: string;
infoPlaceholder: Maybe<string>;
};

export function useSettingsDialog(props: SettingsDialogProps) {
const { onClose } = props;
const { config, compositionRoot } = useAppContext();

const snackbar = useSnackbar();

const [loading, { enable: startLoading, disable: stopLoading }] = useBooleanState(false);
const [reloading, { enable: reloadPage }] = useBooleanState(false);
const [settings, setSettings] = React.useState<Settings>({
...config,
administratorGroups: config.administratorGroups.join(", "),
});

const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setSettings(prevSettings => ({
...prevSettings,
[name]: value,
}));
}, []);

const handleSave = React.useCallback(() => {
startLoading();

const config = {
...settings,
administratorGroups: _c(settings.administratorGroups.split(","))
.map(id => id.trim())
.compact()
.value(),
};

compositionRoot.config.update.execute(config).run(
() => {
onClose();
snackbar.success(i18n.t("Settings saved. Reloading page..."));
stopLoading();
reloadPage();
setTimeout(() => window.location.reload(), 1000);
},
err => {
snackbar.error(err.message);
stopLoading();
}
);
}, [compositionRoot, onClose, reloadPage, settings, snackbar, startLoading, stopLoading]);

const close = React.useCallback(() => {
onClose();
setSettings({
...config,
administratorGroups: config.administratorGroups.join(", "),
});
}, [config, onClose]);

const fields = React.useMemo(
() => getTextFields(settings, handleChange),
[handleChange, settings]
);

return { loading, reloading, handleSave, close, fields };
}

function getTextFields(
settings: Settings,
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
): TooltipTextFieldProps[] {
return [
{
title: i18n.t(
"The sheet name of the zip file that will be created when more than one dataset is selected"
),
label: i18n.t("Sheet name"),
name: "sheetName",
value: settings.sheetName,
onChange: handleChange,
},
{
title: i18n.t(
"The filename of the zip file that will be created when more than one dataset is selected"
),
label: i18n.t("Filename"),
name: "fileName",
value: settings.fileName,
onChange: handleChange,
},
{
title: i18n.t("User Group IDs separated by commas"),
label: i18n.t("Administrator Groups"),
name: "administratorGroups",
value: settings.administratorGroups,
onChange: handleChange,
},
{
title: i18n.t(
"The placeholder label that will be added next to '{{healthFacility}}: '",
{ healthFacility: i18n.t("Health Facility"), nsSeparator: false }
),
label: i18n.t("OU label"),
name: "ouLabel",
value: settings.ouLabel,
onChange: handleChange,
},
{
title: i18n.t(
"The placeholder label that will be added next to '{{reportingPeriod}}: '",
{ reportingPeriod: i18n.t("Reporting Period"), nsSeparator: false }
),
label: i18n.t("Period label"),
name: "periodLabel",
value: settings.periodLabel,
onChange: handleChange,
},
{
title: i18n.t(
"The placeholder message that will be shown to users at the top of the app. You can use this field to provide instructions or other information. To hide this message, leave this field empty."
),
label: i18n.t("Message placeholder"),
name: "infoPlaceholder",
value: settings.infoPlaceholder,
onChange: handleChange,
minRows: 4,
multiline: true,
},
];
}

0 comments on commit 808c68b

Please sign in to comment.