@dhiraj0720/report1chart 1.0.5 → 2.1.0
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/package.json +48 -9
- package/src/api/ApiService.jsx +180 -0
- package/src/api/ApiService.test.jsx +27 -0
- package/src/components/charts/BarChart.jsx +121 -0
- package/src/components/charts/ChartUtils.jsx +38 -0
- package/src/components/charts/LineChart.jsx +142 -0
- package/src/components/charts/PieChart.jsx +125 -0
- package/src/components/common/ErrorDisplay.jsx +51 -0
- package/src/components/common/LoadingSpinner.jsx +32 -0
- package/src/components/common/ReportCard.jsx +98 -0
- package/src/components/tables/CompactTable.jsx +269 -0
- package/src/components/tables/DefaultTable.jsx +225 -0
- package/src/components/tables/FreezeTable.jsx +264 -0
- package/src/components/tables/TableUtils.jsx +47 -0
- package/src/index.jsx +9 -1329
- package/src/screens/ReportDashboard.jsx +132 -0
- package/src/screens/ReportDetail.jsx +265 -0
- package/src/utils/Constants.jsx +38 -0
- package/src/utils/Formatters.jsx +24 -0
- package/src/utils/Validators.jsx +60 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
ScrollView,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import ReportDetail from './ReportDetail';
|
|
10
|
+
import ReportCard from '../components/common/ReportCard';
|
|
11
|
+
import ApiService from '../api/ApiService';
|
|
12
|
+
|
|
13
|
+
const ReportDashboard = ({ reports, onReportSelect, onClose }) => {
|
|
14
|
+
const [selectedReport, setSelectedReport] = useState(null);
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [error, setError] = useState(null);
|
|
17
|
+
|
|
18
|
+
const handleReportClick = async (report) => {
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setError(null);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Fetch data from API using npm package service
|
|
24
|
+
const data = await ApiService.fetchReportData(report.apiConfig);
|
|
25
|
+
|
|
26
|
+
if (data.error) {
|
|
27
|
+
setError(data.error);
|
|
28
|
+
// Use fallback data if available
|
|
29
|
+
if (data.fallbackData) {
|
|
30
|
+
setSelectedReport({
|
|
31
|
+
...report,
|
|
32
|
+
data: data.fallbackData
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
setSelectedReport({
|
|
37
|
+
...report,
|
|
38
|
+
data: data
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (onReportSelect) {
|
|
43
|
+
onReportSelect(report.name);
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
setError(err.message || 'Failed to fetch report data');
|
|
47
|
+
} finally {
|
|
48
|
+
setLoading(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleBack = () => {
|
|
53
|
+
setSelectedReport(null);
|
|
54
|
+
setError(null);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (selectedReport) {
|
|
58
|
+
return (
|
|
59
|
+
<ReportDetail
|
|
60
|
+
report={selectedReport}
|
|
61
|
+
loading={loading}
|
|
62
|
+
error={error}
|
|
63
|
+
onBack={handleBack}
|
|
64
|
+
onClose={onClose}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<View style={styles.container}>
|
|
71
|
+
<View style={styles.header}>
|
|
72
|
+
<Text style={styles.headerTitle}>Reports Dashboard</Text>
|
|
73
|
+
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
|
74
|
+
<Text style={styles.closeText}>×</Text>
|
|
75
|
+
</TouchableOpacity>
|
|
76
|
+
</View>
|
|
77
|
+
|
|
78
|
+
<ScrollView style={styles.reportsList}>
|
|
79
|
+
{reports.map((report) => (
|
|
80
|
+
<ReportCard
|
|
81
|
+
key={report.id}
|
|
82
|
+
report={report}
|
|
83
|
+
onPress={() => handleReportClick(report)}
|
|
84
|
+
/>
|
|
85
|
+
))}
|
|
86
|
+
</ScrollView>
|
|
87
|
+
</View>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const styles = StyleSheet.create({
|
|
92
|
+
container: {
|
|
93
|
+
flex: 1,
|
|
94
|
+
backgroundColor: '#f8f9fa',
|
|
95
|
+
},
|
|
96
|
+
header: {
|
|
97
|
+
flexDirection: 'row',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
justifyContent: 'space-between',
|
|
100
|
+
backgroundColor: '#fff',
|
|
101
|
+
paddingHorizontal: 16,
|
|
102
|
+
paddingVertical: 12,
|
|
103
|
+
borderBottomWidth: 1,
|
|
104
|
+
borderBottomColor: '#e0e0e0',
|
|
105
|
+
},
|
|
106
|
+
headerTitle: {
|
|
107
|
+
fontSize: 18,
|
|
108
|
+
fontWeight: '700',
|
|
109
|
+
color: '#000',
|
|
110
|
+
flex: 1,
|
|
111
|
+
textAlign: 'center',
|
|
112
|
+
},
|
|
113
|
+
closeButton: {
|
|
114
|
+
width: 32,
|
|
115
|
+
height: 32,
|
|
116
|
+
borderRadius: 16,
|
|
117
|
+
backgroundColor: '#f0f0f0',
|
|
118
|
+
justifyContent: 'center',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
},
|
|
121
|
+
closeText: {
|
|
122
|
+
fontSize: 20,
|
|
123
|
+
color: '#666',
|
|
124
|
+
fontWeight: '300',
|
|
125
|
+
},
|
|
126
|
+
reportsList: {
|
|
127
|
+
flex: 1,
|
|
128
|
+
padding: 16,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export default ReportDashboard;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
ScrollView,
|
|
8
|
+
ActivityIndicator,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import DefaultTable from '../components/tables/DefaultTable';
|
|
11
|
+
import CompactTable from '../components/tables/CompactTable';
|
|
12
|
+
import FreezeTable from '../components/tables/FreezeTable';
|
|
13
|
+
import BarChart from '../components/charts/BarChart';
|
|
14
|
+
import PieChart from '../components/charts/PieChart';
|
|
15
|
+
import LineChart from '../components/charts/LineChart';
|
|
16
|
+
import LoadingSpinner from '../components/common/LoadingSpinner';
|
|
17
|
+
import ErrorDisplay from '../components/common/ErrorDisplay';
|
|
18
|
+
|
|
19
|
+
const ReportDetail = ({ report, loading, error, onBack, onClose }) => {
|
|
20
|
+
const [viewType, setViewType] = useState(report.subType || 'default');
|
|
21
|
+
|
|
22
|
+
const renderContent = () => {
|
|
23
|
+
if (loading) {
|
|
24
|
+
return <LoadingSpinner message="Loading report data..." />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (error) {
|
|
28
|
+
return <ErrorDisplay error={error} onRetry={() => onBack()} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!report.data || (Array.isArray(report.data) && report.data.length === 0)) {
|
|
32
|
+
return (
|
|
33
|
+
<View style={styles.noDataContainer}>
|
|
34
|
+
<Text style={styles.noDataText}>No data available for this report</Text>
|
|
35
|
+
</View>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Render based on report type
|
|
40
|
+
switch (report.type) {
|
|
41
|
+
case 'table':
|
|
42
|
+
return renderTable();
|
|
43
|
+
case 'bar':
|
|
44
|
+
return <BarChart data={report.data} title={report.title} />;
|
|
45
|
+
case 'pie':
|
|
46
|
+
return <PieChart data={report.data} title={report.title} />;
|
|
47
|
+
case 'line':
|
|
48
|
+
return <LineChart data={report.data} title={report.title} />;
|
|
49
|
+
default:
|
|
50
|
+
return <BarChart data={report.data} title={report.title} />;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const renderTable = () => {
|
|
55
|
+
switch (viewType) {
|
|
56
|
+
case 'compact':
|
|
57
|
+
return <CompactTable data={report.data} />;
|
|
58
|
+
case 'freeze':
|
|
59
|
+
return <FreezeTable data={report.data} />;
|
|
60
|
+
case 'default':
|
|
61
|
+
default:
|
|
62
|
+
return <DefaultTable data={report.data} />;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<View style={styles.container}>
|
|
68
|
+
<View style={styles.header}>
|
|
69
|
+
<TouchableOpacity
|
|
70
|
+
style={styles.backButton}
|
|
71
|
+
onPress={onBack}
|
|
72
|
+
>
|
|
73
|
+
<Text style={styles.backText}>‹</Text>
|
|
74
|
+
<Text style={styles.backButtonText}>Back</Text>
|
|
75
|
+
</TouchableOpacity>
|
|
76
|
+
<Text style={styles.headerTitle} numberOfLines={1}>
|
|
77
|
+
{report.title || report.name}
|
|
78
|
+
</Text>
|
|
79
|
+
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
|
80
|
+
<Text style={styles.closeText}>×</Text>
|
|
81
|
+
</TouchableOpacity>
|
|
82
|
+
</View>
|
|
83
|
+
|
|
84
|
+
{report.type === 'table' && (
|
|
85
|
+
<View style={styles.viewTypeSelector}>
|
|
86
|
+
<TouchableOpacity
|
|
87
|
+
style={[
|
|
88
|
+
styles.viewTypeButton,
|
|
89
|
+
viewType === 'default' && styles.activeViewType
|
|
90
|
+
]}
|
|
91
|
+
onPress={() => setViewType('default')}
|
|
92
|
+
>
|
|
93
|
+
<Text style={[
|
|
94
|
+
styles.viewTypeText,
|
|
95
|
+
viewType === 'default' && styles.activeViewTypeText
|
|
96
|
+
]}>Varsayılan</Text>
|
|
97
|
+
</TouchableOpacity>
|
|
98
|
+
|
|
99
|
+
<TouchableOpacity
|
|
100
|
+
style={[
|
|
101
|
+
styles.viewTypeButton,
|
|
102
|
+
viewType === 'compact' && styles.activeViewType
|
|
103
|
+
]}
|
|
104
|
+
onPress={() => setViewType('compact')}
|
|
105
|
+
>
|
|
106
|
+
<Text style={[
|
|
107
|
+
styles.viewTypeText,
|
|
108
|
+
viewType === 'compact' && styles.activeViewTypeText
|
|
109
|
+
]}>Kompakt</Text>
|
|
110
|
+
</TouchableOpacity>
|
|
111
|
+
|
|
112
|
+
<TouchableOpacity
|
|
113
|
+
style={[
|
|
114
|
+
styles.viewTypeButton,
|
|
115
|
+
viewType === 'freeze' && styles.activeViewType
|
|
116
|
+
]}
|
|
117
|
+
onPress={() => setViewType('freeze')}
|
|
118
|
+
>
|
|
119
|
+
<Text style={[
|
|
120
|
+
styles.viewTypeText,
|
|
121
|
+
viewType === 'freeze' && styles.activeViewTypeText
|
|
122
|
+
]}>Sabit Kolon</Text>
|
|
123
|
+
</TouchableOpacity>
|
|
124
|
+
</View>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
<ScrollView style={styles.content}>
|
|
128
|
+
{renderContent()}
|
|
129
|
+
|
|
130
|
+
{report.data && (
|
|
131
|
+
<View style={styles.dataInfo}>
|
|
132
|
+
<Text style={styles.infoTitle}>Report Information</Text>
|
|
133
|
+
<Text style={styles.infoText}>
|
|
134
|
+
{report.description || 'Detailed analysis report'}
|
|
135
|
+
</Text>
|
|
136
|
+
<Text style={styles.dataSource}>
|
|
137
|
+
Data points: {Array.isArray(report.data) ? report.data.length : 'N/A'}
|
|
138
|
+
</Text>
|
|
139
|
+
</View>
|
|
140
|
+
)}
|
|
141
|
+
</ScrollView>
|
|
142
|
+
</View>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const styles = StyleSheet.create({
|
|
147
|
+
container: {
|
|
148
|
+
flex: 1,
|
|
149
|
+
backgroundColor: '#f8f9fa',
|
|
150
|
+
},
|
|
151
|
+
header: {
|
|
152
|
+
flexDirection: 'row',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'space-between',
|
|
155
|
+
backgroundColor: '#fff',
|
|
156
|
+
paddingHorizontal: 16,
|
|
157
|
+
paddingVertical: 12,
|
|
158
|
+
borderBottomWidth: 1,
|
|
159
|
+
borderBottomColor: '#e0e0e0',
|
|
160
|
+
},
|
|
161
|
+
headerTitle: {
|
|
162
|
+
fontSize: 18,
|
|
163
|
+
fontWeight: '700',
|
|
164
|
+
color: '#000',
|
|
165
|
+
flex: 1,
|
|
166
|
+
textAlign: 'center',
|
|
167
|
+
},
|
|
168
|
+
backButton: {
|
|
169
|
+
flexDirection: 'row',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
padding: 8,
|
|
172
|
+
},
|
|
173
|
+
backText: {
|
|
174
|
+
fontSize: 24,
|
|
175
|
+
color: '#4CAF50',
|
|
176
|
+
marginRight: 4,
|
|
177
|
+
},
|
|
178
|
+
backButtonText: {
|
|
179
|
+
fontSize: 16,
|
|
180
|
+
color: '#4CAF50',
|
|
181
|
+
fontWeight: '600',
|
|
182
|
+
},
|
|
183
|
+
closeButton: {
|
|
184
|
+
width: 32,
|
|
185
|
+
height: 32,
|
|
186
|
+
borderRadius: 16,
|
|
187
|
+
backgroundColor: '#f0f0f0',
|
|
188
|
+
justifyContent: 'center',
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
},
|
|
191
|
+
closeText: {
|
|
192
|
+
fontSize: 20,
|
|
193
|
+
color: '#666',
|
|
194
|
+
fontWeight: '300',
|
|
195
|
+
},
|
|
196
|
+
content: {
|
|
197
|
+
flex: 1,
|
|
198
|
+
padding: 16,
|
|
199
|
+
},
|
|
200
|
+
viewTypeSelector: {
|
|
201
|
+
flexDirection: 'row',
|
|
202
|
+
backgroundColor: '#f0f0f0',
|
|
203
|
+
borderRadius: 8,
|
|
204
|
+
padding: 4,
|
|
205
|
+
margin: 16,
|
|
206
|
+
alignSelf: 'center',
|
|
207
|
+
},
|
|
208
|
+
viewTypeButton: {
|
|
209
|
+
paddingHorizontal: 16,
|
|
210
|
+
paddingVertical: 8,
|
|
211
|
+
borderRadius: 6,
|
|
212
|
+
marginHorizontal: 2,
|
|
213
|
+
},
|
|
214
|
+
activeViewType: {
|
|
215
|
+
backgroundColor: '#fff',
|
|
216
|
+
shadowColor: '#000',
|
|
217
|
+
shadowOffset: { width: 0, height: 1 },
|
|
218
|
+
shadowOpacity: 0.1,
|
|
219
|
+
shadowRadius: 2,
|
|
220
|
+
elevation: 2,
|
|
221
|
+
},
|
|
222
|
+
viewTypeText: {
|
|
223
|
+
fontSize: 13,
|
|
224
|
+
fontWeight: '500',
|
|
225
|
+
color: '#666',
|
|
226
|
+
},
|
|
227
|
+
activeViewTypeText: {
|
|
228
|
+
color: '#4CAF50',
|
|
229
|
+
fontWeight: '600',
|
|
230
|
+
},
|
|
231
|
+
dataInfo: {
|
|
232
|
+
backgroundColor: '#e8f5e9',
|
|
233
|
+
borderRadius: 12,
|
|
234
|
+
padding: 16,
|
|
235
|
+
marginTop: 16,
|
|
236
|
+
},
|
|
237
|
+
infoTitle: {
|
|
238
|
+
fontSize: 16,
|
|
239
|
+
fontWeight: '700',
|
|
240
|
+
color: '#2e7d32',
|
|
241
|
+
marginBottom: 8,
|
|
242
|
+
},
|
|
243
|
+
infoText: {
|
|
244
|
+
fontSize: 14,
|
|
245
|
+
color: '#555',
|
|
246
|
+
lineHeight: 20,
|
|
247
|
+
marginBottom: 8,
|
|
248
|
+
},
|
|
249
|
+
dataSource: {
|
|
250
|
+
fontSize: 13,
|
|
251
|
+
color: '#777',
|
|
252
|
+
fontStyle: 'italic',
|
|
253
|
+
},
|
|
254
|
+
noDataContainer: {
|
|
255
|
+
padding: 40,
|
|
256
|
+
alignItems: 'center',
|
|
257
|
+
},
|
|
258
|
+
noDataText: {
|
|
259
|
+
fontSize: 16,
|
|
260
|
+
color: '#999',
|
|
261
|
+
textAlign: 'center',
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
export default ReportDetail;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const COLORS = {
|
|
2
|
+
primary: '#4CAF50',
|
|
3
|
+
primaryDark: '#388E3C',
|
|
4
|
+
primaryLight: '#C8E6C9',
|
|
5
|
+
secondary: '#2196F3',
|
|
6
|
+
accent: '#FF9800',
|
|
7
|
+
success: '#27ae60',
|
|
8
|
+
warning: '#f39c12',
|
|
9
|
+
danger: '#e74c3c',
|
|
10
|
+
info: '#3498db',
|
|
11
|
+
dark: '#2c3e50',
|
|
12
|
+
light: '#ecf0f1',
|
|
13
|
+
white: '#ffffff',
|
|
14
|
+
black: '#000000',
|
|
15
|
+
gray: '#95a5a6',
|
|
16
|
+
grayDark: '#7f8c8d',
|
|
17
|
+
grayLight: '#bdc3c7'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const API_STATUS = {
|
|
21
|
+
IDLE: 'idle',
|
|
22
|
+
LOADING: 'loading',
|
|
23
|
+
SUCCESS: 'success',
|
|
24
|
+
ERROR: 'error'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const CHART_TYPES = {
|
|
28
|
+
BAR: 'bar',
|
|
29
|
+
LINE: 'line',
|
|
30
|
+
PIE: 'pie',
|
|
31
|
+
TABLE: 'table'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const TABLE_VIEW_TYPES = {
|
|
35
|
+
DEFAULT: 'default',
|
|
36
|
+
COMPACT: 'compact',
|
|
37
|
+
FREEZE: 'freeze'
|
|
38
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const formatCurrency = (value) => {
|
|
2
|
+
const num = Number(value);
|
|
3
|
+
if (isNaN(num)) return '0';
|
|
4
|
+
|
|
5
|
+
if (num >= 1000000) {
|
|
6
|
+
return `${(num / 1000000).toFixed(1)}M`;
|
|
7
|
+
} else if (num >= 1000) {
|
|
8
|
+
return `${(num / 1000).toFixed(1)}K`;
|
|
9
|
+
}
|
|
10
|
+
return num.toLocaleString();
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const formatNumber = (num) => {
|
|
14
|
+
return num.toLocaleString();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const formatPercentage = (value, decimals = 1) => {
|
|
18
|
+
return `${value >= 0 ? '+' : ''}${value.toFixed(decimals)}%`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const getColor = (index) => {
|
|
22
|
+
const colors = ['#4CAF50', '#2196F3', '#FF9800', '#E91E63', '#9C27B0', '#00BCD4'];
|
|
23
|
+
return colors[index % colors.length];
|
|
24
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const isValidUrl = (url) => {
|
|
2
|
+
try {
|
|
3
|
+
new URL(url);
|
|
4
|
+
return true;
|
|
5
|
+
} catch (error) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const isValidApiResponse = (data) => {
|
|
11
|
+
if (!data) return false;
|
|
12
|
+
|
|
13
|
+
// Check if data is array or has data property
|
|
14
|
+
if (Array.isArray(data)) {
|
|
15
|
+
return data.length > 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (data.data && Array.isArray(data.data)) {
|
|
19
|
+
return data.data.length > 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (data.values && Array.isArray(data.values)) {
|
|
23
|
+
return data.values.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const validateApiConfig = (apiConfig) => {
|
|
30
|
+
const errors = [];
|
|
31
|
+
|
|
32
|
+
if (!apiConfig || typeof apiConfig !== 'object') {
|
|
33
|
+
errors.push('API configuration is required');
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!apiConfig.url || typeof apiConfig.url !== 'string') {
|
|
38
|
+
errors.push('API URL is required and must be a string');
|
|
39
|
+
} else if (!isValidUrl(apiConfig.url)) {
|
|
40
|
+
errors.push('Invalid API URL format');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (apiConfig.method && typeof apiConfig.method !== 'string') {
|
|
44
|
+
errors.push('API method must be a string');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (apiConfig.token && typeof apiConfig.token !== 'string') {
|
|
48
|
+
errors.push('API token must be a string');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return errors;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const formatErrorMessage = (error) => {
|
|
55
|
+
if (typeof error === 'string') return error;
|
|
56
|
+
if (error.message) return error.message;
|
|
57
|
+
if (error.response?.data?.message) return error.response.data.message;
|
|
58
|
+
if (error.response?.statusText) return `${error.response.status}: ${error.response.statusText}`;
|
|
59
|
+
return 'An unknown error occurred';
|
|
60
|
+
};
|