Skip to content

Commit

Permalink
Merge pull request #69 from openimis/develop
Browse files Browse the repository at this point in the history
MERGING RELEASE branches
  • Loading branch information
delcroip committed May 16, 2023
2 parents 0a974b6 + d5fb41e commit b0a20c6
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 34 deletions.
29 changes: 29 additions & 0 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ export function fetchEnrolmentOfficers(mm, variables) {
);
}

export function fetchSubstitutionEnrolmentOfficers(mm, variables) {
return graphqlWithVariables(
`
query SubstitutionEnrolmentOfficers ($str: String, $villagesUuids: [String!], $officerUuid: String) {
substitutionEnrolmentOfficers(str: $str, villagesUuids: $villagesUuids, officerUuid: $officerUuid) {
edges {
node {
id
uuid
code
lastName
otherNames
}
}
}
}
`,
variables,
"ADMIN_SUBSTITUTION_ENROLMENT_OFFICERS",
);
}

export function createUser(mm, user, clientMutationLabel) {
const mutation = prepareMutation(
`
Expand Down Expand Up @@ -151,6 +173,7 @@ export function fetchUser(mm, userId, clientMutationId) {
username
officer {
id
uuid
hasLogin
phone
dob
Expand Down Expand Up @@ -345,3 +368,9 @@ export function setUserEmailValid() {
dispatch({ type: "USER_EMAIL_FIELDS_VALIDATION_SET_VALID" });
};
}

export function saveEmailFormatValidity(isFormatInvalid) {
return (dispatch) => {
dispatch({ type: "USER_EMAIL_FORMAT_VALIDATION_CHECK", payload: { data: { isFormatInvalid } } });
};
}
1 change: 1 addition & 0 deletions src/components/ClaimAdministratorFormPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const ClaimAdministratorFormPanel = (props) => {
module="admin"
label="user.dob"
readOnly={readOnly}
maxDate={new Date()}
onChange={(birthDate) => onEditedChanged({ ...edited, birthDate })}
/>
</Grid>
Expand Down
16 changes: 6 additions & 10 deletions src/components/EnrolmentOfficerFormPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,7 @@ const EnrolmentOfficerFormPanel = (props) => {

const isValid = !isLoading;
useEffect(() => {
toggleUserRoles(
edited,
data,
isValid,
isEnabled,
hasOfficerRole,
onEditedChanged,
OFFICER_ROLE_IS_SYSTEM);
toggleUserRoles(edited, data, isValid, isEnabled, hasOfficerRole, onEditedChanged, OFFICER_ROLE_IS_SYSTEM);
}, [isEnabled]);

useEffect(() => {
Expand Down Expand Up @@ -97,16 +90,19 @@ const EnrolmentOfficerFormPanel = (props) => {
module="admin"
label="user.dob"
readOnly={readOnly}
maxDate={new Date()}
onChange={(birthDate) => onEditedChanged({ ...edited, birthDate })}
/>
</Grid>
<Grid item xs={4} className={classes.item}>
<PublishedComponent
pubRef="admin.EnrolmentOfficerPicker"
pubRef="admin.SubstitutionEnrolmentOfficerPicker"
module="admin"
readOnly={readOnly}
label={formatMessage("substitutionOfficer")}
withLabel
withPlaceholder
value={edited.substitutionOfficer}
villages={edited.officerVillages}
onChange={(substitutionOfficer) => onEditedChanged({ ...edited, substitutionOfficer })}
/>
</Grid>
Expand Down
14 changes: 5 additions & 9 deletions src/components/UserForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import {
clearUser,
fetchUserMutation,
fetchRegionDistricts,
clearRegionDistricts,
fetchObligatoryUserFields,
fetchObligatoryEnrolmentOfficerFields, fetchUsernameLength,
fetchObligatoryEnrolmentOfficerFields,
fetchUsernameLength,
} from "../actions";
import UserMasterPanel from "./UserMasterPanel";

Expand Down Expand Up @@ -138,8 +138,10 @@ class UserForm extends Component {
user.lastName &&
user.otherNames &&
user.username &&
user.email &&
this.props.isUserNameValid === true &&
this.props.isUserEmailValid === true &&
!this.props.isUserEmailFormatInvalid &&
user.roles?.length &&
user.districts?.length > 0 &&
user.language
Expand All @@ -149,13 +151,6 @@ class UserForm extends Component {
if (user.password && user.password !== user.confirmPassword) return false;
if (user.userTypes?.includes(CLAIM_ADMIN_USER_TYPE) && !user.healthFacility) return false;
if (user.userTypes?.includes(ENROLMENT_OFFICER_USER_TYPE) && !user.officerVillages) return false;

if (
(this.props.obligatory_user_fields?.email == "M" ||
(user.userTypes?.includes(ENROLMENT_OFFICER_USER_TYPE) && this.props.obligatory_eo_fields?.email == "M")) &&
!user.email
)
return false;
if (
(this.props.obligatory_user_fields?.phone == "M" ||
(user.userTypes?.includes(ENROLMENT_OFFICER_USER_TYPE) && this.props.obligatory_eo_fields?.phone == "M")) &&
Expand Down Expand Up @@ -263,6 +258,7 @@ const mapStateToProps = (state) => ({
isUserNameValid: state.admin.validationFields?.username?.isValid,
isUserEmailValid: state.admin.validationFields?.userEmail?.isValid,
usernameLength: state.admin?.usernameLength,
isUserEmailFormatInvalid: state.admin.validationFields?.userEmailFormat?.isInvalid,
});

const mapDispatchToProps = (dispatch) =>
Expand Down
38 changes: 28 additions & 10 deletions src/components/UserMasterPanel.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { connect, useDispatch } from "react-redux";

import { withTheme, withStyles } from "@material-ui/core/styles";
import { Grid, Divider, Typography } from "@material-ui/core";
Expand All @@ -11,14 +11,15 @@ import {
PublishedComponent,
ValidatedTextInput,
} from "@openimis/fe-core";
import { CLAIM_ADMIN_USER_TYPE, ENROLMENT_OFFICER_USER_TYPE } from "../constants";
import { CLAIM_ADMIN_USER_TYPE, ENROLMENT_OFFICER_USER_TYPE, EMAIL_REGEX_PATTERN } from "../constants";
import {
usernameValidationCheck,
usernameValidationClear,
setUsernameValid,
userEmailValidationCheck,
userEmailValidationClear,
setUserEmailValid,
saveEmailFormatValidity,
} from "../actions";

const styles = (theme) => ({
Expand Down Expand Up @@ -48,12 +49,14 @@ const UserMasterPanel = (props) => {
usernameValidationError,
isUserEmailValid,
isUserEmailValidating,
isUserEmailFormatInvalid,
emailValidationError,
savedUsername,
savedUserEmail,
usernameLength,
} = props;
const { formatMessage } = useTranslations("admin", modulesManager);
const dispatch = useDispatch();

const shouldValidateUsername = (inputValue) => {
const shouldBeValidated = inputValue !== savedUsername;
Expand All @@ -62,12 +65,28 @@ const UserMasterPanel = (props) => {

const shouldValidateEmail = (inputValue) => {
const shouldBeValidated = inputValue !== savedUserEmail;
if (!inputValue) {
return false;
}
return shouldBeValidated;
};

const checkEmailFormatValidity = (emailInput) => {
if (!emailInput) return false;

const isEmailInvalid = !EMAIL_REGEX_PATTERN.test(emailInput);

return isEmailInvalid;
};

const handleEmailChange = (email) => {
const isFormatValid = checkEmailFormatValidity(email);
dispatch(saveEmailFormatValidity(isFormatValid));

onEditedChanged({ ...edited, email });
};

useEffect(() => {
handleEmailChange(edited?.email);
}, []);

return (
<Grid container direction="row">
<Grid item xs={4} className={classes.item}>
Expand Down Expand Up @@ -123,6 +142,7 @@ const UserMasterPanel = (props) => {
isValid={isUserEmailValid}
isValidating={isUserEmailValidating}
validationError={emailValidationError}
invalidValueFormat={isUserEmailFormatInvalid}
action={userEmailValidationCheck}
clearAction={userEmailValidationClear}
setValidAction={setUserEmailValid}
Expand All @@ -131,12 +151,9 @@ const UserMasterPanel = (props) => {
label="user.email"
type="email"
codeTakenLabel="user.emailAlreadyTaken"
required={
obligatoryUserFields?.email === "M" ||
(edited.userTypes?.includes(ENROLMENT_OFFICER_USER_TYPE) && obligatoryEOFields?.email === "M")
}
required={true}
value={edited?.email ?? ""}
onChange={(email) => onEditedChanged({ ...edited, email })}
onChange={(email) => handleEmailChange(email)}
/>
</Grid>
)}
Expand Down Expand Up @@ -263,6 +280,7 @@ const mapStateToProps = (state) => ({
isUserEmailValidating: state.admin.validationFields?.userEmail?.isValidating,
emailValidationError: state.admin.validationFields?.userEmail?.validationError,
savedUserEmail: state.admin?.user?.email,
isUserEmailFormatInvalid: state.admin.validationFields?.userEmailFormat?.isInvalid,
});

export default withModulesManager(connect(mapStateToProps)(withTheme(withStyles(styles)(UserMasterPanel))));
18 changes: 13 additions & 5 deletions src/components/UserSearcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { injectIntl } from "react-intl";

import { IconButton, Tooltip } from "@material-ui/core";
import { withTheme, withStyles } from "@material-ui/core/styles";
import { Tab as TabIcon, Delete as DeleteIcon } from "@material-ui/icons";

import {
withModulesManager,
formatMessageWithValues,
Expand All @@ -13,10 +16,9 @@ import {
ConfirmDialog,
decodeId,
} from "@openimis/fe-core";
import UserFilter from "./UserFilter";

import { fetchUsersSummaries, deleteUser } from "../actions";
import { RIGHT_USER_DELETE } from "../constants";
import UserFilter from "./UserFilter";

const USER_SEARCHER_CONTRIBUTION_KEY = "user.UserSearcher";

Expand Down Expand Up @@ -45,6 +47,10 @@ const getAligns = () => {
return aligns;
};

const styles = (theme) => ({
horizontalButtonContainer: theme.buttonContainer.horizontal,
});

class UserSearcher extends Component {
state = {
deleteUser: null,
Expand Down Expand Up @@ -131,7 +137,7 @@ class UserSearcher extends Component {
formatDateFromISO(this.props.modulesManager, this.props.intl, this.getUserItem(u, "dob")),

(u) => (
<>
<div className={this.props.classes.horizontalButtonContainer}>
<Tooltip title={formatMessage(this.props.intl, "admin.user", "openNewTab")}>
<IconButton onClick={() => this.props.onDoubleClick(u, true)}>
<TabIcon />
Expand All @@ -144,7 +150,7 @@ class UserSearcher extends Component {
</IconButton>
</Tooltip>
)}
</>
</div>
),
];

Expand Down Expand Up @@ -208,4 +214,6 @@ const mapStateToProps = (state) => ({

const mapDispatchToProps = (dispatch) => bindActionCreators({ fetchUsersSummaries, deleteUser }, dispatch);

export default withModulesManager(connect(mapStateToProps, mapDispatchToProps)(injectIntl(UserSearcher)));
export default withModulesManager(
connect(mapStateToProps, mapDispatchToProps)(injectIntl(withTheme(withStyles(styles)(UserSearcher)))),
);
71 changes: 71 additions & 0 deletions src/components/pickers/SubstitutionEnrolmentOfficerPicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";

import { TextField } from "@material-ui/core";

import { Autocomplete, useTranslations } from "@openimis/fe-core";
import { fetchSubstitutionEOs } from "../../utils";

const formatSuggestion = (p) => {
if (!p) return "?";
return [p.code, p.lastName, p.otherNames].filter(Boolean).join(" ");
};

const SubstitutionEnrolmentOfficerPicker = (props) => {
const {
onChange,
modulesManager,
readOnly = false,
required = false,
value,
villages,
filterOptions,
filterSelectedOptions,
multiple = false,
withLabel = true,
label,
withPlaceholder = false,
placeholder,
} = props;
const dispatch = useDispatch();
const { formatMessage } = useTranslations("admin", modulesManager);
const [searchString, setSearchString] = useState("");
const { isFetching, items } = useSelector((state) => state.admin.substitutionEnrolmentOfficers);
const officerUuid = useSelector((state) => state.admin?.user?.officer?.uuid) ?? null;

const handleInputChange = (str) => {
setSearchString(str);
fetchSubstitutionEOs(dispatch, modulesManager, officerUuid, searchString, villages);
};

return (
<Autocomplete
multiple={multiple}
required={required}
placeholder={placeholder}
label={label}
withLabel={withLabel}
readOnly={readOnly}
options={items}
isLoading={isFetching}
value={value}
getOptionLabel={formatSuggestion}
onChange={onChange}
filterOptions={filterOptions}
filterSelectedOptions={filterSelectedOptions}
onInputChange={handleInputChange}
renderInput={(inputProps) => (
<TextField
{...inputProps}
label={withLabel && (label || formatMessage("EnrolmentOfficerFormPanel.substitutionOfficer"))}
placeholder={
withPlaceholder &&
(placeholder || formatMessage("EnrolmentOfficerFormPanel.substitutionOfficer.placeholder"))
}
/>
)}
/>
);
};

export default SubstitutionEnrolmentOfficerPicker;
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export const CLAIM_ADMIN_USER_TYPE = "CLAIM_ADMIN";
export const CLAIM_ADMIN_IS_SYSTEM = 256;
export const MODULE_NAME = "user";

// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
export const EMAIL_REGEX_PATTERN =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;

export const USER_TYPES = (rights) => {
const baseTypes = [INTERACTIVE_USER_TYPE];
if (rights.includes(RIGHT_ENROLMENTOFFICER)) {
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import UserPage from "./pages/UserPage";
import messagesEn from "./translations/en.json";
import UserPicker from "./components/pickers/UserPicker";
import EnrolmentOfficerPicker from "./components/pickers/EnrolmentOfficerPicker";
import SubstitutionEnrolmentOfficerPicker from "./components/pickers/SubstitutionEnrolmentOfficerPicker";
import UserRolesPicker from "./components/pickers/UserRolesPicker";
import UserTypesPicker from "./components/pickers/UserTypesPicker";
import reducer from "./reducer";
Expand All @@ -28,6 +29,7 @@ const DEFAULT_CONFIG = {
refs: [
{ key: "admin.UserPicker", ref: UserPicker },
{ key: "admin.EnrolmentOfficerPicker", ref: EnrolmentOfficerPicker },
{ key: "admin.SubstitutionEnrolmentOfficerPicker", ref: SubstitutionEnrolmentOfficerPicker },
{ key: "admin.UserRolesPicker", ref: UserRolesPicker },
{ key: "admin.UserTypesPicker", ref: UserTypesPicker },
{
Expand Down
Loading

0 comments on commit b0a20c6

Please sign in to comment.