Skip to content

Commit 055e291

Browse files
committed
feat: 어드민에서의 many-to-many 관계를 위한 M2MSelect 컴포넌트 추가
1 parent eeb5643 commit 055e291

File tree

1 file changed

+78
-2
lines changed

1 file changed

+78
-2
lines changed

apps/pyconkr-admin/src/components/layouts/admin_editor.tsx

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ import {
44
Box,
55
Button,
66
ButtonProps,
7+
Chip,
78
CircularProgress,
9+
FormControl,
810
IconButton,
11+
InputLabel,
12+
MenuItem,
13+
OutlinedSelectProps,
14+
Select,
915
Stack,
1016
Tab,
1117
Table,
@@ -18,10 +24,11 @@ import {
1824
} from "@mui/material";
1925
import Form, { IChangeEvent } from "@rjsf/core";
2026
import MuiForm from "@rjsf/mui";
21-
import { Field, RJSFSchema, UiSchema } from "@rjsf/utils";
27+
import { Field, FieldProps, RJSFSchema, UiSchema } from "@rjsf/utils";
2228
import { customizeValidator } from "@rjsf/validator-ajv8";
2329
import { ErrorBoundary, Suspense } from "@suspensive/react";
2430
import AjvDraft04 from "ajv-draft-04";
31+
import { JSONSchema7 } from "json-schema";
2532
import * as React from "react";
2633
import { useNavigate, useParams } from "react-router-dom";
2734
import * as R from "remeda";
@@ -66,6 +73,75 @@ const FileField: Field = (p) => (
6673
/>
6774
);
6875

76+
type DescriptedEnum = { const: string; title: string };
77+
type DescriptedEnumObject = Record<string, DescriptedEnum>;
78+
79+
const SelectdChipRenderer: React.FC<{ selectable: DescriptedEnumObject; selected: string[] }> = ({ selectable, selected }) => {
80+
const children = selected.map((v) => <Chip key={v} label={selectable[v].title || ""} />);
81+
return <Stack sx={{ flexWrap: "wrap" }} direction="row" spacing={0.5} children={children} />;
82+
};
83+
84+
const fieldPropsToSelectedProps = (props: FieldProps): OutlinedSelectProps & { defaultValue: string[] } => {
85+
const {
86+
name,
87+
formData,
88+
autofocus: autoFocus,
89+
readonly: readOnly,
90+
onFocus: rawOnFocus,
91+
onBlur: rawOnBlur,
92+
onChange: rawOnChange,
93+
94+
schema,
95+
errorSchema,
96+
uiSchema,
97+
idSchema,
98+
formContext,
99+
wasPropertyKeyModified,
100+
registry,
101+
rawErrors,
102+
hideError,
103+
idPrefix,
104+
idSeparator,
105+
color,
106+
...rest
107+
} = props;
108+
const data = {
109+
schema,
110+
errorSchema,
111+
uiSchema,
112+
idSchema,
113+
formContext,
114+
wasPropertyKeyModified,
115+
registry,
116+
rawErrors,
117+
hideError,
118+
idPrefix,
119+
idSeparator,
120+
};
121+
const onFocus = (event: React.FocusEvent<HTMLInputElement>) => rawOnFocus(event.currentTarget.name, event.currentTarget.value);
122+
const onBlur = (event: React.FocusEvent<HTMLInputElement>) => rawOnBlur(event.currentTarget.name, event.currentTarget.value);
123+
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => rawOnChange(event.target.value, undefined, event.target.name);
124+
const sx: OutlinedSelectProps["sx"] = color ? { color, borderColor: color } : {};
125+
const defaultValue = (formData ? (R.isArray(formData) ? formData : [formData.toString()]) : []) as string[];
126+
return R.addProp({ ...rest, name, label: name, defaultValue, autoFocus, readOnly, onFocus, onBlur, onChange, sx }, "data-rjsf", data);
127+
};
128+
129+
const M2MSelect: Field = ErrorBoundary.with(
130+
{ fallback: Common.Components.ErrorFallback },
131+
Suspense.with({ fallback: <CircularProgress /> }, (props) => {
132+
const selectable = (props.schema.items as JSONSchema7).oneOf as DescriptedEnum[];
133+
const selectableListObj: DescriptedEnumObject = selectable.reduce((a, i) => ({ ...a, [i.const]: i }), {} as DescriptedEnumObject);
134+
const children = selectable.map((i) => <MenuItem key={i.const} value={i.const} children={i.title || i.const} />);
135+
const selectRenderer = (selected: string[]) => <SelectdChipRenderer selectable={selectableListObj} selected={selected} />;
136+
return (
137+
<FormControl fullWidth>
138+
<InputLabel id={`${props.name}-label`} children={props.name} />
139+
<Select {...fieldPropsToSelectedProps(props)} children={children} multiple fullWidth renderValue={selectRenderer} />
140+
</FormControl>
141+
);
142+
})
143+
);
144+
69145
type ReadOnlyValueFieldStateType = {
70146
loading: boolean;
71147
blob: Blob | null;
@@ -293,7 +369,7 @@ const InnerAdminEditor: React.FC<AppResourceIdType & AdminEditorPropsType> = Err
293369
onSubmit={onSubmitFunc}
294370
disabled={disabled}
295371
showErrorList={false}
296-
fields={{ file: FileField }}
372+
fields={{ file: FileField, m2m_select: M2MSelect }}
297373
/>
298374
</Box>
299375
</Stack>

0 commit comments

Comments
 (0)