Загрузка данных


import React, { FC, useMemo, useEffect, useCallback, useState } from "react";
import {
  Accordion,
  AccordionDetails,
  AccordionSummary,
  Box,
  Typography,
} from "@mui/material";
import { Switch } from "@components";
import styles from "./FormGroup.module.scss";
import { FormField } from "@shared/ui/components/form/form-field/FormField";
import { ExpandMore } from "@mui/icons-material";
import { AttributeDataTag } from "../../../../../examples/object-details/AttributeDataTag";
import { sortByFieldOrder } from "@shared/utils/sortByFieldOrder";
import { AttributeDataDto } from "@gnr/api";
import {
  NestedAttributeData,
  RootAttributeData,
} from "@lib/dtos/AttributeDataDto.ts";
import { SimpleValueTag } from "@lib/tags/SimpleValueTag";

type Props = {
  readonly fieldGroup: AttributeDataDto;
  readonly objectId: number;
  readonly expanded?: boolean;
  readonly onExpandChange?: (
    event: React.SyntheticEvent,
    isExpanded: boolean,
  ) => void;
  readonly setUsedRoots?: React.Dispatch<
    React.SetStateAction<
      { attributeId: number; value: { tag: string; value: string } }[]
    >
  >;
  readonly zone: number | null;
  readonly type: "filter" | "update" | "create";
  readonly hideFilled?: boolean;
};

export const FormGroup: React.FC<Props> = ({
  fieldGroup,
  objectId,
  expanded,
  onExpandChange,
  setUsedRoots,
  zone,
  type,
  hideFilled,
}) => {
  const group = useMemo(
    () =>
      groupGeneration({
        objectId,
        expanded,
        onExpandChange,
        setUsedRoots,
        zone,
        type,
        fieldGroup,
        hideFilled,
      }),
    [
      expanded,
      fieldGroup,
      objectId,
      onExpandChange,
      setUsedRoots,
      type,
      zone,
      hideFilled,
    ],
  );

  const hasVisibleContent = useMemo(() => {
    const checkVisibleChildren = (group: AttributeDataDto): boolean => {
      if (
        zone !== null &&
        group.tag === "simple" &&
        group.responsibilityZone !== zone
      ) {
        return false;
      }

      if (group.tag === "simple" && hideFilled) {
        return AttributeDataTag.matchWithArg({
          root: () => true,
          nested: () => true,
          simple: (v) => {
            return SimpleValueTag.matchWithArg({
              boolean: (v) => !v.value,
              string: (v) => !v.value,
              number: (v) => !v.value,
              nullableBool: (v) => !v.value,
              file: (v) => !v.value,
              webLink: (v) => !v.url || !v.name,
              image: (v) => !v.value,
              money: (v) => !v.value,
              date: (v) => !v.value,
              htmlText: (v) => !v.value,
              categoryLink: (v) => !v.value,
              objectAttributeLink: (v) => !v.linkPath,
              pluralChoiceListItem: (v) =>
                v.value.every((val) => !val.isActive),
              singleChoiceListItem: (v) =>
                !v.value?.some((val) => val.isActive),
              linkedObjectList: (v) => !v.value,
            })(v.value);
          },
        })(group);
      }

      if (group.tag === "root" || group.tag === "nested") {
        return (
          // @ts-ignore
          group.children?.some((child: AttributeDataDto) =>
            checkVisibleChildren(child),
          ) || false
        );
      }

      return true;
    };

    return checkVisibleChildren(fieldGroup);
  }, [fieldGroup, zone, hideFilled]);

  if (!hasVisibleContent) {
    return null;
  }

  return (
    <Box className={styles.formGroup}>
      <Box className={styles.formFields}>{group}</Box>
    </Box>
  );
};

function groupGeneration({
  objectId,
  expanded,
  onExpandChange,
  setUsedRoots,
  zone,
  type,
  fieldGroup,
  hideFilled,
}: Props) {
  return AttributeDataTag.matchWithArg({
    root: (v) => (
      <RootComponent
        key={v.name}
        children={v.children}
        name={v.name}
        objectId={objectId}
        expanded={expanded}
        onExpandChange={onExpandChange}
        v={v}
        setUsedRoots={setUsedRoots}
        zone={zone}
        type={type}
        hideFilled={hideFilled}
      />
    ),
    simple: (v) => {
      return (
        <FormField
          key={v.name}
          value={v.value}
          name={v.name}
          id={v.id}
          objectId={objectId}
          targetCategoryId={v.targetCategoryId}
          type={type}
          required={type !== "filter" ? (v.required ?? false) : false}
        />
      );
    },
    nested: (v) => (
      <NestedComponent
        name={v.name}
        children={v.children}
        objectId={objectId}
        zone={zone}
        type={type}
        v={v}
        setUsedRoots={setUsedRoots}
        hideFilled={hideFilled}
      />
    ),
  })(fieldGroup);
}

type RootProps = {
  readonly name: string;
  readonly children: AttributeDataDto[];
  readonly objectId: number;
  readonly expanded?: boolean;
  readonly onExpandChange?: (
    event: React.SyntheticEvent,
    isExpanded: boolean,
  ) => void;
  readonly v: RootAttributeData;
  readonly setUsedRoots?: React.Dispatch<
    React.SetStateAction<
      { attributeId: number; value: { tag: string; value: string } }[]
    >
  >;
  readonly zone: number | null;
  readonly type: "filter" | "update" | "create";
  readonly hideFilled?: boolean;
};

const RootComponent: FC<RootProps> = ({
  name,
  children,
  objectId,
  expanded,
  onExpandChange,
  v,
  setUsedRoots,
  zone,
  type,
  hideFilled,
}) => {
  const sortedChildren = useMemo(() => {
    const filtered =
      zone !== null
        ? children?.filter((child) => {
            if (child.tag === "simple") {
              return child.responsibilityZone === zone;
            }
            return true;
          }) || []
        : children || [];
    return filtered.slice().sort(sortByFieldOrder);
  }, [children, zone]);

  useEffect(() => {
    if (type === "create" && v.id !== 0 && setUsedRoots) {
      setUsedRoots((prev) => {
        const exists = prev.some((item) => item.attributeId === v.id);
        if (!exists) {
          return [
            ...prev,
            {
              attributeId: v.id,
              value: {
                tag: "boolean",
                value: "True",
              },
            },
          ];
        }
        return prev;
      });
    }
  }, [type, v.id, setUsedRoots]);

  const onSwitchUseRoot = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const checked = event.target.checked;
      if (setUsedRoots) {
        setUsedRoots((prev) => {
          const exists = prev.some((item) => item.attributeId === v.id);
          if (exists) {
            return prev.map((item) =>
              item.attributeId === v.id
                ? {
                    attributeId: item.attributeId,
                    value: {
                      tag: "boolean",
                      value: checked.toString(),
                    },
                  }
                : item,
            );
          } else {
            return [
              ...prev,
              {
                attributeId: v.id,
                value: {
                  tag: "boolean",
                  value: checked.toString(),
                },
              },
            ];
          }
        });
      }
    },
    [setUsedRoots, v.id],
  );

  return (
    <Accordion elevation={0} expanded={expanded} onChange={onExpandChange}>
      <AccordionSummary
        expandIcon={<ExpandMore />}
        className={styles.titleBlock}
      >
        <Box className={styles.titleBlockWithSwitch}>
          <Typography className={styles.title}>{name}</Typography>
          {v.id !== 0 && type !== "filter" && (
            <Switch
              label="Использовать"
              onChange={onSwitchUseRoot}
              onClick={(e) => e.stopPropagation()}
              defaultChecked={
                // @ts-ignore FIXME
                type === "create" ? true : v.value?.value?.value === "True"
              }
            />
          )}
        </Box>
      </AccordionSummary>
      <AccordionDetails className={styles.accordionDetail}>
        {expanded && sortedChildren.length > 0
          ? sortedChildren.map((i) => (
              <FormGroup
                key={i.id}
                fieldGroup={i}
                objectId={objectId}
                zone={zone}
                type={type}
                setUsedRoots={setUsedRoots}
                hideFilled={hideFilled}
              />
            ))
          : null}
      </AccordionDetails>
    </Accordion>
  );
};

type NestedProps = {
  readonly name: string;
  readonly children: AttributeDataDto[];
  readonly objectId: number;
  readonly zone: number | null;
  readonly type: "filter" | "update" | "create";
  readonly v: NestedAttributeData;
  readonly setUsedRoots?: React.Dispatch<
    React.SetStateAction<
      { attributeId: number; value: { tag: string; value: string } }[]
    >
  >;
  readonly hideFilled?: boolean;
};

const NestedComponent: FC<NestedProps> = ({
  name,
  children,
  objectId,
  zone,
  type,
  v,
  setUsedRoots,
  hideFilled,
}) => {
  const [expanded, setExpanded] = useState(false);

  const sortedChildren = useMemo(() => {
    const filtered =
      zone !== null
        ? children?.filter((child) => {
            if (child.tag === "simple") {
              return child.responsibilityZone === zone;
            }
            return true;
          }) || []
        : children || [];
    return filtered.slice().sort(sortByFieldOrder);
  }, [children, zone]);

  useEffect(() => {
    if (type === "create" && v.id !== 0 && setUsedRoots) {
      setUsedRoots((prev) => {
        const exists = prev.some((item) => item.attributeId === v.id);
        if (!exists) {
          return [
            ...prev,
            {
              attributeId: v.id,
              value: {
                tag: "boolean",
                value: "True",
              },
            },
          ];
        }
        return prev;
      });
    }
  }, [type, v.id, setUsedRoots]);

  const onSwitchUseRoot = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const checked = event.target.checked;
      if (setUsedRoots) {
        setUsedRoots((prev) => {
          const exists = prev.some((item) => item.attributeId === v.id);
          if (exists) {
            return prev.map((item) =>
              item.attributeId === v.id
                ? {
                    attributeId: item.attributeId,
                    value: {
                      tag: "boolean",
                      value: checked.toString(),
                    },
                  }
                : item,
            );
          } else {
            return [
              ...prev,
              {
                attributeId: v.id,
                value: {
                  tag: "boolean",
                  value: checked.toString(),
                },
              },
            ];
          }
        });
      }
    },
    [setUsedRoots, v.id],
  );

  return (
    <Accordion
      className={styles.toggleSection}
      expanded={expanded}
      onChange={(_, isExpanded) => setExpanded(isExpanded)}
    >
      <AccordionSummary expandIcon={<ExpandMore />}>
        <Box className={styles.titleBlockWithSwitch}>
          <Typography className={styles.toggleSectionTitle}>{name}</Typography>
          {v.id !== 0 && type !== "filter" && (
            <Switch
              label="Использовать"
              onChange={onSwitchUseRoot}
              onClick={(e) => e.stopPropagation()}
              defaultChecked={
                // @ts-ignore FIXME
                type === "create" ? true : v.value?.value?.value === "True"
              }
            />
          )}
        </Box>
      </AccordionSummary>
      <AccordionDetails className={styles.toggleSectionContent}>
        {expanded && sortedChildren.length > 0
          ? sortedChildren.map((i) => (
              <FormGroup
                key={i.id}
                fieldGroup={i}
                objectId={objectId}
                zone={zone}
                type={type}
                setUsedRoots={setUsedRoots}
                hideFilled={hideFilled}
              />
            ))
          : null}
      </AccordionDetails>
    </Accordion>
  );
};