Enterprise-CMCS/macpro-mako

View on GitHub
react-app/src/components/RHF/SlotField.tsx

Summary

Maintainability
A
1 hr
Test Coverage
C
73%
import {
  RHFComponentMap,
  RHFOption,
  RHFSlotProps,
  MultiselectOption,
} from "shared-types";
import { CalendarIcon } from "lucide-react";
import { format } from "date-fns";
import { cn } from "@/utils";
import { Popover, PopoverContent, PopoverTrigger } from "@/components";
import {
  DependencyWrapper,
  RHFFieldArray,
  RHFFormGroup,
  RHFSlot,
  RHFTextDisplay,
  ruleGenerator,
  sortFunctions,
  stringCompare,
} from ".";
import {
  Button,
  Calendar,
  Checkbox,
  FormControl,
  FormField,
  FormLabel,
  Input,
  Multiselect,
  RadioGroup,
  RadioGroupItem,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
  Switch,
  Textarea,
  Upload,
} from "@/components/Inputs";
import { useGetCounties } from "@/api";

type SlotFieldProps = RHFSlotProps & { control: any; field: any };
type SelectedSubsetProps = RHFOption & {
  control: any;
  parentId?: string;
};

export const SlotField = ({
  props,
  field,
  rhf,
  text,
  control,
  parentId,
  fields,
  name,
  horizontalLayout,
  index,
}: SlotFieldProps) => {
  const counties = useGetCounties();

  switch (rhf) {
    case "Input":
      return <Input {...props} {...field} aria-label={field.name} />;
    case "Textarea":
      return <Textarea {...props} {...field} aria-label={field.name} />;
    case "Switch":
      return <Switch {...props} {...field} aria-label={field.name} />;
    case "TextDisplay":
      return (
        <p {...props} data-testid={field.name}>
          <RHFTextDisplay text={text ?? "UNDEFINED TEXT FIELD"} index={index} />
        </p>
      );
    case "Upload":
      return (
        <Upload
          {...props}
          files={field?.value ?? []}
          setFiles={field.onChange}
        />
      );
    case "FieldArray":
      return (
        <RHFFieldArray
          control={control}
          name={name}
          rhf={rhf}
          fields={fields ?? []}
          parentId={parentId}
          {...props}
        />
      );
    case "Select": {
      let opts;
      switch (props?.apiCall) {
        case undefined:
          opts = props?.options?.sort((a, b) =>
            props.customSort
              ? sortFunctions[props.customSort](a.label, b.label)
              : stringCompare(a, b),
          );
          break;

        case "countySelect":
          opts =
            counties.sort((a, b) =>
              props.customSort
                ? sortFunctions[props.customSort](a.label, b.label)
                : stringCompare(a, b),
            ) || [];
          break;
      }

      return (
        <Select
          {...props}
          onValueChange={field.onChange}
          defaultValue={field.value}
        >
          <SelectTrigger {...props} aria-label={field.name}>
            <SelectValue {...props} />
          </SelectTrigger>
          <SelectContent className="overflow-auto max-h-60">
            {opts?.map((OPT) => (
              <SelectItem key={`OPT-${OPT.value}`} value={OPT.value}>
                {OPT.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    case "Multiselect": {
      const options = props?.options as MultiselectOption[];
      const value = field.value as string[];

      return (
        <Multiselect
          options={options}
          value={value}
          onChange={(selectedValues) => field.onChange(selectedValues)}
          {...props}
        />
      );
    }
    case "DatePicker":
      return (
        <FormControl>
          <Popover>
            <PopoverTrigger asChild>
              <Button
                variant={"outline"}
                className={cn(
                  "w-[240px] pl-3 text-left font-normal text-[#212121] justify-start",
                  !field.value && "text-muted-foreground",
                )}
              >
                <CalendarIcon className="h-4" />
                {field.value ? (
                  format(new Date(field.value), "MM/dd/yyyy")
                ) : (
                  <span>Pick a date</span>
                )}
              </Button>
            </PopoverTrigger>
            <PopoverContent className="w-auto p-0" align="start">
              <Calendar
                {...props}
                mode="single"
                selected={field.value && new Date(field.value)}
                defaultMonth={field.value && new Date(field.value)}
                onSelect={(date) => field.onChange(date)}
                initialFocus
              />
            </PopoverContent>
          </Popover>
        </FormControl>
      );
    case "Checkbox":
      return (
        <div className="flex flex-col gap-3">
          {(props as RHFComponentMap["Checkbox"]).options.map((OPT) => (
            <DependencyWrapper
              name={OPT.value}
              dependency={OPT.dependency}
              changeMethod={field.onChange}
              parentValue={field.value}
              key={`CHECK-dw-${OPT.value}`}
            >
              <div key={`CHECK-${OPT.value}`}>
                <Checkbox
                  label={OPT.label}
                  value={OPT.value}
                  checked={field.value?.includes(OPT.value)}
                  styledLabel={
                    <RHFTextDisplay
                      text={(OPT.styledLabel || OPT.label) as string}
                    />
                  }
                  onCheckedChange={(c) => {
                    const filtered =
                      field.value?.filter((f: unknown) => f !== OPT.value) ||
                      [];
                    if (!c) return field.onChange(filtered);
                    field.onChange([...filtered, OPT.value]);
                  }}
                  dependency={OPT.dependency}
                  parentValue={field.value}
                  changeMethod={field.onChange}
                  aria-label={field.name}
                  optionlabelClassName={OPT.optionlabelClassName}
                />
                {field.value?.includes(OPT.value) && (
                  <OptChildren {...OPT} parentId={parentId} control={control} />
                )}
              </div>
            </DependencyWrapper>
          ))}
        </div>
      );
    case "Radio":
      return (
        <RadioGroup
          onValueChange={field.onChange}
          defaultValue={field.value}
          className={`flex  ${
            horizontalLayout ? "pl-5 gap-5" : "flex-col space-y-6"
          }`}
        >
          {(props as RHFComponentMap["Radio"]).options.map((OPT) => {
            return (
              <div key={`OPT-${OPT.value}`} className="flex flex-col">
                <div className="flex gap-2">
                  <RadioGroupItem
                    value={OPT.value}
                    id={OPT.value}
                    aria-label={OPT.value}
                  />
                  {
                    <FormLabel className="font-normal" htmlFor={OPT.value}>
                      <RHFTextDisplay
                        text={(OPT.styledLabel || OPT.label) as string}
                      />
                    </FormLabel>
                  }
                </div>
                {field.value?.includes(OPT.value) && (
                  <OptChildren {...OPT} parentId={parentId} control={control} />
                )}
              </div>
            );
          })}
        </RadioGroup>
      );
    case "WrappedGroup":
      return (
        <div className={props?.wrapperClassName}>
          {fields?.map((S, i) => {
            return (
              <DependencyWrapper {...S}>
                <FormField
                  key={`wrappedSlot-${i}`}
                  control={control}
                  name={parentId + S.name}
                  rules={ruleGenerator(S.rules, S.addtnlRules)}
                  render={RHFSlot({ ...S, control, parentId })}
                />
              </DependencyWrapper>
            );
          })}
        </div>
      );
    case "Divider":
      return (
        <div
          className={cn(
            "w-full border-slate-400 border-2",
            props?.wrapperClassName,
          )}
        />
      );
  }
};

export const OptChildren = ({
  form,
  slots,
  control,
  parentId,
}: SelectedSubsetProps) => {
  const childClasses =
    "ml-[0.6rem] mt-4 pl-6 px-4 space-y-6 border-l-4 border-l-primary";

  return (
    <>
      {form && (
        <div className={childClasses}>
          {form.map((FORM, index) => (
            <div key={`rhf-form-${index}-${FORM.description}`}>
              <RHFFormGroup
                form={FORM}
                control={control}
                parentId={parentId}
                className="py-0"
              />
            </div>
          ))}
        </div>
      )}
      {slots && (
        <div className={childClasses}>
          {slots.map((SLOT, index) => (
            <div key={SLOT.name + index}>
              <FormField
                control={control}
                name={parentId + SLOT.name}
                rules={ruleGenerator(SLOT.rules, SLOT.addtnlRules)}
                render={RHFSlot({
                  ...SLOT,
                  control,
                  parentId,
                  name: parentId + SLOT.name,
                })}
              />
            </div>
          ))}
        </div>
      )}
    </>
  );
};