@dhiraj0720/report1chart 1.0.1 → 1.0.4
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 +2 -2
- package/src/index.jsx +813 -0
- package/src/index.js +0 -43
package/package.json
CHANGED
package/src/index.jsx
ADDED
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
ScrollView,
|
|
8
|
+
ActivityIndicator,
|
|
9
|
+
Dimensions,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
const ReportChart = ({
|
|
13
|
+
onReportSelect,
|
|
14
|
+
onClose,
|
|
15
|
+
reports = [],
|
|
16
|
+
onFetchReportData
|
|
17
|
+
}) => {
|
|
18
|
+
const [selectedReport, setSelectedReport] = useState(null);
|
|
19
|
+
const [reportData, setReportData] = useState(null);
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
|
|
23
|
+
// Default reports if none provided
|
|
24
|
+
const defaultReports = [
|
|
25
|
+
{
|
|
26
|
+
id: 1,
|
|
27
|
+
name: 'Report 1',
|
|
28
|
+
title: 'Revenue Trend',
|
|
29
|
+
type: 'line',
|
|
30
|
+
description: 'Monthly revenue analysis'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 2,
|
|
34
|
+
name: 'Report 2',
|
|
35
|
+
title: 'Revenue by Mode',
|
|
36
|
+
type: 'bar',
|
|
37
|
+
description: 'Revenue distribution across modes'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 3,
|
|
41
|
+
name: 'Report 3',
|
|
42
|
+
title: 'Shipment by Direction',
|
|
43
|
+
type: 'pie',
|
|
44
|
+
description: 'Shipment direction analysis'
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const reportList = reports.length > 0 ? reports : defaultReports;
|
|
49
|
+
|
|
50
|
+
const handleReportClick = async (report) => {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
setReportData(null);
|
|
54
|
+
setSelectedReport(report);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Call the parent function to fetch data
|
|
58
|
+
if (onFetchReportData) {
|
|
59
|
+
const data = await onFetchReportData(report);
|
|
60
|
+
if (data) {
|
|
61
|
+
setReportData({
|
|
62
|
+
...data,
|
|
63
|
+
title: report.title || report.name,
|
|
64
|
+
type: report.type || 'bar',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Callback to parent
|
|
70
|
+
if (onReportSelect) {
|
|
71
|
+
onReportSelect(report.name);
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
setError(err.message || 'Failed to fetch report data');
|
|
75
|
+
} finally {
|
|
76
|
+
setLoading(false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Add currency formatter
|
|
81
|
+
const formatCurrency = (value) => {
|
|
82
|
+
const num = Number(value);
|
|
83
|
+
if (isNaN(num)) return '0';
|
|
84
|
+
|
|
85
|
+
if (num >= 1000000) {
|
|
86
|
+
return `${(num / 1000000).toFixed(1)}M`;
|
|
87
|
+
} else if (num >= 1000) {
|
|
88
|
+
return `${(num / 1000).toFixed(1)}K`;
|
|
89
|
+
}
|
|
90
|
+
return num.toString();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const renderChart = () => {
|
|
94
|
+
if (!reportData || !reportData.data) {
|
|
95
|
+
return (
|
|
96
|
+
<View style={styles.noDataContainer}>
|
|
97
|
+
<Text style={styles.noDataText}>No data available for this report</Text>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { type, data } = reportData;
|
|
103
|
+
|
|
104
|
+
switch (type) {
|
|
105
|
+
case 'table':
|
|
106
|
+
return renderTable(data);
|
|
107
|
+
case 'line':
|
|
108
|
+
return renderLineChart(data);
|
|
109
|
+
case 'bar':
|
|
110
|
+
return renderBarChart(data);
|
|
111
|
+
case 'pie':
|
|
112
|
+
return renderPieChart(data);
|
|
113
|
+
default:
|
|
114
|
+
return renderBarChart(data);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
const renderLineChart = (data) => {
|
|
120
|
+
// Handle both formats: { labels, values } or array format
|
|
121
|
+
const values = data.values || data.map(d => d.value || d.revenue || d.shipments || 0);
|
|
122
|
+
const labels = data.labels || data.map(d => d.label || d.month || d.mode || d.direction || '');
|
|
123
|
+
|
|
124
|
+
const maxValue = Math.max(...values, 1);
|
|
125
|
+
const chartHeight = 200;
|
|
126
|
+
const chartWidth = Dimensions.get('window').width - 64;
|
|
127
|
+
const barWidth = (chartWidth / values.length) - 10;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<View style={styles.chartContainer}>
|
|
131
|
+
<Text style={styles.chartTitle}>{reportData.title}</Text>
|
|
132
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
133
|
+
<View style={[styles.chart, { height: chartHeight, width: chartWidth }]}>
|
|
134
|
+
{values.map((value, index) => {
|
|
135
|
+
const height = (value / maxValue) * chartHeight;
|
|
136
|
+
const label = labels[index] || `M${index + 1}`;
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<View key={index} style={styles.linePointWrapper}>
|
|
140
|
+
<View style={[styles.linePoint, {
|
|
141
|
+
height: Math.max(height, 2),
|
|
142
|
+
width: barWidth
|
|
143
|
+
}]} />
|
|
144
|
+
<View style={styles.lineConnector} />
|
|
145
|
+
<Text style={styles.lineLabel} numberOfLines={1}>{label}</Text>
|
|
146
|
+
<Text style={styles.valueText}>{formatNumber(value)}</Text>
|
|
147
|
+
</View>
|
|
148
|
+
);
|
|
149
|
+
})}
|
|
150
|
+
</View>
|
|
151
|
+
</ScrollView>
|
|
152
|
+
</View>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const renderBarChart = (data) => {
|
|
157
|
+
// Handle array format or object format
|
|
158
|
+
const isArray = Array.isArray(data);
|
|
159
|
+
const values = isArray ? data.map(d => d.revenue || d.value || d.shipments || 0) : data.values;
|
|
160
|
+
const labels = isArray ? data.map(d => d.mode || d.label || '') : data.labels;
|
|
161
|
+
|
|
162
|
+
const maxValue = Math.max(...values, 1);
|
|
163
|
+
const chartHeight = 200;
|
|
164
|
+
const chartWidth = Dimensions.get('window').width - 64;
|
|
165
|
+
const barWidth = Math.min(40, (chartWidth / values.length) - 10);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<View style={styles.chartContainer}>
|
|
169
|
+
<Text style={styles.chartTitle}>{reportData.title}</Text>
|
|
170
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
171
|
+
<View style={[styles.chart, { height: chartHeight, width: chartWidth }]}>
|
|
172
|
+
{values.map((value, index) => {
|
|
173
|
+
const height = (value / maxValue) * chartHeight;
|
|
174
|
+
const label = labels?.[index] || `Item ${index + 1}`;
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<View key={index} style={styles.barWrapper}>
|
|
178
|
+
<View style={[styles.bar, {
|
|
179
|
+
height,
|
|
180
|
+
width: barWidth,
|
|
181
|
+
backgroundColor: getColor(index)
|
|
182
|
+
}]} />
|
|
183
|
+
<Text style={styles.barLabel} numberOfLines={1}>{label}</Text>
|
|
184
|
+
<Text style={styles.valueText}>{formatNumber(value)}</Text>
|
|
185
|
+
</View>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
188
|
+
</View>
|
|
189
|
+
</ScrollView>
|
|
190
|
+
</View>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const renderTable = (data) => {
|
|
195
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
196
|
+
return (
|
|
197
|
+
<View style={styles.noDataContainer}>
|
|
198
|
+
<Text style={styles.noDataText}>No data available for this report</Text>
|
|
199
|
+
</View>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={true}>
|
|
205
|
+
<View style={styles.table}>
|
|
206
|
+
{/* Table Header */}
|
|
207
|
+
<View style={styles.tableHeader}>
|
|
208
|
+
<View style={[styles.headerCell, styles.firstColumn]}>
|
|
209
|
+
<Text style={styles.headerText}>FALİYET KAR/ZARAR</Text>
|
|
210
|
+
</View>
|
|
211
|
+
<View style={styles.headerCell}>
|
|
212
|
+
<Text style={styles.headerText}>2024 Fiili</Text>
|
|
213
|
+
</View>
|
|
214
|
+
<View style={styles.headerCell}>
|
|
215
|
+
<Text style={styles.headerText}>2025 Fiili</Text>
|
|
216
|
+
</View>
|
|
217
|
+
<View style={styles.headerCell}>
|
|
218
|
+
<Text style={styles.headerText}>Fiili Artış %</Text>
|
|
219
|
+
</View>
|
|
220
|
+
<View style={styles.headerCell}>
|
|
221
|
+
<Text style={styles.headerText}>2025 Bütçe</Text>
|
|
222
|
+
</View>
|
|
223
|
+
<View style={styles.headerCell}>
|
|
224
|
+
<Text style={styles.headerText}>Sapma %</Text>
|
|
225
|
+
</View>
|
|
226
|
+
<View style={styles.headerCell}>
|
|
227
|
+
<Text style={styles.headerText}>FALİYET GİD.</Text>
|
|
228
|
+
<Text style={styles.headerText}>/ BRÜT KAR %</Text>
|
|
229
|
+
</View>
|
|
230
|
+
</View>
|
|
231
|
+
|
|
232
|
+
{/* Table Rows */}
|
|
233
|
+
{data.map((item, index) => (
|
|
234
|
+
<View
|
|
235
|
+
key={index}
|
|
236
|
+
style={[
|
|
237
|
+
styles.tableRow,
|
|
238
|
+
item.isTotal ? styles.totalRow : (index % 2 === 0 ? styles.evenRow : styles.oddRow)
|
|
239
|
+
]}
|
|
240
|
+
>
|
|
241
|
+
<View style={[styles.cell, styles.firstColumn]}>
|
|
242
|
+
<Text style={item.isTotal ? styles.totalText : styles.cellText}>
|
|
243
|
+
{item.name}
|
|
244
|
+
</Text>
|
|
245
|
+
</View>
|
|
246
|
+
|
|
247
|
+
<View style={styles.cell}>
|
|
248
|
+
<Text style={styles.numberText}>
|
|
249
|
+
{formatCurrency(item.actual2024)}
|
|
250
|
+
</Text>
|
|
251
|
+
</View>
|
|
252
|
+
|
|
253
|
+
<View style={styles.cell}>
|
|
254
|
+
<Text style={styles.numberText}>
|
|
255
|
+
{formatCurrency(item.actual2025)}
|
|
256
|
+
</Text>
|
|
257
|
+
</View>
|
|
258
|
+
|
|
259
|
+
<View style={styles.cell}>
|
|
260
|
+
<Text style={[
|
|
261
|
+
styles.percentText,
|
|
262
|
+
item.yoYChangePercent >= 0 ? styles.positive : styles.negative
|
|
263
|
+
]}>
|
|
264
|
+
{item.yoYChangePercent >= 0 ? '↑' : '↓'} {Math.abs(item.yoYChangePercent)}%
|
|
265
|
+
</Text>
|
|
266
|
+
</View>
|
|
267
|
+
|
|
268
|
+
<View style={styles.cell}>
|
|
269
|
+
<Text style={styles.numberText}>
|
|
270
|
+
{formatCurrency(item.budget2025)}
|
|
271
|
+
</Text>
|
|
272
|
+
</View>
|
|
273
|
+
|
|
274
|
+
<View style={styles.cell}>
|
|
275
|
+
<Text style={[
|
|
276
|
+
styles.percentText,
|
|
277
|
+
item.budgetVariancePercent >= 0 ? styles.positive : styles.negative
|
|
278
|
+
]}>
|
|
279
|
+
{item.budgetVariancePercent >= 0 ? '↑' : '↓'} {Math.abs(item.budgetVariancePercent)}%
|
|
280
|
+
</Text>
|
|
281
|
+
</View>
|
|
282
|
+
|
|
283
|
+
<View style={styles.cell}>
|
|
284
|
+
<Text style={[
|
|
285
|
+
styles.percentText,
|
|
286
|
+
styles.grossMargin
|
|
287
|
+
]}>
|
|
288
|
+
{item.grossMarginPercent}%
|
|
289
|
+
</Text>
|
|
290
|
+
</View>
|
|
291
|
+
</View>
|
|
292
|
+
))}
|
|
293
|
+
</View>
|
|
294
|
+
</ScrollView>
|
|
295
|
+
);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
const renderPieChart = (data) => {
|
|
301
|
+
// Data should be array of objects with label and value
|
|
302
|
+
const total = data.reduce((sum, item) => sum + (item.shipments || item.revenue || item.value || 0), 0);
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<View style={styles.chartContainer}>
|
|
306
|
+
<Text style={styles.chartTitle}>{reportData.title}</Text>
|
|
307
|
+
<View style={styles.pieContainer}>
|
|
308
|
+
{data.map((item, index) => {
|
|
309
|
+
const value = item.shipments || item.revenue || item.value || 0;
|
|
310
|
+
const label = item.direction || item.mode || item.label || `Item ${index + 1}`;
|
|
311
|
+
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0';
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<View key={index} style={styles.pieItem}>
|
|
315
|
+
<View style={[styles.pieColor, { backgroundColor: getColor(index) }]} />
|
|
316
|
+
<Text style={styles.pieLabel}>{label}:</Text>
|
|
317
|
+
<Text style={styles.pieValue}>
|
|
318
|
+
{formatNumber(value)} ({percentage}%)
|
|
319
|
+
</Text>
|
|
320
|
+
</View>
|
|
321
|
+
);
|
|
322
|
+
})}
|
|
323
|
+
</View>
|
|
324
|
+
{total > 0 && (
|
|
325
|
+
<Text style={styles.totalText}>Total: {formatNumber(total)}</Text>
|
|
326
|
+
)}
|
|
327
|
+
</View>
|
|
328
|
+
);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const formatNumber = (num) => {
|
|
332
|
+
return num.toLocaleString();
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const getColor = (index) => {
|
|
336
|
+
const colors = ['#4CAF50', '#2196F3', '#FF9800', '#E91E63', '#9C27B0', '#00BCD4'];
|
|
337
|
+
return colors[index % colors.length];
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const handleClose = () => {
|
|
341
|
+
setSelectedReport(null);
|
|
342
|
+
setReportData(null);
|
|
343
|
+
if (onClose) onClose();
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
if (selectedReport) {
|
|
347
|
+
return (
|
|
348
|
+
<View style={styles.container}>
|
|
349
|
+
<View style={styles.header}>
|
|
350
|
+
<TouchableOpacity
|
|
351
|
+
style={styles.backButton}
|
|
352
|
+
onPress={() => {
|
|
353
|
+
setSelectedReport(null);
|
|
354
|
+
setReportData(null);
|
|
355
|
+
}}
|
|
356
|
+
>
|
|
357
|
+
<Text style={styles.backText}>‹</Text>
|
|
358
|
+
<Text style={styles.backButtonText}>Back</Text>
|
|
359
|
+
</TouchableOpacity>
|
|
360
|
+
<Text style={styles.headerTitle} numberOfLines={1}>
|
|
361
|
+
{selectedReport.title || selectedReport.name}
|
|
362
|
+
</Text>
|
|
363
|
+
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
|
|
364
|
+
<Text style={styles.closeText}>×</Text>
|
|
365
|
+
</TouchableOpacity>
|
|
366
|
+
</View>
|
|
367
|
+
|
|
368
|
+
{loading ? (
|
|
369
|
+
<View style={styles.loadingContainer}>
|
|
370
|
+
<ActivityIndicator size="large" color="#4CAF50" />
|
|
371
|
+
<Text style={styles.loadingText}>Loading report data...</Text>
|
|
372
|
+
</View>
|
|
373
|
+
) : error ? (
|
|
374
|
+
<View style={styles.errorContainer}>
|
|
375
|
+
<Text style={styles.errorText}>Error: {error}</Text>
|
|
376
|
+
<TouchableOpacity
|
|
377
|
+
style={styles.retryButton}
|
|
378
|
+
onPress={() => handleReportClick(selectedReport)}
|
|
379
|
+
>
|
|
380
|
+
<Text style={styles.retryText}>Retry</Text>
|
|
381
|
+
</TouchableOpacity>
|
|
382
|
+
</View>
|
|
383
|
+
) : (
|
|
384
|
+
<ScrollView style={styles.chartView}>
|
|
385
|
+
{renderChart()}
|
|
386
|
+
|
|
387
|
+
{reportData && reportData.data && (
|
|
388
|
+
<View style={styles.dataInfo}>
|
|
389
|
+
<Text style={styles.infoTitle}>Report Information</Text>
|
|
390
|
+
<Text style={styles.infoText}>
|
|
391
|
+
{selectedReport.description || 'Detailed analysis report'}
|
|
392
|
+
</Text>
|
|
393
|
+
<Text style={styles.dataSource}>
|
|
394
|
+
Data points: {Array.isArray(reportData.data) ? reportData.data.length :
|
|
395
|
+
reportData.data.values?.length || reportData.data.labels?.length || 'N/A'}
|
|
396
|
+
</Text>
|
|
397
|
+
</View>
|
|
398
|
+
)}
|
|
399
|
+
</ScrollView>
|
|
400
|
+
)}
|
|
401
|
+
</View>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return (
|
|
406
|
+
<View style={styles.container}>
|
|
407
|
+
<View style={styles.header}>
|
|
408
|
+
<Text style={styles.headerTitle}>Reports Dashboard</Text>
|
|
409
|
+
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
|
|
410
|
+
<Text style={styles.closeText}>×</Text>
|
|
411
|
+
</TouchableOpacity>
|
|
412
|
+
</View>
|
|
413
|
+
|
|
414
|
+
<ScrollView style={styles.reportsList}>
|
|
415
|
+
{reportList.map((report) => (
|
|
416
|
+
<TouchableOpacity
|
|
417
|
+
key={report.id}
|
|
418
|
+
style={styles.reportCard}
|
|
419
|
+
onPress={() => handleReportClick(report)}
|
|
420
|
+
>
|
|
421
|
+
<View style={[styles.reportIcon, { backgroundColor: getColor(report.id) }]}>
|
|
422
|
+
<Text style={styles.reportIconText}>{report.id}</Text>
|
|
423
|
+
</View>
|
|
424
|
+
<View style={styles.reportInfo}>
|
|
425
|
+
<Text style={styles.reportName}>{report.name}</Text>
|
|
426
|
+
<Text style={styles.reportTitle}>{report.title}</Text>
|
|
427
|
+
<Text style={styles.reportDesc}>{report.description}</Text>
|
|
428
|
+
</View>
|
|
429
|
+
<Text style={styles.arrow}>›</Text>
|
|
430
|
+
</TouchableOpacity>
|
|
431
|
+
))}
|
|
432
|
+
</ScrollView>
|
|
433
|
+
</View>
|
|
434
|
+
);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const styles = StyleSheet.create({
|
|
438
|
+
container: {
|
|
439
|
+
flex: 1,
|
|
440
|
+
backgroundColor: '#f8f9fa',
|
|
441
|
+
},
|
|
442
|
+
header: {
|
|
443
|
+
flexDirection: 'row',
|
|
444
|
+
alignItems: 'center',
|
|
445
|
+
justifyContent: 'space-between',
|
|
446
|
+
backgroundColor: '#fff',
|
|
447
|
+
paddingHorizontal: 16,
|
|
448
|
+
paddingVertical: 12,
|
|
449
|
+
borderBottomWidth: 1,
|
|
450
|
+
borderBottomColor: '#e0e0e0',
|
|
451
|
+
},
|
|
452
|
+
headerTitle: {
|
|
453
|
+
fontSize: 18,
|
|
454
|
+
fontWeight: '700',
|
|
455
|
+
color: '#000',
|
|
456
|
+
flex: 1,
|
|
457
|
+
textAlign: 'center',
|
|
458
|
+
},
|
|
459
|
+
backButton: {
|
|
460
|
+
flexDirection: 'row',
|
|
461
|
+
alignItems: 'center',
|
|
462
|
+
padding: 8,
|
|
463
|
+
},
|
|
464
|
+
backText: {
|
|
465
|
+
fontSize: 24,
|
|
466
|
+
color: '#4CAF50',
|
|
467
|
+
marginRight: 4,
|
|
468
|
+
},
|
|
469
|
+
backButtonText: {
|
|
470
|
+
fontSize: 16,
|
|
471
|
+
color: '#4CAF50',
|
|
472
|
+
fontWeight: '600',
|
|
473
|
+
},
|
|
474
|
+
closeButton: {
|
|
475
|
+
width: 32,
|
|
476
|
+
height: 32,
|
|
477
|
+
borderRadius: 16,
|
|
478
|
+
backgroundColor: '#f0f0f0',
|
|
479
|
+
justifyContent: 'center',
|
|
480
|
+
alignItems: 'center',
|
|
481
|
+
},
|
|
482
|
+
closeText: {
|
|
483
|
+
fontSize: 20,
|
|
484
|
+
color: '#666',
|
|
485
|
+
fontWeight: '300',
|
|
486
|
+
},
|
|
487
|
+
reportsList: {
|
|
488
|
+
flex: 1,
|
|
489
|
+
padding: 16,
|
|
490
|
+
},
|
|
491
|
+
reportCard: {
|
|
492
|
+
flexDirection: 'row',
|
|
493
|
+
alignItems: 'center',
|
|
494
|
+
backgroundColor: '#fff',
|
|
495
|
+
borderRadius: 12,
|
|
496
|
+
padding: 16,
|
|
497
|
+
marginBottom: 12,
|
|
498
|
+
borderWidth: 1,
|
|
499
|
+
borderColor: '#e8e8e8',
|
|
500
|
+
shadowColor: '#000',
|
|
501
|
+
shadowOffset: { width: 0, height: 1 },
|
|
502
|
+
shadowOpacity: 0.05,
|
|
503
|
+
shadowRadius: 2,
|
|
504
|
+
elevation: 2,
|
|
505
|
+
},
|
|
506
|
+
reportIcon: {
|
|
507
|
+
width: 44,
|
|
508
|
+
height: 44,
|
|
509
|
+
borderRadius: 22,
|
|
510
|
+
justifyContent: 'center',
|
|
511
|
+
alignItems: 'center',
|
|
512
|
+
marginRight: 12,
|
|
513
|
+
},
|
|
514
|
+
reportIconText: {
|
|
515
|
+
fontSize: 18,
|
|
516
|
+
fontWeight: '700',
|
|
517
|
+
color: '#fff',
|
|
518
|
+
},
|
|
519
|
+
reportInfo: {
|
|
520
|
+
flex: 1,
|
|
521
|
+
},
|
|
522
|
+
reportName: {
|
|
523
|
+
fontSize: 16,
|
|
524
|
+
fontWeight: '600',
|
|
525
|
+
color: '#000',
|
|
526
|
+
marginBottom: 2,
|
|
527
|
+
},
|
|
528
|
+
reportTitle: {
|
|
529
|
+
fontSize: 14,
|
|
530
|
+
fontWeight: '500',
|
|
531
|
+
color: '#4CAF50',
|
|
532
|
+
marginBottom: 4,
|
|
533
|
+
},
|
|
534
|
+
reportDesc: {
|
|
535
|
+
fontSize: 13,
|
|
536
|
+
color: '#666',
|
|
537
|
+
lineHeight: 18,
|
|
538
|
+
},
|
|
539
|
+
arrow: {
|
|
540
|
+
fontSize: 24,
|
|
541
|
+
color: '#ccc',
|
|
542
|
+
fontWeight: '300',
|
|
543
|
+
},
|
|
544
|
+
chartView: {
|
|
545
|
+
flex: 1,
|
|
546
|
+
padding: 16,
|
|
547
|
+
},
|
|
548
|
+
chartContainer: {
|
|
549
|
+
backgroundColor: '#fff',
|
|
550
|
+
borderRadius: 12,
|
|
551
|
+
padding: 16,
|
|
552
|
+
marginBottom: 16,
|
|
553
|
+
borderWidth: 1,
|
|
554
|
+
borderColor: '#e8e8e8',
|
|
555
|
+
shadowColor: '#000',
|
|
556
|
+
shadowOffset: { width: 0, height: 1 },
|
|
557
|
+
shadowOpacity: 0.05,
|
|
558
|
+
shadowRadius: 2,
|
|
559
|
+
elevation: 2,
|
|
560
|
+
},
|
|
561
|
+
chartTitle: {
|
|
562
|
+
fontSize: 18,
|
|
563
|
+
fontWeight: '700',
|
|
564
|
+
color: '#000',
|
|
565
|
+
marginBottom: 20,
|
|
566
|
+
textAlign: 'center',
|
|
567
|
+
},
|
|
568
|
+
chart: {
|
|
569
|
+
flexDirection: 'row',
|
|
570
|
+
alignItems: 'flex-end',
|
|
571
|
+
justifyContent: 'space-between',
|
|
572
|
+
paddingHorizontal: 8,
|
|
573
|
+
},
|
|
574
|
+
barWrapper: {
|
|
575
|
+
alignItems: 'center',
|
|
576
|
+
marginHorizontal: 4,
|
|
577
|
+
},
|
|
578
|
+
bar: {
|
|
579
|
+
borderRadius: 4,
|
|
580
|
+
},
|
|
581
|
+
barLabel: {
|
|
582
|
+
marginTop: 8,
|
|
583
|
+
fontSize: 11,
|
|
584
|
+
color: '#666',
|
|
585
|
+
textAlign: 'center',
|
|
586
|
+
width: 60,
|
|
587
|
+
},
|
|
588
|
+
linePointWrapper: {
|
|
589
|
+
alignItems: 'center',
|
|
590
|
+
marginHorizontal: 4,
|
|
591
|
+
},
|
|
592
|
+
linePoint: {
|
|
593
|
+
backgroundColor: '#2196F3',
|
|
594
|
+
borderRadius: 2,
|
|
595
|
+
},
|
|
596
|
+
lineConnector: {
|
|
597
|
+
width: 2,
|
|
598
|
+
backgroundColor: '#2196F3',
|
|
599
|
+
opacity: 0.3,
|
|
600
|
+
marginVertical: 2,
|
|
601
|
+
flex: 1,
|
|
602
|
+
},
|
|
603
|
+
lineLabel: {
|
|
604
|
+
marginTop: 8,
|
|
605
|
+
fontSize: 11,
|
|
606
|
+
color: '#666',
|
|
607
|
+
textAlign: 'center',
|
|
608
|
+
width: 60,
|
|
609
|
+
},
|
|
610
|
+
valueText: {
|
|
611
|
+
fontSize: 10,
|
|
612
|
+
color: '#999',
|
|
613
|
+
marginTop: 4,
|
|
614
|
+
fontWeight: '500',
|
|
615
|
+
},
|
|
616
|
+
pieContainer: {
|
|
617
|
+
padding: 10,
|
|
618
|
+
},
|
|
619
|
+
pieItem: {
|
|
620
|
+
flexDirection: 'row',
|
|
621
|
+
alignItems: 'center',
|
|
622
|
+
marginBottom: 12,
|
|
623
|
+
padding: 8,
|
|
624
|
+
backgroundColor: '#f9f9f9',
|
|
625
|
+
borderRadius: 8,
|
|
626
|
+
},
|
|
627
|
+
pieColor: {
|
|
628
|
+
width: 16,
|
|
629
|
+
height: 16,
|
|
630
|
+
borderRadius: 8,
|
|
631
|
+
marginRight: 12,
|
|
632
|
+
},
|
|
633
|
+
pieLabel: {
|
|
634
|
+
fontSize: 15,
|
|
635
|
+
fontWeight: '600',
|
|
636
|
+
color: '#000',
|
|
637
|
+
flex: 1,
|
|
638
|
+
},
|
|
639
|
+
pieValue: {
|
|
640
|
+
fontSize: 14,
|
|
641
|
+
color: '#666',
|
|
642
|
+
fontWeight: '500',
|
|
643
|
+
},
|
|
644
|
+
totalText: {
|
|
645
|
+
fontSize: 16,
|
|
646
|
+
fontWeight: '700',
|
|
647
|
+
color: '#4CAF50',
|
|
648
|
+
textAlign: 'center',
|
|
649
|
+
marginTop: 16,
|
|
650
|
+
paddingTop: 16,
|
|
651
|
+
borderTopWidth: 1,
|
|
652
|
+
borderTopColor: '#eee',
|
|
653
|
+
},
|
|
654
|
+
dataInfo: {
|
|
655
|
+
backgroundColor: '#e8f5e9',
|
|
656
|
+
borderRadius: 12,
|
|
657
|
+
padding: 16,
|
|
658
|
+
marginTop: 8,
|
|
659
|
+
},
|
|
660
|
+
infoTitle: {
|
|
661
|
+
fontSize: 16,
|
|
662
|
+
fontWeight: '700',
|
|
663
|
+
color: '#2e7d32',
|
|
664
|
+
marginBottom: 8,
|
|
665
|
+
},
|
|
666
|
+
infoText: {
|
|
667
|
+
fontSize: 14,
|
|
668
|
+
color: '#555',
|
|
669
|
+
lineHeight: 20,
|
|
670
|
+
marginBottom: 8,
|
|
671
|
+
},
|
|
672
|
+
dataSource: {
|
|
673
|
+
fontSize: 13,
|
|
674
|
+
color: '#777',
|
|
675
|
+
fontStyle: 'italic',
|
|
676
|
+
},
|
|
677
|
+
loadingContainer: {
|
|
678
|
+
flex: 1,
|
|
679
|
+
justifyContent: 'center',
|
|
680
|
+
alignItems: 'center',
|
|
681
|
+
padding: 40,
|
|
682
|
+
},
|
|
683
|
+
loadingText: {
|
|
684
|
+
marginTop: 16,
|
|
685
|
+
fontSize: 16,
|
|
686
|
+
color: '#666',
|
|
687
|
+
},
|
|
688
|
+
errorContainer: {
|
|
689
|
+
flex: 1,
|
|
690
|
+
justifyContent: 'center',
|
|
691
|
+
alignItems: 'center',
|
|
692
|
+
padding: 40,
|
|
693
|
+
},
|
|
694
|
+
errorText: {
|
|
695
|
+
fontSize: 16,
|
|
696
|
+
color: '#d32f2f',
|
|
697
|
+
textAlign: 'center',
|
|
698
|
+
marginBottom: 20,
|
|
699
|
+
},
|
|
700
|
+
retryButton: {
|
|
701
|
+
backgroundColor: '#4CAF50',
|
|
702
|
+
paddingHorizontal: 24,
|
|
703
|
+
paddingVertical: 12,
|
|
704
|
+
borderRadius: 8,
|
|
705
|
+
},
|
|
706
|
+
retryText: {
|
|
707
|
+
color: '#fff',
|
|
708
|
+
fontSize: 16,
|
|
709
|
+
fontWeight: '600',
|
|
710
|
+
},
|
|
711
|
+
noDataContainer: {
|
|
712
|
+
padding: 40,
|
|
713
|
+
alignItems: 'center',
|
|
714
|
+
},
|
|
715
|
+
noDataText: {
|
|
716
|
+
fontSize: 16,
|
|
717
|
+
color: '#999',
|
|
718
|
+
textAlign: 'center',
|
|
719
|
+
},
|
|
720
|
+
|
|
721
|
+
// Table Styles
|
|
722
|
+
table: {
|
|
723
|
+
minWidth: 700,
|
|
724
|
+
marginBottom: 20,
|
|
725
|
+
},
|
|
726
|
+
tableHeader: {
|
|
727
|
+
flexDirection: 'row',
|
|
728
|
+
backgroundColor: '#2c3e50',
|
|
729
|
+
borderBottomWidth: 1,
|
|
730
|
+
borderBottomColor: '#ddd',
|
|
731
|
+
},
|
|
732
|
+
headerCell: {
|
|
733
|
+
flex: 1,
|
|
734
|
+
padding: 10,
|
|
735
|
+
minWidth: 100,
|
|
736
|
+
justifyContent: 'center',
|
|
737
|
+
alignItems: 'center',
|
|
738
|
+
borderRightWidth: 1,
|
|
739
|
+
borderRightColor: '#444',
|
|
740
|
+
},
|
|
741
|
+
firstColumn: {
|
|
742
|
+
flex: 1.5,
|
|
743
|
+
minWidth: 180,
|
|
744
|
+
},
|
|
745
|
+
headerText: {
|
|
746
|
+
color: '#fff',
|
|
747
|
+
fontWeight: '600',
|
|
748
|
+
fontSize: 11,
|
|
749
|
+
textAlign: 'center',
|
|
750
|
+
},
|
|
751
|
+
tableRow: {
|
|
752
|
+
flexDirection: 'row',
|
|
753
|
+
borderBottomWidth: 1,
|
|
754
|
+
borderBottomColor: '#eee',
|
|
755
|
+
minHeight: 40,
|
|
756
|
+
},
|
|
757
|
+
evenRow: {
|
|
758
|
+
backgroundColor: '#fff',
|
|
759
|
+
},
|
|
760
|
+
oddRow: {
|
|
761
|
+
backgroundColor: '#f9f9f9',
|
|
762
|
+
},
|
|
763
|
+
totalRow: {
|
|
764
|
+
backgroundColor: '#e8f5e9',
|
|
765
|
+
borderTopWidth: 2,
|
|
766
|
+
borderTopColor: '#4CAF50',
|
|
767
|
+
borderBottomWidth: 2,
|
|
768
|
+
borderBottomColor: '#4CAF50',
|
|
769
|
+
},
|
|
770
|
+
cell: {
|
|
771
|
+
flex: 1,
|
|
772
|
+
padding: 8,
|
|
773
|
+
minWidth: 100,
|
|
774
|
+
justifyContent: 'center',
|
|
775
|
+
alignItems: 'center',
|
|
776
|
+
borderRightWidth: 1,
|
|
777
|
+
borderRightColor: '#eee',
|
|
778
|
+
},
|
|
779
|
+
cellText: {
|
|
780
|
+
fontSize: 12,
|
|
781
|
+
color: '#333',
|
|
782
|
+
textAlign: 'center',
|
|
783
|
+
},
|
|
784
|
+
totalText: {
|
|
785
|
+
fontSize: 12,
|
|
786
|
+
fontWeight: '700',
|
|
787
|
+
color: '#2c3e50',
|
|
788
|
+
textAlign: 'center',
|
|
789
|
+
},
|
|
790
|
+
numberText: {
|
|
791
|
+
fontSize: 11,
|
|
792
|
+
fontWeight: '500',
|
|
793
|
+
fontFamily: 'monospace',
|
|
794
|
+
color: '#333',
|
|
795
|
+
},
|
|
796
|
+
percentText: {
|
|
797
|
+
fontSize: 12,
|
|
798
|
+
fontWeight: '600',
|
|
799
|
+
fontFamily: 'monospace',
|
|
800
|
+
},
|
|
801
|
+
positive: {
|
|
802
|
+
color: '#27ae60',
|
|
803
|
+
},
|
|
804
|
+
negative: {
|
|
805
|
+
color: '#e74c3c',
|
|
806
|
+
},
|
|
807
|
+
grossMargin: {
|
|
808
|
+
color: '#2c3e50',
|
|
809
|
+
fontWeight: '700',
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
export default ReportChart;
|
package/src/index.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
const BarChart = ({ data = [], height = 200 }) => {
|
|
5
|
-
const maxValue = Math.max(...data.map(d => d.value), 1);
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<View style={[styles.container, { height }]}>
|
|
9
|
-
{data.map((item, index) => {
|
|
10
|
-
const barHeight = (item.value / maxValue) * height;
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<View key={index} style={styles.barWrapper}>
|
|
14
|
-
<View style={[styles.bar, { height: barHeight }]} />
|
|
15
|
-
<Text style={styles.label}>{item.name}</Text>
|
|
16
|
-
</View>
|
|
17
|
-
);
|
|
18
|
-
})}
|
|
19
|
-
</View>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default BarChart; // ✅ DEFAULT EXPORT
|
|
24
|
-
|
|
25
|
-
const styles = StyleSheet.create({
|
|
26
|
-
container: {
|
|
27
|
-
flexDirection: 'row',
|
|
28
|
-
alignItems: 'flex-end',
|
|
29
|
-
},
|
|
30
|
-
barWrapper: {
|
|
31
|
-
flex: 1,
|
|
32
|
-
alignItems: 'center',
|
|
33
|
-
},
|
|
34
|
-
bar: {
|
|
35
|
-
width: 22,
|
|
36
|
-
backgroundColor: '#4CAF50',
|
|
37
|
-
borderRadius: 6,
|
|
38
|
-
},
|
|
39
|
-
label: {
|
|
40
|
-
marginTop: 6,
|
|
41
|
-
fontSize: 12,
|
|
42
|
-
},
|
|
43
|
-
});
|