Skip to content

Commit

Permalink
Date and date time field vqa updates (#2690)
Browse files Browse the repository at this point in the history
Fixes the ff vqa issues:
#2659 (comment)
#2659 (comment)
  • Loading branch information
finnar-bin authored Apr 22, 2024
1 parent ca4c427 commit 80315be
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 163 deletions.
319 changes: 175 additions & 144 deletions src/shell/components/FieldTypeDate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import {
DatePickerProps,
} from "@mui/x-date-pickers-pro";
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
import { memo, useEffect, useRef, useState } from "react";
import {
memo,
useEffect,
useRef,
useState,
useImperativeHandle,
forwardRef,
} from "react";
import Button from "@mui/material/Button";
import { Typography, Stack, Box, TextField } from "@mui/material";
import format from "date-fns/format";
Expand Down Expand Up @@ -43,6 +50,8 @@ const parseDateInput = (input: string): Date | null => {
const currentYear = new Date().getFullYear();

let [monthInput, dayInput, yearInput] = dateParts;
yearInput = yearInput.slice(0, 4);
dayInput = dayInput.slice(0, 2);
let month = months[monthInput.toLowerCase().slice(0, 3)];
if (isNaN(month)) {
month = currentMonth;
Expand All @@ -58,161 +67,183 @@ const parseDateInput = (input: string): Date | null => {
};

export const FieldTypeDate = memo(
({
required,
error,
slots,
onClear,
valueFormatPreview,
...props
}: FieldTypeDateProps) => {
const textFieldRef = useRef<HTMLInputElement>(null);
const [isOpen, setIsOpen] = useState(false);

/**
* Clear the input value
*/
const handleClear = () => {
if (props.onChange) props.onChange(null, null);
if (textFieldRef.current) textFieldRef.current.value = "";
onClear && onClear();
};

/**
* Open the date picker
*/
const handleOpen = () => {
setIsOpen(true);
};

useEffect(() => {
if (textFieldRef.current && isOpen) {
/**
* This Perform a check if there's no value set
* When the user clicks on the input field, set the value to the current date
*/
if (props.value === null) {
props.onChange(new Date(), null);
textFieldRef.current.value = format(new Date(), "MMM dd, yyyy");
textFieldRef.current.setSelectionRange(0, 3);
forwardRef(
(
{
required,
error,
slots,
onClear,
valueFormatPreview,
...props
}: FieldTypeDateProps,
ref
) => {
const textFieldRef = useRef<HTMLInputElement>(null);
const [isOpen, setIsOpen] = useState(false);

/**
* Clear the input value
*/
const handleClear = () => {
if (props.onChange) props.onChange(null, null);
if (textFieldRef.current) textFieldRef.current.value = "";
onClear && onClear();
};

/**
* Open the date picker
*/
const handleOpen = () => {
setIsOpen(true);
};

useEffect(() => {
if (textFieldRef.current && isOpen) {
/**
* This Perform a check if there's no value set
* When the user clicks on the input field, set the value to the current date
*/
if (props.value === null) {
props.onChange(new Date(), null);
textFieldRef.current.value = format(new Date(), "MMM dd, yyyy");
textFieldRef.current.setSelectionRange(0, 3);
}

/**
* Delay the focus to the input field to
* ensure the picker is open before focusing to the field
*/
setTimeout(() => {
textFieldRef.current?.focus();
});
}

/**
* Delay the focus to the input field to
* ensure the picker is open before focusing to the field
* This handles the case when the user selects a date from the picker
* directly and not use the input field to manually enter the date
*/
setTimeout(() => {
textFieldRef.current?.focus();
});
}
if (!isOpen && props.value) {
textFieldRef.current.value = format(props.value, "MMM dd, yyyy");
}
textFieldRef.current.blur();
}, [isOpen]);

/**
* This handles the case when the user selects a date from the picker
* directly and not use the input field to manually enter the date
* handles the case when the value is set from the parent component or db values
*/
if (!isOpen && props.value) {
textFieldRef.current.value = format(props.value, "MMM dd, yyyy");
}
textFieldRef.current.blur();
}, [isOpen]);

/**
* handles the case when the value is set from the parent component or db values
*/
useEffect(() => {
if (props.value) {
textFieldRef.current.value = format(props.value, "MMM dd, yyyy");
}
}, [props.value]);

return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Stack direction="row" gap={0.5} alignItems="center">
<Box maxWidth={160}>
<DatePicker
reduceAnimations
open={isOpen}
onClose={() => {
setIsOpen(false);
}}
{...props}
inputRef={textFieldRef}
disableHighlightToday={!!props.value}
slots={{
field: CustomField,
openPickerIcon: CalendarTodayRoundedIcon,
...slots,
}}
slotProps={{
desktopPaper: {
sx: {
mt: 1,

"& .MuiDateCalendar-root .MuiPickersSlideTransition-root": {
minHeight: 0,
pb: 2,
pt: 1.5,
useEffect(() => {
if (props.value) {
textFieldRef.current.value = format(props.value, "MMM dd, yyyy");
}
}, []);

useImperativeHandle(
ref,
() => {
return {
setDefaultDate() {
textFieldRef.current.value = format(new Date(), "MMM dd, yyyy");
},
};
},
[]
);

return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Stack direction="row" gap={0.5} alignItems="center">
<Box maxWidth={160}>
<DatePicker
reduceAnimations
open={isOpen}
onClose={() => {
setIsOpen(false);
}}
{...props}
inputRef={textFieldRef}
disableHighlightToday={!!props.value}
slots={{
field: CustomField,
openPickerIcon: CalendarTodayRoundedIcon,
...slots,
}}
slotProps={{
desktopPaper: {
sx: {
mt: 1,

"& .MuiDateCalendar-root .MuiPickersSlideTransition-root":
{
minHeight: 0,
pb: 2,
pt: 1.5,
},
},
},
},
field: {
//@ts-expect-error - OnClick type does not exist on fieldProps
onClick: handleOpen,
onFocus: handleOpen,
error,
onChange: (e: any) => {
const inputDate = e.target.value;
const parsedDate = parseDateInput(inputDate);

if (parsedDate) {
props.onChange(parsedDate, null);
}
field: {
//@ts-expect-error - OnClick type does not exist on fieldProps
onClick: handleOpen,
onFocus: handleOpen,
error,
onChange: (e: any) => {
const inputDate = e.target.value;
const parsedDate = parseDateInput(inputDate);

if (parsedDate) {
props.onChange(parsedDate, null);
}
},
onKeyDown: (evt: KeyboardEvent) => {
if (evt.key === "Enter") {
setIsOpen(false);
textFieldRef.current?.blur();
}

if (evt.key === "Tab") {
setIsOpen(false);
}
},
},
onKeyDown: (evt: KeyboardEvent) => {
if (evt.key === "Enter") {
setIsOpen(false);
textFieldRef.current?.blur();
}
inputAdornment: {
position: "start",
},
},
inputAdornment: {
position: "start",
},
openPickerButton: {
tabIndex: -1,
size: "small",
},
openPickerIcon: {
sx: {
fontSize: 20,
openPickerButton: {
tabIndex: -1,
size: "small",
},
},
}}
/>
</Box>

{!!slots?.timePicker && slots.timePicker}

<Button
data-cy="dateFieldClearButton"
color="inherit"
variant="text"
size="small"
sx={{ minWidth: 45 }}
onClick={handleClear}
>
Clear
</Button>
</Stack>
{(valueFormatPreview || props.value) && (
<Typography variant="body3" color="text.secondary" sx={{ mt: 0.5 }}>
Stored as{" "}
{valueFormatPreview ?? moment(props.value).format("yyyy-MM-DD")}
</Typography>
)}
</LocalizationProvider>
);
}
openPickerIcon: {
sx: {
fontSize: 20,
},
},
}}
/>
</Box>

{!!slots?.timePicker && slots.timePicker}

<Button
data-cy="dateFieldClearButton"
color="inherit"
variant="text"
size="small"
sx={{ minWidth: 45 }}
onClick={handleClear}
>
Clear
</Button>
</Stack>
{(valueFormatPreview || props.value) && (
<Typography variant="body3" color="text.secondary" sx={{ mt: 0.5 }}>
Stored as{" "}
{valueFormatPreview ?? moment(props.value).format("yyyy-MM-DD")}
</Typography>
)}
</LocalizationProvider>
);
}
)
);

function CustomField(props: any) {
Expand Down
Loading

0 comments on commit 80315be

Please sign in to comment.