All files / app/components Timepicker.tsx

100% Statements 22/22
100% Branches 12/12
100% Functions 6/6
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 1105x 5x 5x 5x 5x 5x 5x 5x                                         405x 405x       82x     5x                           696x 696x                             39x 39x 39x     53x 53x                                                                       692x  
import { TimePicker } from "@mui/x-date-pickers";
import { useTranslation } from "i18n";
import { getMuiTimeFormat } from "i18n/datetimes";
import { GLOBAL } from "i18n/namespaces";
import React, { useMemo } from "react";
import { Control, Controller, UseControllerProps } from "react-hook-form";
import { Temporal } from "temporal-polyfill";
import dayjs, { Dayjs } from "utils/dayjs";
 
interface TimepickerProps {
  className?: string;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  control: Control<any>;
  defaultValue?: Temporal.PlainTime;
  error: boolean;
  helperText: React.ReactNode;
  id: string;
  rules?: UseControllerProps["rules"];
  label?: string;
  name: string;
  onPostChange?(value: Temporal.PlainTime | null): void;
  onAccept?(value: Temporal.PlainTime | null): void;
  testId?: string;
}
 
// Convert between our API's Temporal.PlainTime and MUI's expected Dayjs values.
// Use the browser timezone in case we compare to now, aka dayjs().
function temporalToDayjs(value: Temporal.PlainTime): Dayjs {
  const timeString = value.toString({ smallestUnit: "minute" });
  return dayjs(`1970-01-01T${timeString}`); // We don't care about the date, but dayjs needs one.
}
 
function dayjsToTemporal(value: Dayjs): Temporal.PlainTime {
  return Temporal.PlainTime.from(value.format("HH:mm"));
}
 
const Timepicker = ({
  className,
  control,
  defaultValue,
  error,
  helperText,
  id,
  rules,
  label,
  name,
  onPostChange,
  onAccept,
  testId,
}: TimepickerProps) => {
  const { t, i18n } = useTranslation([GLOBAL]);
  const format = useMemo(() => getMuiTimeFormat(i18n.language), [i18n.language]);
 
  return (
    <Controller
      control={control}
      defaultValue={defaultValue ?? null}
      name={name}
      rules={rules}
      render={({ field }) => (
        <TimePicker
          data-testid={testId}
          {...field}
          label={label}
          value={field.value ? temporalToDayjs(field.value) : null}
          onChange={(valueDayjs: Dayjs | null) => {
            const valueTemporal = valueDayjs && valueDayjs.isValid() ? dayjsToTemporal(valueDayjs) : null;
            field.onChange(valueTemporal);
            onPostChange?.(valueTemporal);
          }}
          onAccept={(valueDayjs: Dayjs | null) => {
            const valueTemporal = valueDayjs && valueDayjs.isValid() ? dayjsToTemporal(valueDayjs) : null;
            onAccept?.(valueTemporal);
          }}
          format={format}
          ampm={format.includes("a")} // Clock picker uses am/pm iff format also uses it
          slotProps={{
            textField: {
              fullWidth: true,
              id,
              error,
              helperText: <span data-testid={`${name}-helper-text`}>{helperText}</span>,
              variant: "standard",
              sx: {
                "& .MuiOutlinedInput-root": {
                  backgroundColor: "var(--mui-palette-primary-main)",
                  color: "var(--mui-palette-text-primary)",
                },
                "& .MuiPaper-root": {
                  backgroundColor: "var(--mui-palette-primary-main)",
                  color: "var(--mui-palette-text-primary)",
                },
              },
              slotProps: {
                inputLabel: { shrink: true },
                input: {
                  className,
                  "aria-label": t("global:change_time"),
                },
              },
            },
          }}
        />
      )}
    />
  );
};
 
export default Timepicker;