@pautena/react-design-system 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +13 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/value-displays/index.d.ts +1 -0
- package/dist/cjs/types/components/value-displays/value-datetime/index.d.ts +1 -0
- package/dist/cjs/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
- package/dist/cjs/types/generators/generators.mock.d.ts +9 -5
- package/dist/cjs/types/generators/generators.model.d.ts +25 -1
- package/dist/cjs/types/generators/model-router/index.d.ts +1 -0
- package/dist/cjs/types/generators/model-router/model-router.types.d.ts +1 -0
- package/dist/cjs/types/generators/model-router/screens/details-screen.d.ts +1 -1
- package/dist/cjs/types/hooks/index.d.ts +1 -0
- package/dist/cjs/types/hooks/routing/index.d.ts +1 -0
- package/dist/cjs/types/hooks/routing/routing.hooks.d.ts +5 -0
- package/dist/cjs/types/providers/notification-center/index.d.ts +1 -0
- package/dist/cjs/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
- package/dist/esm/index.js +13 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/value-displays/index.d.ts +1 -0
- package/dist/esm/types/components/value-displays/value-datetime/index.d.ts +1 -0
- package/dist/esm/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
- package/dist/esm/types/generators/generators.mock.d.ts +9 -5
- package/dist/esm/types/generators/generators.model.d.ts +25 -1
- package/dist/esm/types/generators/model-router/index.d.ts +1 -0
- package/dist/esm/types/generators/model-router/model-router.types.d.ts +1 -0
- package/dist/esm/types/generators/model-router/screens/details-screen.d.ts +1 -1
- package/dist/esm/types/hooks/index.d.ts +1 -0
- package/dist/esm/types/hooks/routing/index.d.ts +1 -0
- package/dist/esm/types/hooks/routing/routing.hooks.d.ts +5 -0
- package/dist/esm/types/providers/notification-center/index.d.ts +1 -0
- package/dist/esm/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
- package/dist/index.d.ts +54 -2
- package/package.json +6 -2
- package/src/components/value-displays/index.ts +1 -0
- package/src/components/value-displays/value-datetime/index.ts +1 -0
- package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +21 -0
- package/src/components/value-displays/value-datetime/value-datetime.test.tsx +23 -0
- package/src/components/value-displays/value-datetime/value-datetime.tsx +40 -0
- package/src/components/value-displays/value-text/{value-test.test.tsx → value-text.test.tsx} +0 -0
- package/src/generators/generators.mock.ts +56 -17
- package/src/generators/generators.model.ts +39 -1
- package/src/generators/model-form/model-form.stories.tsx +2 -2
- package/src/generators/model-form/model-form.test.tsx +39 -22
- package/src/generators/model-form/model-form.tsx +220 -33
- package/src/generators/model-router/index.ts +1 -0
- package/src/generators/model-router/model-router.test.tsx +338 -70
- package/src/generators/model-router/model-router.tsx +1 -1
- package/src/generators/model-router/model-router.types.ts +4 -0
- package/src/generators/model-router/screens/add-screen.tsx +16 -20
- package/src/generators/model-router/screens/details-screen.tsx +3 -2
- package/src/generators/model-router/screens/list-screen.tsx +17 -0
- package/src/generators/model-router/screens/update-screen.tsx +22 -13
- package/src/generators/model-router/stories/model-router.stories.tsx +54 -3
- package/src/generators/object-details/object-details.tsx +5 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/routing/index.ts +1 -0
- package/src/hooks/routing/routing.hooks.ts +23 -0
- package/src/hooks/routing/routing.test.tsx +83 -0
- package/src/providers/notification-center/index.ts +1 -0
- package/src/providers/notification-center/notification-center.hooks.ts +23 -0
- package/src/providers/notification-center/notification-center.test.tsx +87 -1
- package/src/storybook.tsx +10 -0
- package/src/tests/actions.ts +43 -0
- package/src/tests/assertions.ts +75 -1
- package/src/tests/index.ts +1 -0
- package/src/tests/testing-library.tsx +5 -1
|
@@ -5,8 +5,19 @@ import {
|
|
|
5
5
|
expectModelFieldInputValue,
|
|
6
6
|
render,
|
|
7
7
|
screen,
|
|
8
|
+
selectOption,
|
|
9
|
+
selectOptions,
|
|
10
|
+
pickDatetime,
|
|
11
|
+
expectToHaveBeenCalledOnceWithMockInstance,
|
|
8
12
|
} from "../../tests";
|
|
9
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
BirthDateFormat,
|
|
15
|
+
createModelInstance,
|
|
16
|
+
MockInstance,
|
|
17
|
+
mockModel,
|
|
18
|
+
ReturnTimeFormat,
|
|
19
|
+
TradeDateFormat,
|
|
20
|
+
} from "../generators.mock";
|
|
10
21
|
import userEvent from "@testing-library/user-event";
|
|
11
22
|
|
|
12
23
|
describe("ModelForm", () => {
|
|
@@ -47,54 +58,60 @@ describe("ModelForm", () => {
|
|
|
47
58
|
|
|
48
59
|
it("would call onSubmit if I fullfill all inputs and press the submit button", async () => {
|
|
49
60
|
const { onSubmit } = renderComponent();
|
|
61
|
+
const birthDate = new Date(2047, 11, 26);
|
|
62
|
+
const returnTime = new Date(1970, 0, 1, 10, 12);
|
|
63
|
+
const tradeDate = new Date(2047, 11, 26, 12, 50);
|
|
50
64
|
|
|
51
65
|
await userEvent.type(screen.getByRole("textbox", { name: "Id" }), "Id-1");
|
|
52
66
|
await userEvent.type(screen.getByRole("textbox", { name: /first name/i }), "Karianne");
|
|
53
67
|
await userEvent.type(screen.getByRole("textbox", { name: /middle name/i }), "Noah");
|
|
54
68
|
await userEvent.type(screen.getByRole("textbox", { name: /last name/i }), "Gorczany");
|
|
55
|
-
await
|
|
69
|
+
await selectOption(screen.getByRole("button", { name: /gender/i }), "Cis Man");
|
|
56
70
|
await userEvent.type(screen.getByRole("spinbutton", { name: /age/i }), "37");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
61
|
-
await userEvent.type(screen.getByRole("textbox", { name: /model/i }), "Spyder");
|
|
62
|
-
await userEvent.type(screen.getByRole("textbox", { name: /manufacturer/i }), "Bugatti");
|
|
71
|
+
pickDatetime(screen.getByRole("textbox", { name: /birth date/i }), birthDate, BirthDateFormat);
|
|
72
|
+
await selectOption(screen.getByRole("button", { name: /model/i }), "Spyder");
|
|
73
|
+
await selectOption(screen.getByRole("button", { name: /manufacturer/i }), "Bugatti");
|
|
63
74
|
await userEvent.type(screen.getByRole("textbox", { name: /color/i }), "red");
|
|
64
|
-
await
|
|
75
|
+
await selectOptions(screen.getByRole("button", { name: /type/i }), [
|
|
76
|
+
"Coupe",
|
|
77
|
+
"Hatchback",
|
|
78
|
+
"Minivan",
|
|
79
|
+
]);
|
|
65
80
|
await userEvent.type(screen.getByRole("textbox", { name: /vin/i }), "46N6UE4VJ2XL28828");
|
|
66
81
|
await userEvent.type(screen.getByRole("textbox", { name: /vrm/i }), "NE51AFH");
|
|
82
|
+
pickDatetime(
|
|
83
|
+
screen.getByRole("textbox", { name: /return time/i }),
|
|
84
|
+
returnTime,
|
|
85
|
+
ReturnTimeFormat,
|
|
86
|
+
);
|
|
67
87
|
await userEvent.type(screen.getByRole("spinbutton", { name: /q/i }), "9");
|
|
68
|
-
await userEvent.
|
|
88
|
+
await userEvent.click(screen.getByRole("checkbox", { name: /available/i }));
|
|
69
89
|
await userEvent.type(screen.getByRole("textbox", { name: /currency/i }), "mxn");
|
|
70
|
-
|
|
71
|
-
screen.getByRole("textbox", { name: /trade date/i }),
|
|
72
|
-
"Thu Jul 21 2022 22:44:10",
|
|
73
|
-
);
|
|
90
|
+
pickDatetime(screen.getByRole("textbox", { name: /trade date/i }), tradeDate, TradeDateFormat);
|
|
74
91
|
|
|
75
92
|
await userEvent.click(screen.getByRole("button", { name: /save/i }));
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
expect(onSubmit).toHaveBeenCalledWith({
|
|
94
|
+
expectToHaveBeenCalledOnceWithMockInstance(onSubmit, {
|
|
79
95
|
id: "Id-1",
|
|
80
96
|
firstName: "Karianne",
|
|
81
97
|
middleName: "Noah",
|
|
82
98
|
lastName: "Gorczany",
|
|
83
99
|
gender: "Cis Man",
|
|
84
|
-
age:
|
|
85
|
-
birthDate
|
|
100
|
+
age: 37,
|
|
101
|
+
birthDate,
|
|
86
102
|
car: {
|
|
87
103
|
model: "Spyder",
|
|
88
104
|
manufacturer: "Bugatti",
|
|
89
105
|
color: "red",
|
|
90
|
-
type: "
|
|
106
|
+
type: ["Coupe", "Hatchback", "Minivan"],
|
|
91
107
|
vin: "46N6UE4VJ2XL28828",
|
|
92
108
|
vrm: "NE51AFH",
|
|
109
|
+
returnTime,
|
|
93
110
|
},
|
|
94
|
-
quantity:
|
|
95
|
-
available:
|
|
111
|
+
quantity: 9,
|
|
112
|
+
available: true,
|
|
96
113
|
currency: "mxn",
|
|
97
|
-
tradeDate
|
|
114
|
+
tradeDate,
|
|
98
115
|
});
|
|
99
116
|
});
|
|
100
117
|
});
|
|
@@ -1,8 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
4
|
+
Checkbox,
|
|
5
|
+
FormControl,
|
|
6
|
+
FormControlLabel,
|
|
7
|
+
Grid,
|
|
8
|
+
InputLabel,
|
|
9
|
+
ListItemText,
|
|
10
|
+
MenuItem,
|
|
11
|
+
Paper,
|
|
12
|
+
Select,
|
|
13
|
+
SelectChangeEvent,
|
|
14
|
+
TextField,
|
|
15
|
+
Typography,
|
|
16
|
+
} from "@mui/material";
|
|
17
|
+
import { DesktopDatePicker, TimePicker, DateTimePicker } from "@mui/x-date-pickers";
|
|
18
|
+
import React, { ChangeEvent, FormEvent, ReactElement, useMemo } from "react";
|
|
3
19
|
import { useState } from "react";
|
|
4
20
|
import { useGetDefaultThemeColor } from "../../utils/theme";
|
|
5
|
-
import { Model, ModelField, BasicModelInstance } from "../generators.model";
|
|
21
|
+
import { Model, ModelField, BasicModelInstance, ModelFieldTypes } from "../generators.model";
|
|
22
|
+
|
|
23
|
+
const InitialStateZeroValue: Record<string, any> = {
|
|
24
|
+
string: undefined,
|
|
25
|
+
number: undefined,
|
|
26
|
+
boolean: false,
|
|
27
|
+
enum: "",
|
|
28
|
+
multienum: [],
|
|
29
|
+
date: "01/01/1970",
|
|
30
|
+
group: {},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getFieldInitialState = <T extends BasicModelInstance>(
|
|
34
|
+
field: ModelField,
|
|
35
|
+
initialValues: T | undefined,
|
|
36
|
+
) => {
|
|
37
|
+
return initialValues ? initialValues[field.id] : InitialStateZeroValue[field.type];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getValuesInitialState = <T extends BasicModelInstance>(
|
|
41
|
+
model: Model,
|
|
42
|
+
initialValues: T | undefined,
|
|
43
|
+
): T => {
|
|
44
|
+
const obj = {} as any;
|
|
45
|
+
|
|
46
|
+
model.fields.forEach((field) => {
|
|
47
|
+
let value: any;
|
|
48
|
+
if (field.type === "group") {
|
|
49
|
+
value = {};
|
|
50
|
+
field.value.forEach((groupField) => {
|
|
51
|
+
value[groupField.id] = getFieldInitialState(
|
|
52
|
+
groupField,
|
|
53
|
+
initialValues && initialValues[field.id],
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
} else if (field.type === "date" || field.type === "time") {
|
|
57
|
+
value = (initialValues && initialValues[field.id]) || field.default;
|
|
58
|
+
} else {
|
|
59
|
+
value = getFieldInitialState(field, initialValues);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
obj[field.id] = value;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return obj;
|
|
66
|
+
};
|
|
6
67
|
|
|
7
68
|
export interface ModelFormProps<T extends BasicModelInstance> {
|
|
8
69
|
model: Model;
|
|
@@ -17,28 +78,63 @@ export const ModelForm = <T extends BasicModelInstance>({
|
|
|
17
78
|
onSubmit,
|
|
18
79
|
initialValues,
|
|
19
80
|
}: ModelFormProps<T>) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
e.target;
|
|
81
|
+
const valuesInitialState = useMemo(
|
|
82
|
+
() => getValuesInitialState<T>(model, initialValues),
|
|
83
|
+
[model, initialValues],
|
|
84
|
+
);
|
|
85
|
+
const [values, setValues] = useState<T>(valuesInitialState);
|
|
26
86
|
|
|
87
|
+
const setKeyValue = (name: string, key: string | undefined, value: any) => {
|
|
27
88
|
setValues((v) => {
|
|
28
89
|
const n: Record<string, object> = {};
|
|
29
90
|
if (key) {
|
|
30
91
|
n[key] = {
|
|
31
92
|
...v[key],
|
|
32
|
-
[
|
|
93
|
+
[name]: value,
|
|
33
94
|
};
|
|
34
95
|
} else {
|
|
35
|
-
n[
|
|
96
|
+
n[name] = value;
|
|
36
97
|
}
|
|
37
98
|
|
|
38
99
|
return { ...v, ...n };
|
|
39
100
|
});
|
|
40
101
|
};
|
|
41
102
|
|
|
103
|
+
const handleCheckboxChange = (e: ChangeEvent<any>, key: string | undefined) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
setKeyValue(e.target.name, key, e.target.checked);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleSelectChange = (e: SelectChangeEvent<any>, key: string | undefined) => {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
setKeyValue(e.target.name, key, e.target.value);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleMultiSelectChange = (e: SelectChangeEvent<any>, key: string | undefined) => {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
const { value } = e.target;
|
|
116
|
+
const newValue = typeof value === "string" ? value.split(",") : value;
|
|
117
|
+
setKeyValue(e.target.name, key, newValue);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleInputChange = (
|
|
121
|
+
e: ChangeEvent<any>,
|
|
122
|
+
key: string | undefined,
|
|
123
|
+
type: ModelFieldTypes,
|
|
124
|
+
) => {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
|
|
127
|
+
let value = e.target.value;
|
|
128
|
+
if (type === "number") {
|
|
129
|
+
value = parseInt(e.target.value);
|
|
130
|
+
}
|
|
131
|
+
setKeyValue(e.target.name, key, value);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const handleDateChange = (value: any, key: string | undefined, id: string) => {
|
|
135
|
+
setKeyValue(id, key, value);
|
|
136
|
+
};
|
|
137
|
+
|
|
42
138
|
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
43
139
|
e.preventDefault();
|
|
44
140
|
onSubmit(values);
|
|
@@ -48,28 +144,113 @@ export const ModelForm = <T extends BasicModelInstance>({
|
|
|
48
144
|
const defaultColor = useGetDefaultThemeColor({ lightWeight: 200, darkWeight: 800 });
|
|
49
145
|
|
|
50
146
|
const { id, type, name, description, xs, sm, md, lg, xl } = field;
|
|
147
|
+
|
|
148
|
+
let fieldInput: ReactElement;
|
|
149
|
+
const value = key ? values[key][id] : values[id];
|
|
51
150
|
if (type === "group") {
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
</Paper>
|
|
67
|
-
</Grid>
|
|
151
|
+
fieldInput = (
|
|
152
|
+
<Paper>
|
|
153
|
+
<Box bgcolor={defaultColor} px={2} py={1} mb={2}>
|
|
154
|
+
<Typography variant="h6" role="heading" aria-level={1}>
|
|
155
|
+
{name}
|
|
156
|
+
</Typography>
|
|
157
|
+
<Typography variant="body2" role="heading" aria-level={2}>
|
|
158
|
+
{description}
|
|
159
|
+
</Typography>
|
|
160
|
+
</Box>
|
|
161
|
+
<Grid container spacing={2} sx={{ p: 2 }}>
|
|
162
|
+
{field.value.map((f) => renderField(f, id))}
|
|
163
|
+
</Grid>
|
|
164
|
+
</Paper>
|
|
68
165
|
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
166
|
+
} else if (type === "boolean") {
|
|
167
|
+
fieldInput = (
|
|
168
|
+
<Box sx={{ height: 1, display: "flex", alignItems: "center" }}>
|
|
169
|
+
<FormControlLabel
|
|
170
|
+
control={
|
|
171
|
+
<Checkbox name={id} onChange={(e) => handleCheckboxChange(e, key)} checked={value} />
|
|
172
|
+
}
|
|
173
|
+
label={name}
|
|
174
|
+
/>
|
|
175
|
+
</Box>
|
|
176
|
+
);
|
|
177
|
+
} else if (type === "enum") {
|
|
178
|
+
fieldInput = (
|
|
179
|
+
<FormControl fullWidth>
|
|
180
|
+
<InputLabel id={`${id}-select-label`}>{name}</InputLabel>
|
|
181
|
+
<Select
|
|
182
|
+
labelId={`${id}-select-label`}
|
|
183
|
+
id={`${id}-select`}
|
|
184
|
+
value={value}
|
|
185
|
+
label={name}
|
|
186
|
+
name={id}
|
|
187
|
+
onChange={(e) => handleSelectChange(e, key)}
|
|
188
|
+
required
|
|
189
|
+
>
|
|
190
|
+
{field.value.map((fieldValue) => (
|
|
191
|
+
<MenuItem key={fieldValue} value={fieldValue}>
|
|
192
|
+
{fieldValue}
|
|
193
|
+
</MenuItem>
|
|
194
|
+
))}
|
|
195
|
+
</Select>
|
|
196
|
+
</FormControl>
|
|
197
|
+
);
|
|
198
|
+
} else if (type === "multienum") {
|
|
199
|
+
fieldInput = (
|
|
200
|
+
<FormControl fullWidth>
|
|
201
|
+
<InputLabel id={`${id}-select-label`}>{name}</InputLabel>
|
|
202
|
+
<Select
|
|
203
|
+
labelId={`${id}-select-label`}
|
|
204
|
+
id={`${id}-select`}
|
|
205
|
+
value={value || []}
|
|
206
|
+
renderValue={(selected) => selected.join(", ")}
|
|
207
|
+
label={name}
|
|
208
|
+
name={id}
|
|
209
|
+
onChange={(e) => handleMultiSelectChange(e, key)}
|
|
210
|
+
required
|
|
211
|
+
multiple
|
|
212
|
+
>
|
|
213
|
+
{field.value.map((fieldValue) => (
|
|
214
|
+
<MenuItem key={fieldValue} value={fieldValue}>
|
|
215
|
+
<Checkbox checked={(value || []).includes(fieldValue)} />
|
|
216
|
+
<ListItemText primary={fieldValue} />
|
|
217
|
+
</MenuItem>
|
|
218
|
+
))}
|
|
219
|
+
</Select>
|
|
220
|
+
</FormControl>
|
|
221
|
+
);
|
|
222
|
+
} else if (type === "date") {
|
|
223
|
+
fieldInput = (
|
|
224
|
+
<DesktopDatePicker
|
|
225
|
+
label={name}
|
|
226
|
+
inputFormat={field.format}
|
|
227
|
+
value={value}
|
|
228
|
+
onChange={(value) => handleDateChange(value, key, id)}
|
|
229
|
+
renderInput={(params: any) => <TextField {...params} />}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
} else if (type === "time") {
|
|
233
|
+
fieldInput = (
|
|
234
|
+
<TimePicker
|
|
235
|
+
label={name}
|
|
236
|
+
inputFormat={field.format}
|
|
237
|
+
value={value}
|
|
238
|
+
onChange={(value) => handleDateChange(value, key, id)}
|
|
239
|
+
renderInput={(params: any) => <TextField {...params} />}
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
} else if (type === "datetime") {
|
|
243
|
+
fieldInput = (
|
|
244
|
+
<DateTimePicker
|
|
245
|
+
label={name}
|
|
246
|
+
inputFormat={field.format}
|
|
247
|
+
value={value}
|
|
248
|
+
onChange={(value) => handleDateChange(value, key, id)}
|
|
249
|
+
renderInput={(params: any) => <TextField {...params} />}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
} else {
|
|
253
|
+
fieldInput = (
|
|
73
254
|
<TextField
|
|
74
255
|
required
|
|
75
256
|
type={type}
|
|
@@ -77,9 +258,15 @@ export const ModelForm = <T extends BasicModelInstance>({
|
|
|
77
258
|
name={id}
|
|
78
259
|
variant="outlined"
|
|
79
260
|
fullWidth
|
|
80
|
-
value={
|
|
81
|
-
onChange={(e) => handleInputChange(e, key)}
|
|
261
|
+
value={value}
|
|
262
|
+
onChange={(e) => handleInputChange(e, key, type)}
|
|
82
263
|
/>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<Grid item key={id} xs={xs} sm={sm} md={md} lg={lg} xl={xl}>
|
|
269
|
+
{fieldInput}
|
|
83
270
|
</Grid>
|
|
84
271
|
);
|
|
85
272
|
};
|