Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Active users][Settings][Account settings] Adapt fields and sync data #135

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions src/components/Form/IpaCalendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React from "react";
// PatternFly
import { CalendarMonth, DropdownItem } from "@patternfly/react-core";
// Layouts
import DataTimePickerLayout from "src/components/layouts/Calendar/DataTimePickerLayout";
// Components
import CalendarButton from "src/components/layouts/Calendar/CalendarButton";
// Data types
import { User } from "src/utils/datatypes/globalDataTypes";
// Utils
import {
getFullDate,
getFullTime,
getLDAPGeneralizedTime,
parseFullDateStringToUTCFormat,
} from "src/utils/utils";
// ipaObject Utils
import {
IPAParamDefinition,
getParamProperties,
updateIpaObject,
} from "src/utils/ipaObjectUtils";
import CalendarLayout from "src/components/layouts/Calendar/CalendarLayout";

const IpaCalendar = (props: IPAParamDefinition) => {
// Date and time picker (Calendar)
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
const [isTimeOpen, setIsTimeOpen] = React.useState(false);
const [valueDate, setValueDate] = React.useState("YYYY-MM-DD");
const [valueTime, setValueTime] = React.useState("HH:MM");
const times = Array.from(new Array(10), (_, i) => i + 10);
const defaultTime = "10:00";

const { readOnly } = getParamProperties(props);

// Initialize parameters got from the 'User' object
React.useEffect(() => {
if (props.ipaObject !== undefined) {
// Parse ipaObject into 'User' type to access the 'datetime' parameter
const user = props.ipaObject as unknown as User;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IpaCalendar component should not have any reference to a specific object type (in this case user). The raw value should be obtained the same way as in text input, e.g.:

const { required, readOnly, value, onChange } = getParamProperties(props);

After that you can extract the base64 value and change it into Date object. There is also no need to do it in an effect as it is simple conversion of value from props.

But the question is whether we should be doing this parsing here. I think that the conversion from base64 to Date or string (depending what we find more useful) should happen in apiToUser method in userUtils or any similar method that is used in rpc for other object types.

The reason is that in ipaObject should be of the same type/structure after changing it. Or in other words, it should be possible to set it to the same value and be regarded as a same value. Atm the source is object with base64 string and after modification it is string, thus this doesn't apply.

if (user[props.name] && user[props.name][0].__datetime__) {
// Parse to UTC format
const paramUtcDate = parseFullDateStringToUTCFormat(
user[props.name][0].__datetime__
);

// Get date
const fullDate = getFullDate(paramUtcDate);
setValueDate(fullDate);

// Get time
const fullTime = getFullTime(paramUtcDate);
setValueTime(fullTime);
}
}
}, [props.ipaObject]);

// 'onToggle' calendar function
const onToggleCalendar = () => {
setIsCalendarOpen(!isCalendarOpen);
setIsTimeOpen(false);
};

// 'onToggle' time function
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
const onToggleTime = (_ev: any) => {
setIsTimeOpen(!isTimeOpen);
setIsCalendarOpen(false);
};

// On selecting a date from the calendar
const onSelectCalendar = (newValueDate: Date) => {
const newValue = getFullDate(newValueDate);
setValueDate(newValue);

setIsCalendarOpen(!isCalendarOpen);
// setting default time when it is not picked
let valTime = valueTime;
if (valueTime === "HH:MM") {
setValueTime(defaultTime);
valTime = defaultTime;
}

// Convert to LDAP format
const newFullDate = new Date(newValue + " " + valTime);
const LDAPDate = getLDAPGeneralizedTime(newFullDate);

// Update 'ipaObject' with the new date
if (props.ipaObject !== undefined && props.onChange !== undefined) {
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
}
};

// On selecting a time from the dropdown
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onSelectTime = (ev: any) => {
const newTime = ev.target.value as string;

setValueTime(newTime);
setIsTimeOpen(!isTimeOpen);

const newFullDate = new Date(valueDate + " " + newTime);
const LDAPDate = getLDAPGeneralizedTime(newFullDate);

// Update 'ipaObject' with the new date
// - Parse to ISO format (to return to the "user_mod" API call)
if (props.ipaObject !== undefined && props.onChange !== undefined) {
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
}
};

const timeOptions = times.map((time) => (
<DropdownItem key={time} component="button" value={`${time}:00`}>
{`${time}:00`}
</DropdownItem>
));

const calendar = (
<CalendarMonth
date={new Date(valueDate)}
onChange={onSelectCalendar}
disabled={readOnly}
/>
);

const time = (
<DataTimePickerLayout
dropdownOnSelect={onSelectTime}
toggleAriaLabel="Toggle the time picker menu"
toggleIndicator={null}
toggleOnToggle={onToggleTime}
toggleStyle={{ padding: "6px 16px" }}
dropdownIsOpen={isTimeOpen}
dropdownItems={timeOptions}
/>
);

const calendarButton = (
<CalendarButton
ariaLabel="Toggle the calendar"
onClick={onToggleCalendar}
/>
);

return (
<CalendarLayout
name={props.name}
position="bottom"
bodyContent={calendar}
showClose={false}
isVisible={isCalendarOpen}
hasNoPadding={true}
hasAutoWidth={true}
textInputId="date-time"
textInputAriaLabel="date and time picker"
textInputValue={valueDate + " " + valueTime}
>
{readOnly ? <></> : calendarButton}
{readOnly ? <></> : time}
</CalendarLayout>
);
};

export default IpaCalendar;
46 changes: 46 additions & 0 deletions src/components/Form/IpaCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
// PatternFly
import { Checkbox } from "@patternfly/react-core";
// Utils
import {
IPAParamDefinitionCheckbox,
getParamPropertiesCheckBox,
} from "src/utils/ipaObjectUtils";

const IpaCheckbox = (props: IPAParamDefinitionCheckbox) => {
const { required, readOnly, onChange, className } =
getParamPropertiesCheckBox(props);
const [isChecked, setIsChecked] = React.useState(false);

React.useEffect(() => {
if (props.ipaObject !== undefined && props.value !== undefined) {
const checkboxesStateList = props.ipaObject[props.name] as string[];
if (checkboxesStateList !== undefined) {
const index = checkboxesStateList.indexOf(props.value);

if (index > -1) {
setIsChecked(true);
} else {
setIsChecked(false);
}
}
}
}, [props.ipaObject]);

return (
<Checkbox
id={props.id || ""}
name={props.name}
label={props.value}
onChange={onChange}
isRequired={required}
readOnly={readOnly}
isChecked={isChecked}
aria-label={props.name}
className={className}
isDisabled={readOnly}
/>
);
};

export default IpaCheckbox;
35 changes: 35 additions & 0 deletions src/components/Form/IpaSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
// PatternFly
import { Select, SelectOption, SelectVariant } from "@patternfly/react-core";
// Utils
import {
IPAParamDefinitionSelect,
getParamPropertiesSelect,
} from "src/utils/ipaObjectUtils";

const IpaSelect = (props: IPAParamDefinitionSelect) => {
const { required, readOnly, value } = getParamPropertiesSelect(props);

return (
<Select
id={props.id}
name={props.name}
variant={props.variant || SelectVariant.single}
aria-label={props.name}
onToggle={props.onToggle}
onSelect={props.onSelect}
selections={value?.toString()}
isOpen={props.isOpen}
aria-labelledby={props.ariaLabelledBy || props.id}
readOnly={readOnly}
isDisabled={readOnly}
required={required}
>
{props.elementsOptions.map((option, index) => (
<SelectOption key={index} value={option} />
))}
</Select>
);
};

export default IpaSelect;
1 change: 0 additions & 1 deletion src/components/Form/IpaTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const IpaTextInput = (props: IPAParamDefinition) => {

return (
<TextInput
id={props.name}
name={props.name}
value={convertToString(value)}
onChange={onChange}
Expand Down
93 changes: 93 additions & 0 deletions src/components/Form/IpaTextInputFromList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from "react";
// Patternfly
import { Flex, FlexItem } from "@patternfly/react-core";
// Layouts
import SecondaryButton from "../layouts/SecondaryButton";
// Data types
import { Metadata } from "src/utils/datatypes/globalDataTypes";
// Fields
import IpaTextInputWithId from "./IpaTextInputWithId";
// Hooks
import useAlerts from "src/hooks/useAlerts";
// ipaObject utils
import { getParamProperties } from "src/utils/ipaObjectUtils";

interface PropsToTextInputFromList {
name: string;
elementsList: string[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ipaObject: Record<string, any>;
metadata: Metadata;
onOpenModal: () => void;
onRemove: (idx: number) => void;
}

const IpaTextInputFromList = (props: PropsToTextInputFromList) => {
const [elementsList, setElementsList] = React.useState(props.elementsList);

// Get 'readOnly' to determine if the field has permissions to be edited
const { readOnly } = getParamProperties({
name: props.name,
ipaObject: props.ipaObject,
objectName: "user",
metadata: props.metadata,
});

// Alerts to show in the UI
const alerts = useAlerts();

React.useEffect(() => {
setElementsList(props.elementsList);
}, [props.elementsList]);

return (
<>
<alerts.ManagedAlerts />
<Flex direction={{ default: "column" }} name={props.name}>
{elementsList !== undefined &&
elementsList.map((element, idx) => (
<Flex direction={{ default: "row" }} key={idx} name="value">
<FlexItem
key={idx}
flex={{ default: "flex_1" }}
className="pf-u-ml-lg"
>
<IpaTextInputWithId
id={props.name + "-" + idx}
value={element}
name={props.name}
ipaObject={props.ipaObject}
objectName="user"
metadata={props.metadata}
idx={idx}
readOnly={true} // This field is always read-only
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor issue, but I think you get a build warning when a prop is explicitly set to true or false. A boolean prop is set or it isn't. Again, if it's not generating a warning then don't worry about it, but I've seen this trigger annoying messages. :-)

/>
</FlexItem>
<FlexItem
key={element + "-delete-button"}
order={{ default: "-1" }}
>
<SecondaryButton
name="remove"
onClickHandler={() => props.onRemove(idx)}
isDisabled={readOnly}
>
Delete
</SecondaryButton>
</FlexItem>
</Flex>
))}
</Flex>
<SecondaryButton
classname="pf-u-mt-md"
name="add"
onClickHandler={props.onOpenModal}
isDisabled={readOnly}
>
Add
</SecondaryButton>
</>
);
};

export default IpaTextInputFromList;
38 changes: 38 additions & 0 deletions src/components/Form/IpaTextInputWithId.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
// PatternFly
import { TextInput } from "@patternfly/react-core";
// Utils
import {
IPAParamDefinitionWithIndex,
convertToString,
getParamPropertiesWithIndex,
} from "src/utils/ipaObjectUtils";

const IpaTextInputWithId = (props: IPAParamDefinitionWithIndex) => {
const { required, readOnly, value, idx } = getParamPropertiesWithIndex(props);

const [paramValue, setParamValue] = React.useState("");

React.useEffect(() => {
if (props.value !== undefined) {
setParamValue(props.value);
} else {
setParamValue(convertToString(value));
}
}, [props.value, value]);

return (
<TextInput
key={idx}
id={props.id || props.name}
name={props.name}
value={paramValue}
type="text"
aria-label={props.name}
isRequired={required}
readOnlyVariant={readOnly ? "plain" : undefined}
/>
);
};

export default IpaTextInputWithId;
Loading