Загрузка данных
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>
);
};