@homefile/components-v2 2.14.11 → 2.14.12
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/components/partner/panel/PartnerCategorySelect.js +1 -3
- package/dist/mocks/forms/dynamicForm.mock.js +45 -53
- package/dist/utils/DynamicForm.utils.js +83 -140
- package/package.json +1 -1
- package/src/components/partner/panel/PartnerCategorySelect.tsx +1 -2
- package/src/mocks/forms/dynamicForm.mock.ts +48 -53
- package/src/utils/DynamicForm.utils.ts +93 -138
|
@@ -3,9 +3,7 @@ import { useState } from 'react';
|
|
|
3
3
|
import { t } from 'i18next';
|
|
4
4
|
import { Image, Flex, Menu, Text } from '@chakra-ui/react';
|
|
5
5
|
import { SelectButton, SelectList, SelectItem } from '../..';
|
|
6
|
-
import {
|
|
7
|
-
// categoryItems,
|
|
8
|
-
partnerCategoriesIcons, textOptionVariants, } from '../../../helpers';
|
|
6
|
+
import { partnerCategoriesIcons, textOptionVariants, } from '../../../helpers';
|
|
9
7
|
export const PartnerCategorySelect = ({ onClick, width = '10rem', }) => {
|
|
10
8
|
const [category, setCategory] = useState(t('partner.categories.all'));
|
|
11
9
|
const categoryItems = [
|
|
@@ -770,78 +770,70 @@ export const unknownFormMock = {
|
|
|
770
770
|
productClass: '',
|
|
771
771
|
misc: {
|
|
772
772
|
information: {
|
|
773
|
-
description: 'The
|
|
773
|
+
description: 'The KitchenAid KRSC503ESS01 is a side-by-side refrigerator that features a sleek, stainless steel design and a variety of advanced features for food preservation.',
|
|
774
774
|
features: [
|
|
775
|
-
'
|
|
776
|
-
'
|
|
777
|
-
'
|
|
778
|
-
'
|
|
779
|
-
'
|
|
775
|
+
'External ice and water dispenser',
|
|
776
|
+
'Humidity-controlled crisper drawers',
|
|
777
|
+
'Adaptive cooling technology',
|
|
778
|
+
'LED lighting',
|
|
779
|
+
'Adjustable shelves',
|
|
780
780
|
],
|
|
781
781
|
},
|
|
782
782
|
warranty: {
|
|
783
|
-
|
|
784
|
-
|
|
783
|
+
duration: '1 Year',
|
|
784
|
+
coverage: 'Parts and labor for the entire unit.',
|
|
785
785
|
},
|
|
786
|
-
estimatedYearOfManufacture:
|
|
786
|
+
estimatedYearOfManufacture: 2018,
|
|
787
787
|
specifications: {
|
|
788
788
|
dimensions: {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
depth: '
|
|
789
|
+
height: '69.75 inches',
|
|
790
|
+
width: '36 inches',
|
|
791
|
+
depth: '33.375 inches',
|
|
792
792
|
},
|
|
793
793
|
capacity: {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
},
|
|
798
|
-
|
|
799
|
-
color: '
|
|
800
|
-
|
|
801
|
-
|
|
794
|
+
totalCapacity: '25.8 cubic feet',
|
|
795
|
+
refrigeratorCapacity: '16.69 cubic feet',
|
|
796
|
+
freezerCapacity: '9.11 cubic feet',
|
|
797
|
+
},
|
|
798
|
+
energyEfficiency: 'Energy Star certified',
|
|
799
|
+
color: 'Stainless Steel',
|
|
800
|
+
finish: 'Fingerprint resistant',
|
|
801
|
+
iceMaker: 'Yes',
|
|
802
|
+
waterDispenser: 'Yes',
|
|
802
803
|
},
|
|
803
804
|
troubleshootingTips: [
|
|
804
805
|
{
|
|
805
806
|
issue: 'Refrigerator not cooling',
|
|
806
|
-
|
|
807
|
-
'Check if the refrigerator is plugged in and receiving power.',
|
|
808
|
-
'Ensure the temperature settings are correct.',
|
|
809
|
-
'Make sure the vents are not blocked.',
|
|
810
|
-
'Check for a faulty compressor or other internal components.',
|
|
811
|
-
],
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
issue: 'Water pooling inside the fridge',
|
|
815
|
-
tips: [
|
|
816
|
-
'Check the drainage hole for blockages.',
|
|
817
|
-
'Inspect the door seals for proper sealing.',
|
|
818
|
-
'Ensure that food items do not block airflow.',
|
|
819
|
-
],
|
|
820
|
-
},
|
|
821
|
-
],
|
|
822
|
-
replacementParts: {
|
|
823
|
-
compressor: 'LG part number 4924JA0211A',
|
|
824
|
-
doorSeal: 'LG part number 4986JJ3001A',
|
|
825
|
-
fanMotor: 'LG part number 4681JB1028C',
|
|
826
|
-
lighting: 'LG part number 6912JB2004E',
|
|
827
|
-
},
|
|
828
|
-
recommendedParts: [
|
|
829
|
-
{
|
|
830
|
-
partName: 'Water Filter',
|
|
831
|
-
partNumber: 'EDR1RXD1',
|
|
832
|
-
description: 'Refrigerator water filter for cleaner, fresher water.',
|
|
807
|
+
solution: 'Check if the thermostat is set to the recommended temperature and ensure that doors are sealing properly.',
|
|
833
808
|
},
|
|
834
809
|
{
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
description: 'Replacement ice maker assembly for the freezer.',
|
|
810
|
+
issue: 'Water dispenser not working',
|
|
811
|
+
solution: 'Ensure the water supply is connected and the water filter is not clogged.',
|
|
838
812
|
},
|
|
839
813
|
{
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
description: 'Adjustable shelves for refrigerator compartment.',
|
|
814
|
+
issue: 'Ice maker not producing ice',
|
|
815
|
+
solution: 'Make sure the ice maker is turned on and that the water line is not frozen.',
|
|
843
816
|
},
|
|
844
817
|
],
|
|
818
|
+
replacementParts: {
|
|
819
|
+
recommendedParts: [
|
|
820
|
+
{
|
|
821
|
+
partName: 'Water Filter',
|
|
822
|
+
partNumber: 'EDR1RXD1',
|
|
823
|
+
description: 'Refrigerator water filter for cleaner, fresher water.',
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
partName: 'Ice Maker Kit',
|
|
827
|
+
partNumber: 'W10190948A',
|
|
828
|
+
description: 'Replacement ice maker assembly for the freezer.',
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
partName: 'Shelves',
|
|
832
|
+
partNumber: 'W10720274',
|
|
833
|
+
description: 'Adjustable shelves for refrigerator compartment.',
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
},
|
|
845
837
|
},
|
|
846
838
|
createdAt: '2025-04-17T19:26:39.435Z',
|
|
847
839
|
updatedAt: '2025-04-17T19:26:39.435Z',
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
2
|
const MAX_DEPTH = 10;
|
|
3
3
|
const FIELDS_TO_IGNORE = ['_id', '__v', 'createdAt', 'updatedAt'];
|
|
4
|
+
const NESTED_RECURSION_DEPTH = 1;
|
|
4
5
|
export const mapApiObjectToFormFields = ({ obj = {}, depth = 0, labelPrefix = '', } = {}) => {
|
|
5
6
|
if (depth > MAX_DEPTH)
|
|
6
7
|
return [];
|
|
8
|
+
// merge misc into root
|
|
7
9
|
const misc = 'misc' in obj && typeof obj.misc === 'object' && obj.misc !== null
|
|
8
10
|
? obj.misc
|
|
9
11
|
: {};
|
|
@@ -14,160 +16,101 @@ export const mapApiObjectToFormFields = ({ obj = {}, depth = 0, labelPrefix = ''
|
|
|
14
16
|
.filter(([key]) => !FIELDS_TO_IGNORE.includes(key))
|
|
15
17
|
.flatMap(([key, value]) => {
|
|
16
18
|
const currentLabel = `${labelPrefix ? `${labelPrefix} - ` : ''}${capitalize(key)}`;
|
|
19
|
+
// primitives
|
|
17
20
|
if (typeof value === 'string') {
|
|
18
|
-
return [
|
|
19
|
-
{
|
|
20
|
-
id: uuidv4(),
|
|
21
|
-
name: currentLabel,
|
|
22
|
-
label: currentLabel,
|
|
23
|
-
description: '',
|
|
24
|
-
comments: '',
|
|
25
|
-
value,
|
|
26
|
-
type: 'text',
|
|
27
|
-
visible: true,
|
|
28
|
-
},
|
|
29
|
-
];
|
|
21
|
+
return [buildField(currentLabel, 'text', value)];
|
|
30
22
|
}
|
|
31
23
|
if (typeof value === 'number') {
|
|
32
|
-
return [
|
|
33
|
-
{
|
|
34
|
-
id: uuidv4(),
|
|
35
|
-
name: currentLabel,
|
|
36
|
-
label: currentLabel,
|
|
37
|
-
description: '',
|
|
38
|
-
comments: '',
|
|
39
|
-
value: String(value),
|
|
40
|
-
type: 'number',
|
|
41
|
-
visible: true,
|
|
42
|
-
},
|
|
43
|
-
];
|
|
24
|
+
return [buildField(currentLabel, 'number', String(value))];
|
|
44
25
|
}
|
|
45
26
|
if (typeof value === 'boolean') {
|
|
46
|
-
return [
|
|
47
|
-
{
|
|
48
|
-
id: uuidv4(),
|
|
49
|
-
name: currentLabel,
|
|
50
|
-
label: currentLabel,
|
|
51
|
-
description: '',
|
|
52
|
-
comments: '',
|
|
53
|
-
value: String(value),
|
|
54
|
-
type: 'switch',
|
|
55
|
-
visible: true,
|
|
56
|
-
},
|
|
57
|
-
];
|
|
27
|
+
return [buildField(currentLabel, 'switch', String(value))];
|
|
58
28
|
}
|
|
29
|
+
// string array
|
|
59
30
|
if (Array.isArray(value) && value.every((v) => typeof v === 'string')) {
|
|
60
|
-
|
|
61
|
-
return [
|
|
62
|
-
{
|
|
63
|
-
id: uuidv4(),
|
|
64
|
-
name: currentLabel,
|
|
65
|
-
label: currentLabel,
|
|
66
|
-
description: '',
|
|
67
|
-
comments: '',
|
|
68
|
-
value: htmlValue,
|
|
69
|
-
type: 'textarea',
|
|
70
|
-
visible: true,
|
|
71
|
-
},
|
|
72
|
-
];
|
|
31
|
+
return [buildField(currentLabel, 'textarea', value.join('<br/>'))];
|
|
73
32
|
}
|
|
33
|
+
// object array directly
|
|
74
34
|
if (Array.isArray(value) &&
|
|
75
35
|
value.every((v) => Object.prototype.toString.call(v) === '[object Object]')) {
|
|
76
|
-
|
|
77
|
-
.map((item) => {
|
|
78
|
-
const flat = flattenObject(item);
|
|
79
|
-
return Object.entries(flat)
|
|
80
|
-
.map(([k, v]) => {
|
|
81
|
-
const label = capitalize(k);
|
|
82
|
-
return Array.isArray(v) &&
|
|
83
|
-
v.every((el) => typeof el === 'string')
|
|
84
|
-
? `<span><strong>${label}:</strong><ul>${v
|
|
85
|
-
.map((el) => `<li>${el}</li>`)
|
|
86
|
-
.join('')}</ul></span><br/>`
|
|
87
|
-
: `<span><strong>${label}:</strong> ${formatHtmlValue(v)}</span><br/>`;
|
|
88
|
-
})
|
|
89
|
-
.join('');
|
|
90
|
-
})
|
|
91
|
-
.join('<br/>');
|
|
92
|
-
return [
|
|
93
|
-
{
|
|
94
|
-
id: uuidv4(),
|
|
95
|
-
name: currentLabel,
|
|
96
|
-
label: currentLabel,
|
|
97
|
-
description: '',
|
|
98
|
-
comments: '',
|
|
99
|
-
value: `<span>${htmlList}</span>`,
|
|
100
|
-
type: 'textarea',
|
|
101
|
-
visible: true,
|
|
102
|
-
},
|
|
103
|
-
];
|
|
36
|
+
return [buildField(currentLabel, 'textarea', renderObjectArray(value))];
|
|
104
37
|
}
|
|
38
|
+
// object with single array field
|
|
105
39
|
if (isRecord(value)) {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
.join('');
|
|
117
|
-
const htmlValue = `<span>${lines}</span>`;
|
|
118
|
-
return [
|
|
119
|
-
{
|
|
120
|
-
id: uuidv4(),
|
|
121
|
-
name: currentLabel,
|
|
122
|
-
label: currentLabel,
|
|
123
|
-
description: '',
|
|
124
|
-
comments: '',
|
|
125
|
-
value: htmlValue,
|
|
126
|
-
type: 'textarea',
|
|
127
|
-
visible: true,
|
|
128
|
-
},
|
|
129
|
-
];
|
|
40
|
+
const entries = Object.entries(value);
|
|
41
|
+
if (entries.length === 1 &&
|
|
42
|
+
Array.isArray(entries[0][1]) &&
|
|
43
|
+
entries[0][1].every(isRecord)) {
|
|
44
|
+
const [subKey, subArr] = entries[0];
|
|
45
|
+
const label = `${currentLabel} - ${capitalize(subKey)}`;
|
|
46
|
+
return [
|
|
47
|
+
Object.assign(Object.assign({}, buildField(label, 'textarea', renderObjectArray(subArr))), { name: label, label }),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
130
50
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
51
|
+
// nested object
|
|
52
|
+
if (isRecord(value)) {
|
|
53
|
+
if (depth >= NESTED_RECURSION_DEPTH) {
|
|
54
|
+
return mapApiObjectToFormFields({
|
|
55
|
+
obj: value,
|
|
56
|
+
depth: depth + 1,
|
|
57
|
+
labelPrefix: currentLabel,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// shallow object -> flatten
|
|
61
|
+
return [buildField(currentLabel, 'textarea', renderObject(value))];
|
|
62
|
+
}
|
|
63
|
+
// fallback
|
|
64
|
+
return [buildField(currentLabel, 'default', String(value), false)];
|
|
143
65
|
});
|
|
144
66
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
67
|
+
// helpers
|
|
68
|
+
const buildField = (name, type, value, visible = true) => ({
|
|
69
|
+
id: uuidv4(),
|
|
70
|
+
name,
|
|
71
|
+
label: name,
|
|
72
|
+
description: '',
|
|
73
|
+
comments: '',
|
|
74
|
+
value,
|
|
75
|
+
type: type,
|
|
76
|
+
visible,
|
|
77
|
+
});
|
|
78
|
+
const renderObjectArray = (arr) => arr
|
|
79
|
+
.map((item) => `<div>${renderObject(item).replace(/<br\/\>/g, '')}</div>`)
|
|
80
|
+
.join('<br/>');
|
|
81
|
+
const renderObject = (obj) => {
|
|
82
|
+
const flat = flattenObject(obj);
|
|
83
|
+
return ('<div>' +
|
|
84
|
+
Object.entries(flat)
|
|
85
|
+
.map(([k, v]) => {
|
|
86
|
+
const label = capitalize(k);
|
|
87
|
+
if (Array.isArray(v) && v.every((el) => typeof el === 'string')) {
|
|
88
|
+
return `<div><strong>${label}:</strong><ul>${v
|
|
89
|
+
.map((el) => `<li>${el}</li>`)
|
|
90
|
+
.join('')}</ul></div>`;
|
|
91
|
+
}
|
|
92
|
+
return `<div><strong>${label}:</strong> ${formatHtmlValue(v)}</div>`;
|
|
93
|
+
})
|
|
94
|
+
.join('<br/>') +
|
|
95
|
+
'</div>');
|
|
150
96
|
};
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
97
|
+
const capitalize = (str) => str
|
|
98
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
99
|
+
.replace(/_/g, ' ')
|
|
100
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
101
|
+
const isRecord = (val) => typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
102
|
+
const formatHtmlValue = (val) => Array.isArray(val) && val.every((v) => typeof v === 'string')
|
|
103
|
+
? val.join('<br/>')
|
|
104
|
+
: String(val);
|
|
105
|
+
const flattenObject = (obj, parentKey = '') => Object.entries(obj).reduce((acc, [key, value]) => {
|
|
106
|
+
const fullKey = parentKey
|
|
107
|
+
? `${parentKey} - ${capitalize(key)}`
|
|
108
|
+
: capitalize(key);
|
|
109
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
110
|
+
Object.assign(acc, flattenObject(value, fullKey));
|
|
157
111
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
? `${parentKey} - ${capitalize(key)}`
|
|
164
|
-
: capitalize(key);
|
|
165
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
166
|
-
Object.assign(acc, flattenObject(value, fullKey));
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
acc[fullKey] = value;
|
|
170
|
-
}
|
|
171
|
-
return acc;
|
|
172
|
-
}, {});
|
|
173
|
-
};
|
|
112
|
+
else {
|
|
113
|
+
acc[fullKey] = value;
|
|
114
|
+
}
|
|
115
|
+
return acc;
|
|
116
|
+
}, {});
|
package/package.json
CHANGED
|
@@ -3,7 +3,6 @@ import { t } from 'i18next'
|
|
|
3
3
|
import { Image, Flex, Menu, Text } from '@chakra-ui/react'
|
|
4
4
|
import { SelectButton, SelectList, SelectItem } from '@/components'
|
|
5
5
|
import {
|
|
6
|
-
// categoryItems,
|
|
7
6
|
partnerCategoriesIcons,
|
|
8
7
|
textOptionVariants,
|
|
9
8
|
} from '@/helpers'
|
|
@@ -14,7 +13,7 @@ export const PartnerCategorySelect = ({
|
|
|
14
13
|
width = '10rem',
|
|
15
14
|
}: PartnerCategorySelectI) => {
|
|
16
15
|
const [category, setCategory] = useState<string>(t('partner.categories.all'))
|
|
17
|
-
|
|
16
|
+
|
|
18
17
|
const categoryItems: SelectItemI<PartnerCategoryTypes>[] = [
|
|
19
18
|
{
|
|
20
19
|
name: t('partner.categories.all'),
|
|
@@ -781,78 +781,73 @@ export const unknownFormMock: Record<string, any> = {
|
|
|
781
781
|
misc: {
|
|
782
782
|
information: {
|
|
783
783
|
description:
|
|
784
|
-
'The
|
|
784
|
+
'The KitchenAid KRSC503ESS01 is a side-by-side refrigerator that features a sleek, stainless steel design and a variety of advanced features for food preservation.',
|
|
785
785
|
features: [
|
|
786
|
-
'
|
|
787
|
-
'
|
|
788
|
-
'
|
|
789
|
-
'
|
|
790
|
-
'
|
|
786
|
+
'External ice and water dispenser',
|
|
787
|
+
'Humidity-controlled crisper drawers',
|
|
788
|
+
'Adaptive cooling technology',
|
|
789
|
+
'LED lighting',
|
|
790
|
+
'Adjustable shelves',
|
|
791
791
|
],
|
|
792
792
|
},
|
|
793
793
|
warranty: {
|
|
794
|
-
|
|
795
|
-
|
|
794
|
+
duration: '1 Year',
|
|
795
|
+
coverage: 'Parts and labor for the entire unit.',
|
|
796
796
|
},
|
|
797
|
-
estimatedYearOfManufacture:
|
|
797
|
+
estimatedYearOfManufacture: 2018,
|
|
798
798
|
specifications: {
|
|
799
799
|
dimensions: {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
depth: '
|
|
800
|
+
height: '69.75 inches',
|
|
801
|
+
width: '36 inches',
|
|
802
|
+
depth: '33.375 inches',
|
|
803
803
|
},
|
|
804
804
|
capacity: {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
},
|
|
809
|
-
|
|
810
|
-
color: '
|
|
811
|
-
|
|
812
|
-
|
|
805
|
+
totalCapacity: '25.8 cubic feet',
|
|
806
|
+
refrigeratorCapacity: '16.69 cubic feet',
|
|
807
|
+
freezerCapacity: '9.11 cubic feet',
|
|
808
|
+
},
|
|
809
|
+
energyEfficiency: 'Energy Star certified',
|
|
810
|
+
color: 'Stainless Steel',
|
|
811
|
+
finish: 'Fingerprint resistant',
|
|
812
|
+
iceMaker: 'Yes',
|
|
813
|
+
waterDispenser: 'Yes',
|
|
813
814
|
},
|
|
814
815
|
troubleshootingTips: [
|
|
815
816
|
{
|
|
816
817
|
issue: 'Refrigerator not cooling',
|
|
817
|
-
|
|
818
|
-
'Check if the
|
|
819
|
-
'Ensure the temperature settings are correct.',
|
|
820
|
-
'Make sure the vents are not blocked.',
|
|
821
|
-
'Check for a faulty compressor or other internal components.',
|
|
822
|
-
],
|
|
823
|
-
},
|
|
824
|
-
{
|
|
825
|
-
issue: 'Water pooling inside the fridge',
|
|
826
|
-
tips: [
|
|
827
|
-
'Check the drainage hole for blockages.',
|
|
828
|
-
'Inspect the door seals for proper sealing.',
|
|
829
|
-
'Ensure that food items do not block airflow.',
|
|
830
|
-
],
|
|
831
|
-
},
|
|
832
|
-
],
|
|
833
|
-
replacementParts: {
|
|
834
|
-
compressor: 'LG part number 4924JA0211A',
|
|
835
|
-
doorSeal: 'LG part number 4986JJ3001A',
|
|
836
|
-
fanMotor: 'LG part number 4681JB1028C',
|
|
837
|
-
lighting: 'LG part number 6912JB2004E',
|
|
838
|
-
},
|
|
839
|
-
recommendedParts: [
|
|
840
|
-
{
|
|
841
|
-
partName: 'Water Filter',
|
|
842
|
-
partNumber: 'EDR1RXD1',
|
|
843
|
-
description: 'Refrigerator water filter for cleaner, fresher water.',
|
|
818
|
+
solution:
|
|
819
|
+
'Check if the thermostat is set to the recommended temperature and ensure that doors are sealing properly.',
|
|
844
820
|
},
|
|
845
821
|
{
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
822
|
+
issue: 'Water dispenser not working',
|
|
823
|
+
solution:
|
|
824
|
+
'Ensure the water supply is connected and the water filter is not clogged.',
|
|
849
825
|
},
|
|
850
826
|
{
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
827
|
+
issue: 'Ice maker not producing ice',
|
|
828
|
+
solution:
|
|
829
|
+
'Make sure the ice maker is turned on and that the water line is not frozen.',
|
|
854
830
|
},
|
|
855
831
|
],
|
|
832
|
+
replacementParts: {
|
|
833
|
+
recommendedParts: [
|
|
834
|
+
{
|
|
835
|
+
partName: 'Water Filter',
|
|
836
|
+
partNumber: 'EDR1RXD1',
|
|
837
|
+
description: 'Refrigerator water filter for cleaner, fresher water.',
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
partName: 'Ice Maker Kit',
|
|
841
|
+
partNumber: 'W10190948A',
|
|
842
|
+
description: 'Replacement ice maker assembly for the freezer.',
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
partName: 'Shelves',
|
|
846
|
+
partNumber: 'W10720274',
|
|
847
|
+
description: 'Adjustable shelves for refrigerator compartment.',
|
|
848
|
+
},
|
|
849
|
+
],
|
|
850
|
+
},
|
|
856
851
|
},
|
|
857
852
|
createdAt: '2025-04-17T19:26:39.435Z',
|
|
858
853
|
updatedAt: '2025-04-17T19:26:39.435Z',
|
|
@@ -9,6 +9,7 @@ interface MapApiObjectToFormFieldsI {
|
|
|
9
9
|
|
|
10
10
|
const MAX_DEPTH = 10
|
|
11
11
|
const FIELDS_TO_IGNORE = ['_id', '__v', 'createdAt', 'updatedAt']
|
|
12
|
+
const NESTED_RECURSION_DEPTH = 1
|
|
12
13
|
|
|
13
14
|
export const mapApiObjectToFormFields = ({
|
|
14
15
|
obj = {},
|
|
@@ -17,191 +18,146 @@ export const mapApiObjectToFormFields = ({
|
|
|
17
18
|
}: MapApiObjectToFormFieldsI = {}): ReportI[] => {
|
|
18
19
|
if (depth > MAX_DEPTH) return []
|
|
19
20
|
|
|
21
|
+
// merge misc into root
|
|
20
22
|
const misc =
|
|
21
23
|
'misc' in obj && typeof obj.misc === 'object' && obj.misc !== null
|
|
22
24
|
? obj.misc
|
|
23
25
|
: {}
|
|
24
26
|
const rest = { ...obj }
|
|
25
27
|
delete rest.misc
|
|
26
|
-
|
|
27
28
|
const merged = { ...rest, ...misc }
|
|
28
29
|
|
|
29
30
|
return Object.entries(merged)
|
|
30
31
|
.filter(([key]) => !FIELDS_TO_IGNORE.includes(key))
|
|
31
|
-
.flatMap(([key, value]):
|
|
32
|
+
.flatMap(([key, value]): ReportI[] => {
|
|
32
33
|
const currentLabel = `${
|
|
33
34
|
labelPrefix ? `${labelPrefix} - ` : ''
|
|
34
35
|
}${capitalize(key)}`
|
|
35
36
|
|
|
37
|
+
// primitives
|
|
36
38
|
if (typeof value === 'string') {
|
|
37
|
-
return [
|
|
38
|
-
{
|
|
39
|
-
id: uuidv4(),
|
|
40
|
-
name: currentLabel,
|
|
41
|
-
label: currentLabel,
|
|
42
|
-
description: '',
|
|
43
|
-
comments: '',
|
|
44
|
-
value,
|
|
45
|
-
type: 'text',
|
|
46
|
-
visible: true,
|
|
47
|
-
},
|
|
48
|
-
]
|
|
39
|
+
return [buildField(currentLabel, 'text', value)]
|
|
49
40
|
}
|
|
50
|
-
|
|
51
41
|
if (typeof value === 'number') {
|
|
52
|
-
return [
|
|
53
|
-
{
|
|
54
|
-
id: uuidv4(),
|
|
55
|
-
name: currentLabel,
|
|
56
|
-
label: currentLabel,
|
|
57
|
-
description: '',
|
|
58
|
-
comments: '',
|
|
59
|
-
value: String(value),
|
|
60
|
-
type: 'number',
|
|
61
|
-
visible: true,
|
|
62
|
-
},
|
|
63
|
-
]
|
|
42
|
+
return [buildField(currentLabel, 'number', String(value))]
|
|
64
43
|
}
|
|
65
|
-
|
|
66
44
|
if (typeof value === 'boolean') {
|
|
67
|
-
return [
|
|
68
|
-
{
|
|
69
|
-
id: uuidv4(),
|
|
70
|
-
name: currentLabel,
|
|
71
|
-
label: currentLabel,
|
|
72
|
-
description: '',
|
|
73
|
-
comments: '',
|
|
74
|
-
value: String(value),
|
|
75
|
-
type: 'switch',
|
|
76
|
-
visible: true,
|
|
77
|
-
},
|
|
78
|
-
]
|
|
45
|
+
return [buildField(currentLabel, 'switch', String(value))]
|
|
79
46
|
}
|
|
80
47
|
|
|
48
|
+
// string array
|
|
81
49
|
if (Array.isArray(value) && value.every((v) => typeof v === 'string')) {
|
|
82
|
-
|
|
83
|
-
return [
|
|
84
|
-
{
|
|
85
|
-
id: uuidv4(),
|
|
86
|
-
name: currentLabel,
|
|
87
|
-
label: currentLabel,
|
|
88
|
-
description: '',
|
|
89
|
-
comments: '',
|
|
90
|
-
value: htmlValue,
|
|
91
|
-
type: 'textarea',
|
|
92
|
-
visible: true,
|
|
93
|
-
},
|
|
94
|
-
]
|
|
50
|
+
return [buildField(currentLabel, 'textarea', value.join('<br/>'))]
|
|
95
51
|
}
|
|
96
52
|
|
|
53
|
+
// object array directly
|
|
97
54
|
if (
|
|
98
55
|
Array.isArray(value) &&
|
|
99
56
|
value.every(
|
|
100
57
|
(v) => Object.prototype.toString.call(v) === '[object Object]'
|
|
101
58
|
)
|
|
102
59
|
) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
id: uuidv4(),
|
|
125
|
-
name: currentLabel,
|
|
126
|
-
label: currentLabel,
|
|
127
|
-
description: '',
|
|
128
|
-
comments: '',
|
|
129
|
-
value: `<span>${htmlList}</span>`,
|
|
130
|
-
type: 'textarea',
|
|
131
|
-
visible: true,
|
|
132
|
-
},
|
|
133
|
-
]
|
|
60
|
+
return [buildField(currentLabel, 'textarea', renderObjectArray(value))]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// object with single array field
|
|
64
|
+
if (isRecord(value)) {
|
|
65
|
+
const entries = Object.entries(value) as [string, any][]
|
|
66
|
+
if (
|
|
67
|
+
entries.length === 1 &&
|
|
68
|
+
Array.isArray(entries[0][1]) &&
|
|
69
|
+
entries[0][1].every(isRecord)
|
|
70
|
+
) {
|
|
71
|
+
const [subKey, subArr] = entries[0]
|
|
72
|
+
const label = `${currentLabel} - ${capitalize(subKey)}`
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
...buildField(label, 'textarea', renderObjectArray(subArr)),
|
|
76
|
+
name: label,
|
|
77
|
+
label,
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
}
|
|
134
81
|
}
|
|
135
82
|
|
|
83
|
+
// nested object
|
|
136
84
|
if (isRecord(value)) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return Array.isArray(v) && v.every((el) => typeof el === 'string')
|
|
143
|
-
? `<span><strong>${label}:</strong><ul>${v
|
|
144
|
-
.map((el) => `<li>${el}</li>`)
|
|
145
|
-
.join('')}</ul></span></br/>`
|
|
146
|
-
: `<span><strong>${label}:</strong> ${formatHtmlValue(
|
|
147
|
-
v
|
|
148
|
-
)}</span></br/>`
|
|
85
|
+
if (depth >= NESTED_RECURSION_DEPTH) {
|
|
86
|
+
return mapApiObjectToFormFields({
|
|
87
|
+
obj: value,
|
|
88
|
+
depth: depth + 1,
|
|
89
|
+
labelPrefix: currentLabel,
|
|
149
90
|
})
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return [
|
|
154
|
-
{
|
|
155
|
-
id: uuidv4(),
|
|
156
|
-
name: currentLabel,
|
|
157
|
-
label: currentLabel,
|
|
158
|
-
description: '',
|
|
159
|
-
comments: '',
|
|
160
|
-
value: htmlValue,
|
|
161
|
-
type: 'textarea',
|
|
162
|
-
visible: true,
|
|
163
|
-
},
|
|
164
|
-
]
|
|
91
|
+
}
|
|
92
|
+
// shallow object -> flatten
|
|
93
|
+
return [buildField(currentLabel, 'textarea', renderObject(value))]
|
|
165
94
|
}
|
|
166
95
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
id: uuidv4(),
|
|
170
|
-
name: currentLabel,
|
|
171
|
-
label: currentLabel,
|
|
172
|
-
description: '',
|
|
173
|
-
comments: '',
|
|
174
|
-
value: String(value),
|
|
175
|
-
type: 'default',
|
|
176
|
-
visible: false,
|
|
177
|
-
},
|
|
178
|
-
]
|
|
96
|
+
// fallback
|
|
97
|
+
return [buildField(currentLabel, 'default', String(value), false)]
|
|
179
98
|
})
|
|
180
99
|
}
|
|
181
100
|
|
|
182
|
-
|
|
183
|
-
|
|
101
|
+
// helpers
|
|
102
|
+
const buildField = (
|
|
103
|
+
name: string,
|
|
104
|
+
type: string,
|
|
105
|
+
value: string,
|
|
106
|
+
visible = true
|
|
107
|
+
): ReportI => ({
|
|
108
|
+
id: uuidv4(),
|
|
109
|
+
name,
|
|
110
|
+
label: name,
|
|
111
|
+
description: '',
|
|
112
|
+
comments: '',
|
|
113
|
+
value,
|
|
114
|
+
type: type as any,
|
|
115
|
+
visible,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const renderObjectArray = (arr: Record<string, any>[]): string =>
|
|
119
|
+
arr
|
|
120
|
+
.map((item) => `<div>${renderObject(item).replace(/<br\/\>/g, '')}</div>`)
|
|
121
|
+
.join('<br/>')
|
|
122
|
+
|
|
123
|
+
const renderObject = (obj: Record<string, any>): string => {
|
|
124
|
+
const flat = flattenObject(obj)
|
|
125
|
+
return (
|
|
126
|
+
'<div>' +
|
|
127
|
+
Object.entries(flat)
|
|
128
|
+
.map(([k, v]) => {
|
|
129
|
+
const label = capitalize(k)
|
|
130
|
+
if (Array.isArray(v) && v.every((el) => typeof el === 'string')) {
|
|
131
|
+
return `<div><strong>${label}:</strong><ul>${v
|
|
132
|
+
.map((el) => `<li>${el}</li>`)
|
|
133
|
+
.join('')}</ul></div>`
|
|
134
|
+
}
|
|
135
|
+
return `<div><strong>${label}:</strong> ${formatHtmlValue(v)}</div>`
|
|
136
|
+
})
|
|
137
|
+
.join('<br/>') +
|
|
138
|
+
'</div>'
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const capitalize = (str: string): string =>
|
|
143
|
+
str
|
|
184
144
|
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
185
145
|
.replace(/_/g, ' ')
|
|
186
146
|
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
187
|
-
}
|
|
188
147
|
|
|
189
|
-
const isRecord = (val: unknown): val is Record<string, any> =>
|
|
190
|
-
|
|
191
|
-
}
|
|
148
|
+
const isRecord = (val: unknown): val is Record<string, any> =>
|
|
149
|
+
typeof val === 'object' && val !== null && !Array.isArray(val)
|
|
192
150
|
|
|
193
|
-
const formatHtmlValue = (val: unknown): string =>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return String(val)
|
|
198
|
-
}
|
|
151
|
+
const formatHtmlValue = (val: unknown): string =>
|
|
152
|
+
Array.isArray(val) && val.every((v) => typeof v === 'string')
|
|
153
|
+
? val.join('<br/>')
|
|
154
|
+
: String(val)
|
|
199
155
|
|
|
200
156
|
const flattenObject = (
|
|
201
157
|
obj: Record<string, any>,
|
|
202
158
|
parentKey = ''
|
|
203
|
-
): Record<string, any> =>
|
|
204
|
-
|
|
159
|
+
): Record<string, any> =>
|
|
160
|
+
Object.entries(obj).reduce((acc, [key, value]) => {
|
|
205
161
|
const fullKey = parentKey
|
|
206
162
|
? `${parentKey} - ${capitalize(key)}`
|
|
207
163
|
: capitalize(key)
|
|
@@ -212,4 +168,3 @@ const flattenObject = (
|
|
|
212
168
|
}
|
|
213
169
|
return acc
|
|
214
170
|
}, {} as Record<string, any>)
|
|
215
|
-
}
|