@dhiraj0720/report1chart 2.2.7 → 2.2.9
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 +4 -4
- package/src/components/BarLineChart.jsx +96 -0
- package/src/components/DivisionSelector.jsx +1 -1
- package/src/components/FrozenTableReport2.jsx +59 -0
- package/src/components/FrozenTableReport3.jsx +59 -0
- package/src/components/LineChart.jsx +77 -28
- package/src/components/MonthSelector.jsx +20 -27
- package/src/components/ProgressBar.jsx +1 -1
- package/src/components/Report1Card.jsx +7 -17
- package/src/components/SvgBarLineChart.jsx +79 -0
- package/src/components/SvgLineChart.jsx +90 -0
- package/src/index.jsx +41 -11
- package/src/screens/Report1Screen.jsx +8 -4
- package/src/screens/Report2Screen.jsx +47 -19
- package/src/screens/Report3Screen.jsx +44 -11
- package/src/screens/ReportListScreen.jsx +6 -29
- package/src/components/BarChart.jsx +0 -48
- package/src/components/BarChartReport3.jsx +0 -21
- package/src/components/FreezeTableReport3.jsx +0 -60
- package/src/components/FrozenTable.jsx +0 -71
- package/src/components/LineChartReport3.jsx +0 -22
- package/src/components/ReportList.jsx +0 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhiraj0720/report1chart",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.9",
|
|
4
4
|
"main": "src/index.jsx",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo 'No tests'"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"axios": "^1.13.2"
|
|
9
|
+
"axios": "^1.13.2",
|
|
10
|
+
"react-native-svg": "^15.15.1"
|
|
10
11
|
},
|
|
11
12
|
"peerDependencies": {
|
|
12
13
|
"react": ">=16.8.0",
|
|
@@ -21,6 +22,5 @@
|
|
|
21
22
|
],
|
|
22
23
|
"author": "Dhiraj",
|
|
23
24
|
"license": "MIT",
|
|
24
|
-
"description": "A simple report chart and table package for React Native"
|
|
25
|
-
"devDependencies": {}
|
|
25
|
+
"description": "A simple report chart and table package for React Native"
|
|
26
26
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const BarLineChart = ({ data }) => {
|
|
5
|
+
if (!data?.series?.length) return null;
|
|
6
|
+
|
|
7
|
+
const max = Math.max(...data.series.flatMap(s => s.data));
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<View style={styles.container}>
|
|
11
|
+
<Text style={styles.title}>{data.title}</Text>
|
|
12
|
+
|
|
13
|
+
<View style={styles.chart}>
|
|
14
|
+
{data.labels.map((label, i) => (
|
|
15
|
+
<View key={label} style={styles.group}>
|
|
16
|
+
{data.series.map((s, idx) => {
|
|
17
|
+
const height = (s.data[i] / max) * 120;
|
|
18
|
+
return (
|
|
19
|
+
<View
|
|
20
|
+
key={idx}
|
|
21
|
+
style={[
|
|
22
|
+
styles.bar,
|
|
23
|
+
{
|
|
24
|
+
height,
|
|
25
|
+
backgroundColor:
|
|
26
|
+
idx === 0
|
|
27
|
+
? '#FB8C00'
|
|
28
|
+
: idx === 1
|
|
29
|
+
? '#1E88E5'
|
|
30
|
+
: '#90CAF9',
|
|
31
|
+
},
|
|
32
|
+
]}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
})}
|
|
36
|
+
<Text style={styles.label}>{label}</Text>
|
|
37
|
+
</View>
|
|
38
|
+
))}
|
|
39
|
+
</View>
|
|
40
|
+
|
|
41
|
+
<View style={styles.legend}>
|
|
42
|
+
{data.series.map(s => (
|
|
43
|
+
<Text key={s.name} style={styles.legendText}>
|
|
44
|
+
■ {s.name}
|
|
45
|
+
</Text>
|
|
46
|
+
))}
|
|
47
|
+
</View>
|
|
48
|
+
</View>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default BarLineChart;
|
|
53
|
+
|
|
54
|
+
const styles = StyleSheet.create({
|
|
55
|
+
container: {
|
|
56
|
+
backgroundColor: '#fff',
|
|
57
|
+
padding: 12,
|
|
58
|
+
borderRadius: 12,
|
|
59
|
+
marginVertical: 12,
|
|
60
|
+
},
|
|
61
|
+
title: {
|
|
62
|
+
fontWeight: '700',
|
|
63
|
+
marginBottom: 8,
|
|
64
|
+
textAlign: 'center',
|
|
65
|
+
},
|
|
66
|
+
chart: {
|
|
67
|
+
flexDirection: 'row',
|
|
68
|
+
alignItems: 'flex-end',
|
|
69
|
+
justifyContent: 'space-around',
|
|
70
|
+
height: 160,
|
|
71
|
+
},
|
|
72
|
+
group: {
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
flexDirection: 'row',
|
|
75
|
+
},
|
|
76
|
+
bar: {
|
|
77
|
+
width: 10,
|
|
78
|
+
marginHorizontal: 2,
|
|
79
|
+
borderRadius: 2,
|
|
80
|
+
},
|
|
81
|
+
label: {
|
|
82
|
+
fontSize: 10,
|
|
83
|
+
marginTop: 4,
|
|
84
|
+
position: 'absolute',
|
|
85
|
+
bottom: -16,
|
|
86
|
+
},
|
|
87
|
+
legend: {
|
|
88
|
+
flexDirection: 'row',
|
|
89
|
+
justifyContent: 'center',
|
|
90
|
+
marginTop: 16,
|
|
91
|
+
},
|
|
92
|
+
legendText: {
|
|
93
|
+
fontSize: 12,
|
|
94
|
+
marginHorizontal: 8,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ScrollView,
|
|
2
|
+
import { ScrollView, TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
3
|
|
|
4
4
|
const DivisionSelector = ({ divisions, selected, onSelect }) => (
|
|
5
5
|
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const Cell = ({ children, bold }) => (
|
|
5
|
+
<Text style={[styles.cell, bold && styles.bold]}>{children}</Text>
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
const FrozenTableReport2 = ({ rows }) => (
|
|
9
|
+
<View style={styles.container}>
|
|
10
|
+
<View style={styles.frozen}>
|
|
11
|
+
<Cell bold>AY</Cell>
|
|
12
|
+
{rows.map((r, i) => (
|
|
13
|
+
<Cell key={i} bold={r.monthLabel === 'Total'}>{r.monthLabel}</Cell>
|
|
14
|
+
))}
|
|
15
|
+
</View>
|
|
16
|
+
|
|
17
|
+
<ScrollView horizontal>
|
|
18
|
+
<View>
|
|
19
|
+
<View style={styles.headerRow}>
|
|
20
|
+
{[
|
|
21
|
+
'2024 TEU','2025 TEU','TEU %',
|
|
22
|
+
'2024 Kar','2025 Kar','Kar %',
|
|
23
|
+
'2025 Bütçe','Bütçe %'
|
|
24
|
+
].map(h => <Cell key={h} bold>{h}</Cell>)}
|
|
25
|
+
</View>
|
|
26
|
+
|
|
27
|
+
{rows.map((r, i) => (
|
|
28
|
+
<View key={i} style={styles.row}>
|
|
29
|
+
<Cell>{r.teu2024}</Cell>
|
|
30
|
+
<Cell>{r.teu2025}</Cell>
|
|
31
|
+
<Cell>{r.teuChangePercent}%</Cell>
|
|
32
|
+
<Cell>{r.profitUsd2024}</Cell>
|
|
33
|
+
<Cell>{r.profitUsd2025}</Cell>
|
|
34
|
+
<Cell>{r.profitChangePercent}%</Cell>
|
|
35
|
+
<Cell>{r.budgetProfitUsd2025}</Cell>
|
|
36
|
+
<Cell>{r.budgetChangePercent}%</Cell>
|
|
37
|
+
</View>
|
|
38
|
+
))}
|
|
39
|
+
</View>
|
|
40
|
+
</ScrollView>
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export default FrozenTableReport2;
|
|
45
|
+
|
|
46
|
+
const styles = StyleSheet.create({
|
|
47
|
+
container: { flexDirection: 'row' },
|
|
48
|
+
frozen: { width: 90, backgroundColor: '#f4f4f4' },
|
|
49
|
+
headerRow: { flexDirection: 'row', backgroundColor: '#f4f4f4' },
|
|
50
|
+
row: { flexDirection: 'row' },
|
|
51
|
+
cell: {
|
|
52
|
+
width: 90,
|
|
53
|
+
padding: 8,
|
|
54
|
+
textAlign: 'center',
|
|
55
|
+
borderBottomWidth: 1,
|
|
56
|
+
borderColor: '#ddd',
|
|
57
|
+
},
|
|
58
|
+
bold: { fontWeight: '700' },
|
|
59
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const Cell = ({ children, bold }) => (
|
|
5
|
+
<Text style={[styles.cell, bold && styles.bold]}>{children}</Text>
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
const FrozenTableReport3 = ({ rows }) => (
|
|
9
|
+
<View style={styles.container}>
|
|
10
|
+
<View style={styles.frozen}>
|
|
11
|
+
<Cell bold>AY</Cell>
|
|
12
|
+
{rows.map((r, i) => (
|
|
13
|
+
<Cell key={i} bold={r.monthLabel === 'Total'}>{r.monthLabel}</Cell>
|
|
14
|
+
))}
|
|
15
|
+
</View>
|
|
16
|
+
|
|
17
|
+
<ScrollView horizontal>
|
|
18
|
+
<View>
|
|
19
|
+
<View style={styles.headerRow}>
|
|
20
|
+
{[
|
|
21
|
+
'2024 Yük','2025 Yük','Yük %',
|
|
22
|
+
'2024 Gelir','2025 Gelir','Gelir %',
|
|
23
|
+
'2025 Bütçe','Bütçe %'
|
|
24
|
+
].map(h => <Cell key={h} bold>{h}</Cell>)}
|
|
25
|
+
</View>
|
|
26
|
+
|
|
27
|
+
{rows.map((r, i) => (
|
|
28
|
+
<View key={i} style={styles.row}>
|
|
29
|
+
<Cell>{r.loadCount2024}</Cell>
|
|
30
|
+
<Cell>{r.loadCount2025}</Cell>
|
|
31
|
+
<Cell>{r.loadCountChangePercent}%</Cell>
|
|
32
|
+
<Cell>{r.revenueTl2024}</Cell>
|
|
33
|
+
<Cell>{r.revenueTl2025}</Cell>
|
|
34
|
+
<Cell>{r.revenueChangePercent}%</Cell>
|
|
35
|
+
<Cell>{r.budgetRevenueTl2025}</Cell>
|
|
36
|
+
<Cell>{r.budgetChangePercent}%</Cell>
|
|
37
|
+
</View>
|
|
38
|
+
))}
|
|
39
|
+
</View>
|
|
40
|
+
</ScrollView>
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export default FrozenTableReport3;
|
|
45
|
+
|
|
46
|
+
const styles = StyleSheet.create({
|
|
47
|
+
container: { flexDirection: 'row' },
|
|
48
|
+
frozen: { width: 90, backgroundColor: '#f4f4f4' },
|
|
49
|
+
headerRow: { flexDirection: 'row', backgroundColor: '#f4f4f4' },
|
|
50
|
+
row: { flexDirection: 'row' },
|
|
51
|
+
cell: {
|
|
52
|
+
width: 100,
|
|
53
|
+
padding: 8,
|
|
54
|
+
textAlign: 'center',
|
|
55
|
+
borderBottomWidth: 1,
|
|
56
|
+
borderColor: '#ddd',
|
|
57
|
+
},
|
|
58
|
+
bold: { fontWeight: '700' },
|
|
59
|
+
});
|
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View, Text,
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
3
|
|
|
4
4
|
const LineChart = ({ data }) => {
|
|
5
|
-
if (!data?.series) return null;
|
|
5
|
+
if (!data?.series?.length) return null;
|
|
6
6
|
|
|
7
7
|
const max = Math.max(...data.series.flatMap(s => s.data));
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
10
|
<View style={styles.container}>
|
|
11
11
|
<Text style={styles.title}>{data.title}</Text>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
12
|
+
|
|
13
|
+
<View style={styles.chart}>
|
|
14
|
+
{data.labels.map((label, i) => (
|
|
15
|
+
<View key={label} style={styles.column}>
|
|
16
|
+
<View style={styles.lines}>
|
|
17
|
+
{data.series.map((s, idx) => {
|
|
18
|
+
const height = (s.data[i] / max) * 120;
|
|
19
|
+
return (
|
|
20
|
+
<View
|
|
21
|
+
key={idx}
|
|
22
|
+
style={[
|
|
23
|
+
styles.point,
|
|
24
|
+
{
|
|
25
|
+
bottom: height,
|
|
26
|
+
backgroundColor: idx === 0 ? '#FB8C00' : '#1E88E5',
|
|
27
|
+
},
|
|
28
|
+
]}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
})}
|
|
29
32
|
</View>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
<Text style={styles.label}>{label}</Text>
|
|
34
|
+
</View>
|
|
35
|
+
))}
|
|
36
|
+
</View>
|
|
37
|
+
|
|
38
|
+
<View style={styles.legend}>
|
|
39
|
+
{data.series.map((s, i) => (
|
|
40
|
+
<Text key={s.name} style={styles.legendText}>
|
|
41
|
+
● {s.name}
|
|
42
|
+
</Text>
|
|
43
|
+
))}
|
|
44
|
+
</View>
|
|
33
45
|
</View>
|
|
34
46
|
);
|
|
35
47
|
};
|
|
@@ -37,10 +49,47 @@ const LineChart = ({ data }) => {
|
|
|
37
49
|
export default LineChart;
|
|
38
50
|
|
|
39
51
|
const styles = StyleSheet.create({
|
|
40
|
-
container: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
container: {
|
|
53
|
+
backgroundColor: '#fff',
|
|
54
|
+
padding: 12,
|
|
55
|
+
borderRadius: 12,
|
|
56
|
+
marginVertical: 12,
|
|
57
|
+
},
|
|
58
|
+
title: {
|
|
59
|
+
fontWeight: '700',
|
|
60
|
+
marginBottom: 8,
|
|
61
|
+
textAlign: 'center',
|
|
62
|
+
},
|
|
63
|
+
chart: {
|
|
64
|
+
flexDirection: 'row',
|
|
65
|
+
justifyContent: 'space-around',
|
|
66
|
+
height: 160,
|
|
67
|
+
},
|
|
68
|
+
column: {
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
width: 40,
|
|
71
|
+
},
|
|
72
|
+
lines: {
|
|
73
|
+
flex: 1,
|
|
74
|
+
justifyContent: 'flex-end',
|
|
75
|
+
},
|
|
76
|
+
point: {
|
|
77
|
+
width: 8,
|
|
78
|
+
height: 8,
|
|
79
|
+
borderRadius: 4,
|
|
80
|
+
position: 'absolute',
|
|
81
|
+
},
|
|
82
|
+
label: {
|
|
83
|
+
fontSize: 10,
|
|
84
|
+
marginTop: 4,
|
|
85
|
+
},
|
|
86
|
+
legend: {
|
|
87
|
+
flexDirection: 'row',
|
|
88
|
+
justifyContent: 'center',
|
|
89
|
+
marginTop: 8,
|
|
90
|
+
},
|
|
91
|
+
legendText: {
|
|
92
|
+
fontSize: 12,
|
|
93
|
+
marginHorizontal: 8,
|
|
94
|
+
},
|
|
46
95
|
});
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ScrollView, TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
3
|
|
|
4
|
-
const MonthSelector = ({ months, selected, onSelect }) =>
|
|
5
|
-
|
|
6
|
-
<
|
|
4
|
+
const MonthSelector = ({ months, selected, onSelect }) => (
|
|
5
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.row}>
|
|
6
|
+
<TouchableOpacity
|
|
7
|
+
style={[styles.btn, selected === 'ALL' && styles.active]}
|
|
8
|
+
onPress={() => onSelect('ALL')}
|
|
9
|
+
>
|
|
10
|
+
<Text style={styles.text}>Select all</Text>
|
|
11
|
+
</TouchableOpacity>
|
|
12
|
+
|
|
13
|
+
{months.map(m => (
|
|
7
14
|
<TouchableOpacity
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
key={m}
|
|
16
|
+
style={[styles.btn, selected === m && styles.active]}
|
|
17
|
+
onPress={() => onSelect(m)}
|
|
10
18
|
>
|
|
11
|
-
<Text style={styles.text}>
|
|
19
|
+
<Text style={styles.text}>{m}</Text>
|
|
12
20
|
</TouchableOpacity>
|
|
21
|
+
))}
|
|
22
|
+
</ScrollView>
|
|
23
|
+
);
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
<TouchableOpacity
|
|
16
|
-
key={m}
|
|
17
|
-
style={[styles.btn, selected === m && styles.active]}
|
|
18
|
-
onPress={() => onSelect(m)}
|
|
19
|
-
>
|
|
20
|
-
<Text style={styles.text}>{m}</Text>
|
|
21
|
-
</TouchableOpacity>
|
|
22
|
-
))}
|
|
23
|
-
</ScrollView>
|
|
24
|
-
);
|
|
25
|
-
};
|
|
25
|
+
export default MonthSelector;
|
|
26
26
|
|
|
27
27
|
const styles = StyleSheet.create({
|
|
28
28
|
row: { paddingVertical: 10 },
|
|
@@ -33,13 +33,6 @@ const styles = StyleSheet.create({
|
|
|
33
33
|
backgroundColor: '#eef2f7',
|
|
34
34
|
marginRight: 8,
|
|
35
35
|
},
|
|
36
|
-
active: {
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
text: {
|
|
40
|
-
color: '#000',
|
|
41
|
-
fontWeight: '600',
|
|
42
|
-
},
|
|
36
|
+
active: { backgroundColor: '#0f172a' },
|
|
37
|
+
text: { color: '#000', fontWeight: '600' },
|
|
43
38
|
});
|
|
44
|
-
|
|
45
|
-
export default MonthSelector;
|
|
@@ -2,13 +2,13 @@ import React from 'react';
|
|
|
2
2
|
import { View, Text, StyleSheet } from 'react-native';
|
|
3
3
|
import ProgressBar from './ProgressBar';
|
|
4
4
|
|
|
5
|
-
const format =
|
|
5
|
+
const format = n => (n ?? 0).toLocaleString();
|
|
6
6
|
|
|
7
7
|
const Trend = ({ value }) => {
|
|
8
|
-
const
|
|
8
|
+
const up = value >= 0;
|
|
9
9
|
return (
|
|
10
|
-
<Text style={[styles.trend,
|
|
11
|
-
{
|
|
10
|
+
<Text style={[styles.trend, up ? styles.up : styles.down]}>
|
|
11
|
+
{up ? '↑' : '↓'} {Math.abs(value)}%
|
|
12
12
|
</Text>
|
|
13
13
|
);
|
|
14
14
|
};
|
|
@@ -23,10 +23,8 @@ const Row = ({ label, value }) => (
|
|
|
23
23
|
const Report1Card = ({ item }) => {
|
|
24
24
|
return (
|
|
25
25
|
<View style={styles.card}>
|
|
26
|
-
{/* TITLE */}
|
|
27
26
|
<Text style={styles.title}>{item.name}</Text>
|
|
28
27
|
|
|
29
|
-
{/* VALUES */}
|
|
30
28
|
<Row label="2024 Actual" value={format(item.actual2024)} />
|
|
31
29
|
<Row label="2025 Actual" value={format(item.actual2025)} />
|
|
32
30
|
|
|
@@ -42,16 +40,14 @@ const Report1Card = ({ item }) => {
|
|
|
42
40
|
<Trend value={item.budgetVariancePercent} />
|
|
43
41
|
</View>
|
|
44
42
|
|
|
45
|
-
{/* OPEX / GROSS PROFIT */}
|
|
46
43
|
{item.opexToGrossProfitPercent !== undefined && (
|
|
47
44
|
<>
|
|
48
45
|
<View style={styles.row}>
|
|
49
|
-
<Text style={styles.label}>
|
|
46
|
+
<Text style={styles.label}>Faaliyet Gid. / Brüt Kar</Text>
|
|
50
47
|
<Text style={styles.value}>
|
|
51
48
|
{item.opexToGrossProfitPercent}%
|
|
52
49
|
</Text>
|
|
53
50
|
</View>
|
|
54
|
-
|
|
55
51
|
<ProgressBar value={item.opexToGrossProfitPercent} />
|
|
56
52
|
</>
|
|
57
53
|
)}
|
|
@@ -74,7 +70,6 @@ const styles = StyleSheet.create({
|
|
|
74
70
|
fontSize: 16,
|
|
75
71
|
fontWeight: '700',
|
|
76
72
|
marginBottom: 12,
|
|
77
|
-
color: '#000',
|
|
78
73
|
},
|
|
79
74
|
row: {
|
|
80
75
|
flexDirection: 'row',
|
|
@@ -88,16 +83,11 @@ const styles = StyleSheet.create({
|
|
|
88
83
|
value: {
|
|
89
84
|
fontSize: 13,
|
|
90
85
|
fontWeight: '600',
|
|
91
|
-
color: '#000',
|
|
92
86
|
},
|
|
93
87
|
trend: {
|
|
94
88
|
fontSize: 13,
|
|
95
89
|
fontWeight: '700',
|
|
96
90
|
},
|
|
97
|
-
up: {
|
|
98
|
-
|
|
99
|
-
},
|
|
100
|
-
down: {
|
|
101
|
-
color: '#d32f2f',
|
|
102
|
-
},
|
|
91
|
+
up: { color: '#2e7d32' },
|
|
92
|
+
down: { color: '#d32f2f' },
|
|
103
93
|
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, ScrollView } from 'react-native';
|
|
3
|
+
import Svg, { Rect, Circle, Text as SvgText } from 'react-native-svg';
|
|
4
|
+
|
|
5
|
+
const SvgBarLineChart = ({ data }) => {
|
|
6
|
+
if (!data?.series) return null;
|
|
7
|
+
|
|
8
|
+
const width = Math.max(360, data.labels.length * 90);
|
|
9
|
+
const height = 240;
|
|
10
|
+
const padding = 40;
|
|
11
|
+
|
|
12
|
+
const max = Math.max(...data.series.flatMap(s => s.data));
|
|
13
|
+
const barWidth = 14;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<View style={{ marginVertical: 16 }}>
|
|
17
|
+
<Text style={{ fontWeight: '700', marginBottom: 8 }}>
|
|
18
|
+
{data.title}
|
|
19
|
+
</Text>
|
|
20
|
+
|
|
21
|
+
<ScrollView horizontal>
|
|
22
|
+
<Svg width={width} height={height}>
|
|
23
|
+
{data.labels.map((label, i) => {
|
|
24
|
+
const x = padding + i * 70;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<React.Fragment key={i}>
|
|
28
|
+
{/* 2024 bar */}
|
|
29
|
+
<Rect
|
|
30
|
+
x={x}
|
|
31
|
+
y={height - (data.series[0].data[i] / max) * 150}
|
|
32
|
+
width={barWidth}
|
|
33
|
+
height={(data.series[0].data[i] / max) * 150}
|
|
34
|
+
fill="#E07A3F"
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
{/* 2025 bar */}
|
|
38
|
+
<Rect
|
|
39
|
+
x={x + barWidth + 4}
|
|
40
|
+
y={height - (data.series[1].data[i] / max) * 150}
|
|
41
|
+
width={barWidth}
|
|
42
|
+
height={(data.series[1].data[i] / max) * 150}
|
|
43
|
+
fill="#4E79A7"
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
{/* Budget dot */}
|
|
47
|
+
<Circle
|
|
48
|
+
cx={x + barWidth}
|
|
49
|
+
cy={height - (data.series[2].data[i] / max) * 150}
|
|
50
|
+
r={4}
|
|
51
|
+
fill="#8AB6E8"
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<SvgText
|
|
55
|
+
x={x + barWidth}
|
|
56
|
+
y={height - 6}
|
|
57
|
+
fontSize="10"
|
|
58
|
+
textAnchor="middle"
|
|
59
|
+
>
|
|
60
|
+
{label}
|
|
61
|
+
</SvgText>
|
|
62
|
+
</React.Fragment>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
</Svg>
|
|
66
|
+
</ScrollView>
|
|
67
|
+
|
|
68
|
+
<View style={{ flexDirection: 'row', marginTop: 8 }}>
|
|
69
|
+
{data.series.map((s, i) => (
|
|
70
|
+
<Text key={i} style={{ marginRight: 16 }}>
|
|
71
|
+
● {s.name}
|
|
72
|
+
</Text>
|
|
73
|
+
))}
|
|
74
|
+
</View>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default SvgBarLineChart;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, ScrollView } from 'react-native';
|
|
3
|
+
import Svg, { Line, Circle, Text as SvgText } from 'react-native-svg';
|
|
4
|
+
|
|
5
|
+
const SvgLineChart = ({ data }) => {
|
|
6
|
+
if (!data?.series) return null;
|
|
7
|
+
|
|
8
|
+
const width = Math.max(360, data.labels.length * 80);
|
|
9
|
+
const height = 220;
|
|
10
|
+
const padding = 40;
|
|
11
|
+
|
|
12
|
+
const values = data.series.flatMap(s => s.data);
|
|
13
|
+
const max = Math.max(...values);
|
|
14
|
+
const min = Math.min(...values);
|
|
15
|
+
|
|
16
|
+
const y = v =>
|
|
17
|
+
height - padding -
|
|
18
|
+
((v - min) / (max - min || 1)) * (height - padding * 2);
|
|
19
|
+
|
|
20
|
+
const xStep = (width - padding * 2) / (data.labels.length - 1 || 1);
|
|
21
|
+
const colors = ['#E07A3F', '#4E79A7'];
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<View style={{ marginVertical: 16 }}>
|
|
25
|
+
<Text style={{ fontWeight: '700', marginBottom: 8 }}>
|
|
26
|
+
{data.title}
|
|
27
|
+
</Text>
|
|
28
|
+
|
|
29
|
+
<ScrollView horizontal>
|
|
30
|
+
<Svg width={width} height={height}>
|
|
31
|
+
{/* Grid */}
|
|
32
|
+
{[0.25, 0.5, 0.75].map((p, i) => (
|
|
33
|
+
<Line
|
|
34
|
+
key={i}
|
|
35
|
+
x1={padding}
|
|
36
|
+
x2={width - padding}
|
|
37
|
+
y1={padding + (height - padding * 2) * p}
|
|
38
|
+
y2={padding + (height - padding * 2) * p}
|
|
39
|
+
stroke="#ccc"
|
|
40
|
+
strokeDasharray="4"
|
|
41
|
+
/>
|
|
42
|
+
))}
|
|
43
|
+
|
|
44
|
+
{data.series.map((s, si) =>
|
|
45
|
+
s.data.map((v, i) => {
|
|
46
|
+
const x = padding + i * xStep;
|
|
47
|
+
const yVal = y(v);
|
|
48
|
+
|
|
49
|
+
if (i === s.data.length - 1) return null;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<React.Fragment key={`${si}-${i}`}>
|
|
53
|
+
<Line
|
|
54
|
+
x1={x}
|
|
55
|
+
y1={yVal}
|
|
56
|
+
x2={x + xStep}
|
|
57
|
+
y2={y(s.data[i + 1])}
|
|
58
|
+
stroke={colors[si]}
|
|
59
|
+
strokeWidth={2}
|
|
60
|
+
strokeDasharray={si === 0 ? '4' : '0'}
|
|
61
|
+
/>
|
|
62
|
+
<Circle cx={x} cy={yVal} r={4} fill={colors[si]} />
|
|
63
|
+
<SvgText
|
|
64
|
+
x={x}
|
|
65
|
+
y={yVal - 8}
|
|
66
|
+
fontSize="10"
|
|
67
|
+
textAnchor="middle"
|
|
68
|
+
>
|
|
69
|
+
{v.toLocaleString()}
|
|
70
|
+
</SvgText>
|
|
71
|
+
</React.Fragment>
|
|
72
|
+
);
|
|
73
|
+
})
|
|
74
|
+
)}
|
|
75
|
+
</Svg>
|
|
76
|
+
</ScrollView>
|
|
77
|
+
|
|
78
|
+
{/* Legend */}
|
|
79
|
+
<View style={{ flexDirection: 'row', marginTop: 8 }}>
|
|
80
|
+
{data.series.map((s, i) => (
|
|
81
|
+
<Text key={i} style={{ marginRight: 16, color: colors[i] }}>
|
|
82
|
+
● {s.name}
|
|
83
|
+
</Text>
|
|
84
|
+
))}
|
|
85
|
+
</View>
|
|
86
|
+
</View>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default SvgLineChart;
|
package/src/index.jsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { View, ActivityIndicator } from 'react-native';
|
|
2
3
|
|
|
3
4
|
import ReportListScreen from './screens/ReportListScreen';
|
|
4
5
|
import Report1Screen from './screens/Report1Screen';
|
|
@@ -6,38 +7,67 @@ import Report2Screen from './screens/Report2Screen';
|
|
|
6
7
|
import Report3Screen from './screens/Report3Screen';
|
|
7
8
|
|
|
8
9
|
const AnalyticsReports = ({ config }) => {
|
|
9
|
-
const [
|
|
10
|
+
const [active, setActive] = useState(null);
|
|
11
|
+
const [loading, setLoading] = useState(true);
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// SPLASH / LOADER (5 seconds)
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
setLoading(false);
|
|
17
|
+
}, 5000);
|
|
18
|
+
|
|
19
|
+
return () => clearTimeout(timer);
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
// 👉 SHOW LOADER FIRST (BEFORE REPORT LIST)
|
|
23
|
+
if (loading) {
|
|
24
|
+
return (
|
|
25
|
+
<View
|
|
26
|
+
style={{
|
|
27
|
+
flex: 1,
|
|
28
|
+
justifyContent: 'center',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<ActivityIndicator size="large" />
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 👉 AFTER LOADER → SHOW REPORT LIST
|
|
38
|
+
if (!active) {
|
|
39
|
+
return <ReportListScreen onSelect={setActive} />;
|
|
13
40
|
}
|
|
14
41
|
|
|
15
|
-
|
|
42
|
+
// 👉 REPORT 1
|
|
43
|
+
if (active === 1) {
|
|
16
44
|
return (
|
|
17
45
|
<Report1Screen
|
|
18
|
-
|
|
46
|
+
endpoint={config.report1.url}
|
|
19
47
|
token={config.token}
|
|
20
|
-
onBack={() =>
|
|
48
|
+
onBack={() => setActive(null)}
|
|
21
49
|
/>
|
|
22
50
|
);
|
|
23
51
|
}
|
|
24
52
|
|
|
25
|
-
|
|
53
|
+
// 👉 REPORT 2
|
|
54
|
+
if (active === 2) {
|
|
26
55
|
return (
|
|
27
56
|
<Report2Screen
|
|
28
57
|
api={config.report2}
|
|
29
58
|
token={config.token}
|
|
30
|
-
onBack={() =>
|
|
59
|
+
onBack={() => setActive(null)}
|
|
31
60
|
/>
|
|
32
61
|
);
|
|
33
62
|
}
|
|
34
63
|
|
|
35
|
-
|
|
64
|
+
// 👉 REPORT 3
|
|
65
|
+
if (active === 3) {
|
|
36
66
|
return (
|
|
37
67
|
<Report3Screen
|
|
38
68
|
api={config.report3}
|
|
39
69
|
token={config.token}
|
|
40
|
-
onBack={() =>
|
|
70
|
+
onBack={() => setActive(null)}
|
|
41
71
|
/>
|
|
42
72
|
);
|
|
43
73
|
}
|
|
@@ -3,18 +3,22 @@ import { ScrollView, Text, ActivityIndicator } from 'react-native';
|
|
|
3
3
|
import fetchReport1 from '../api/report1Fetcher';
|
|
4
4
|
import Report1Card from '../components/Report1Card';
|
|
5
5
|
|
|
6
|
-
const Report1Screen = ({
|
|
6
|
+
const Report1Screen = ({ endpoint, token, onBack }) => {
|
|
7
7
|
const [rows, setRows] = useState(null);
|
|
8
8
|
|
|
9
9
|
useEffect(() => {
|
|
10
|
-
fetchReport1(
|
|
10
|
+
fetchReport1(endpoint, token).then(setRows);
|
|
11
11
|
}, []);
|
|
12
12
|
|
|
13
13
|
if (!rows) return <ActivityIndicator />;
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
|
-
<ScrollView>
|
|
17
|
-
<Text onPress={onBack}>‹ Back</Text>
|
|
16
|
+
<ScrollView style={{ padding: 16 }}>
|
|
17
|
+
<Text onPress={onBack} style={{ marginBottom: 12 }}>‹ Back</Text>
|
|
18
|
+
<Text style={{ fontSize: 18, fontWeight: '700', marginBottom: 12 }}>
|
|
19
|
+
PERFORMANS RAPORU (USD)
|
|
20
|
+
</Text>
|
|
21
|
+
|
|
18
22
|
{rows.map((r, i) => (
|
|
19
23
|
<Report1Card key={i} item={r} />
|
|
20
24
|
))}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { ScrollView, Text, ActivityIndicator } from 'react-native';
|
|
3
|
+
|
|
3
4
|
import { getDivisions, getTable, getLine, getBar } from '../api/report2Fetcher';
|
|
4
5
|
import MonthSelector from '../components/MonthSelector';
|
|
5
6
|
import DivisionSelector from '../components/DivisionSelector';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import FrozenTableReport2 from '../components/FrozenTableReport2';
|
|
8
|
+
import SvgLineChart from '../components/SvgLineChart';
|
|
9
|
+
import SvgBarLineChart from '../components/SvgBarLineChart';
|
|
9
10
|
|
|
10
|
-
const Report2Screen = ({
|
|
11
|
+
const Report2Screen = ({ api, token, onBack }) => {
|
|
11
12
|
const [divisions, setDivisions] = useState([]);
|
|
12
13
|
const [division, setDivision] = useState(null);
|
|
14
|
+
const [month, setMonth] = useState('ALL');
|
|
13
15
|
const [table, setTable] = useState(null);
|
|
14
16
|
const [line, setLine] = useState(null);
|
|
15
17
|
const [bar, setBar] = useState(null);
|
|
16
|
-
const [month, setMonth] = useState(null);
|
|
17
18
|
|
|
18
19
|
useEffect(() => {
|
|
19
|
-
getDivisions(
|
|
20
|
+
getDivisions(api.divisions, token).then(d => {
|
|
20
21
|
setDivisions(d);
|
|
21
22
|
setDivision(d[0]?.code);
|
|
22
23
|
});
|
|
@@ -25,43 +26,70 @@ const Report2Screen = ({ report2, token, onBack }) => {
|
|
|
25
26
|
useEffect(() => {
|
|
26
27
|
if (!division) return;
|
|
27
28
|
Promise.all([
|
|
28
|
-
getTable(
|
|
29
|
-
getLine(
|
|
30
|
-
getBar(
|
|
29
|
+
getTable(api.table, division, token),
|
|
30
|
+
getLine(api.line, division, token),
|
|
31
|
+
getBar(api.bar, division, token),
|
|
31
32
|
]).then(([t, l, b]) => {
|
|
32
33
|
setTable(t);
|
|
33
34
|
setLine(l);
|
|
34
35
|
setBar(b);
|
|
35
|
-
setMonth(
|
|
36
|
+
setMonth('ALL');
|
|
36
37
|
});
|
|
37
38
|
}, [division]);
|
|
38
39
|
|
|
39
40
|
if (!table || !line || !bar) return <ActivityIndicator />;
|
|
40
41
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const filterChartByMonth = (chart, month) => {
|
|
43
|
+
if (month === 'ALL') return chart;
|
|
44
|
+
const index = chart.labels.indexOf(month);
|
|
45
|
+
if (index === -1) return chart;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
...chart,
|
|
49
|
+
labels: [chart.labels[index]],
|
|
50
|
+
series: chart.series.map(s => ({
|
|
51
|
+
...s,
|
|
52
|
+
data: [s.data[index]],
|
|
53
|
+
})),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
const rows =
|
|
59
|
+
month === 'ALL'
|
|
60
|
+
? table.rows
|
|
61
|
+
: table.rows.filter(r => r.monthLabel === month);
|
|
62
|
+
|
|
63
|
+
const filteredLine = filterChartByMonth(line, month);
|
|
64
|
+
const filteredBar = filterChartByMonth(bar, month);
|
|
65
|
+
|
|
44
66
|
|
|
45
67
|
return (
|
|
46
|
-
<ScrollView>
|
|
68
|
+
<ScrollView style={{ padding: 16 }}>
|
|
47
69
|
<Text onPress={onBack}>‹ Back</Text>
|
|
48
70
|
|
|
71
|
+
<Text style={{ fontSize: 18, fontWeight: '700', marginVertical: 12 }}>
|
|
72
|
+
{table.title}
|
|
73
|
+
</Text>
|
|
74
|
+
|
|
49
75
|
<MonthSelector
|
|
50
76
|
months={line.labels}
|
|
51
77
|
selected={month}
|
|
52
78
|
onSelect={setMonth}
|
|
53
79
|
/>
|
|
54
80
|
|
|
55
|
-
<FrozenTable rows={rows} />
|
|
56
|
-
|
|
57
|
-
<LineChart data={line} />
|
|
58
|
-
<BarChart data={bar} />
|
|
59
|
-
|
|
60
81
|
<DivisionSelector
|
|
61
82
|
divisions={divisions}
|
|
62
83
|
selected={division}
|
|
63
84
|
onSelect={setDivision}
|
|
64
85
|
/>
|
|
86
|
+
|
|
87
|
+
<FrozenTableReport2 rows={rows} />
|
|
88
|
+
|
|
89
|
+
<SvgLineChart data={filteredLine} />
|
|
90
|
+
<SvgBarLineChart data={filteredBar} />
|
|
91
|
+
|
|
92
|
+
|
|
65
93
|
</ScrollView>
|
|
66
94
|
);
|
|
67
95
|
};
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { ScrollView, ActivityIndicator } from 'react-native';
|
|
2
|
+
import { ScrollView, Text, ActivityIndicator } from 'react-native';
|
|
3
|
+
|
|
3
4
|
import {
|
|
4
5
|
fetchReport3Table,
|
|
5
6
|
fetchReport3Line,
|
|
6
|
-
fetchReport3Bar
|
|
7
|
+
fetchReport3Bar,
|
|
7
8
|
} from '../api/report3Fetcher';
|
|
8
9
|
|
|
9
10
|
import MonthSelector from '../components/MonthSelector';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
11
|
+
import FrozenTableReport3 from '../components/FrozenTableReport3';
|
|
12
|
+
import SvgLineChart from '../components/SvgLineChart';
|
|
13
|
+
import SvgBarLineChart from '../components/SvgBarLineChart';
|
|
13
14
|
|
|
14
|
-
const Report3Screen = ({ api, token }) => {
|
|
15
|
+
const Report3Screen = ({ api, token, onBack }) => {
|
|
15
16
|
const [table, setTable] = useState(null);
|
|
16
17
|
const [line, setLine] = useState(null);
|
|
17
18
|
const [bar, setBar] = useState(null);
|
|
@@ -25,18 +26,50 @@ const Report3Screen = ({ api, token }) => {
|
|
|
25
26
|
|
|
26
27
|
if (!table || !line || !bar) return <ActivityIndicator />;
|
|
27
28
|
|
|
29
|
+
const filterChartByMonth = (chart, month) => {
|
|
30
|
+
if (month === 'ALL') return chart;
|
|
31
|
+
const index = chart.labels.indexOf(month);
|
|
32
|
+
if (index === -1) return chart;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...chart,
|
|
36
|
+
labels: [chart.labels[index]],
|
|
37
|
+
series: chart.series.map(s => ({
|
|
38
|
+
...s,
|
|
39
|
+
data: [s.data[index]],
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
28
44
|
const months = table.rows.filter(r => r.month !== 99).map(r => r.monthLabel);
|
|
45
|
+
|
|
29
46
|
const rows =
|
|
30
47
|
month === 'ALL'
|
|
31
48
|
? table.rows
|
|
32
49
|
: table.rows.filter(r => r.monthLabel === month);
|
|
33
50
|
|
|
51
|
+
const filteredLine = filterChartByMonth(line, month);
|
|
52
|
+
const filteredBar = filterChartByMonth(bar, month);
|
|
53
|
+
|
|
54
|
+
|
|
34
55
|
return (
|
|
35
|
-
<ScrollView>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
56
|
+
<ScrollView style={{ padding: 16 }}>
|
|
57
|
+
<Text onPress={onBack}>‹ Back</Text>
|
|
58
|
+
|
|
59
|
+
<Text style={{ fontSize: 18, fontWeight: '700', marginVertical: 12 }}>
|
|
60
|
+
{table.title}
|
|
61
|
+
</Text>
|
|
62
|
+
|
|
63
|
+
<MonthSelector
|
|
64
|
+
months={months}
|
|
65
|
+
selected={month}
|
|
66
|
+
onSelect={setMonth}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<FrozenTableReport3 rows={rows} />
|
|
70
|
+
|
|
71
|
+
<SvgLineChart data={filteredLine} />
|
|
72
|
+
<SvgBarLineChart data={filteredBar} />
|
|
40
73
|
</ScrollView>
|
|
41
74
|
);
|
|
42
75
|
};
|
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import ReportCard from '../components/ReportCard';
|
|
3
4
|
|
|
4
5
|
const ReportListScreen = ({ onSelect }) => {
|
|
5
|
-
const reports = [
|
|
6
|
-
{ id: 1, title: 'Report 1' },
|
|
7
|
-
{ id: 2, title: 'Report 2' },
|
|
8
|
-
{ id: 3, title: 'Report 3' },
|
|
9
|
-
];
|
|
10
|
-
|
|
11
6
|
return (
|
|
12
|
-
<View style={
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
style={styles.card}
|
|
17
|
-
onPress={() => onSelect(r.id)}
|
|
18
|
-
>
|
|
19
|
-
<Text style={styles.text}>{r.title}</Text>
|
|
20
|
-
</TouchableOpacity>
|
|
21
|
-
))}
|
|
7
|
+
<View style={{ padding: 16 }}>
|
|
8
|
+
<ReportCard title="Report 1" onPress={() => onSelect(1)} />
|
|
9
|
+
<ReportCard title="Report 2" onPress={() => onSelect(2)} />
|
|
10
|
+
<ReportCard title="Report 3" onPress={() => onSelect(3)} />
|
|
22
11
|
</View>
|
|
23
12
|
);
|
|
24
13
|
};
|
|
25
14
|
|
|
26
|
-
const styles = StyleSheet.create({
|
|
27
|
-
container: { padding: 16 },
|
|
28
|
-
card: {
|
|
29
|
-
backgroundColor: '#fff',
|
|
30
|
-
padding: 20,
|
|
31
|
-
borderRadius: 12,
|
|
32
|
-
marginBottom: 12,
|
|
33
|
-
elevation: 2,
|
|
34
|
-
},
|
|
35
|
-
text: { fontSize: 16, fontWeight: '700' },
|
|
36
|
-
});
|
|
37
|
-
|
|
38
15
|
export default ReportListScreen;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
const BarChart = ({ data }) => {
|
|
5
|
-
if (!data?.series) return null;
|
|
6
|
-
|
|
7
|
-
const max = Math.max(...data.series.flatMap(s => s.data));
|
|
8
|
-
|
|
9
|
-
const colors = ['#FB8C00', '#1E88E5', '#9E9E9E'];
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<View style={styles.container}>
|
|
13
|
-
<Text style={styles.title}>{data.title}</Text>
|
|
14
|
-
<ScrollView horizontal>
|
|
15
|
-
<View style={styles.chart}>
|
|
16
|
-
{data.labels.map((label, i) => (
|
|
17
|
-
<View key={i} style={styles.group}>
|
|
18
|
-
{data.series.map((s, idx) => (
|
|
19
|
-
<View
|
|
20
|
-
key={idx}
|
|
21
|
-
style={[
|
|
22
|
-
styles.bar,
|
|
23
|
-
{
|
|
24
|
-
height: (s.data[i] / max) * 120,
|
|
25
|
-
backgroundColor: colors[idx]
|
|
26
|
-
}
|
|
27
|
-
]}
|
|
28
|
-
/>
|
|
29
|
-
))}
|
|
30
|
-
<Text style={styles.label}>{label}</Text>
|
|
31
|
-
</View>
|
|
32
|
-
))}
|
|
33
|
-
</View>
|
|
34
|
-
</ScrollView>
|
|
35
|
-
</View>
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export default BarChart;
|
|
40
|
-
|
|
41
|
-
const styles = StyleSheet.create({
|
|
42
|
-
container: { marginVertical: 12 },
|
|
43
|
-
title: { fontWeight: '700', marginBottom: 8 },
|
|
44
|
-
chart: { flexDirection: 'row', alignItems: 'flex-end' },
|
|
45
|
-
group: { flexDirection: 'row', alignItems: 'flex-end', marginHorizontal: 6 },
|
|
46
|
-
bar: { width: 10, marginHorizontal: 2, borderRadius: 2 },
|
|
47
|
-
label: { fontSize: 10, marginTop: 4, textAlign: 'center', width: 40 }
|
|
48
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
const BarChartReport3 = ({ data }) => {
|
|
5
|
-
return (
|
|
6
|
-
<View style={styles.card}>
|
|
7
|
-
<Text style={styles.title}>{data.title}</Text>
|
|
8
|
-
{data.series.map(s => (
|
|
9
|
-
<Text key={s.name} style={styles.legend}>{s.name}</Text>
|
|
10
|
-
))}
|
|
11
|
-
</View>
|
|
12
|
-
);
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const styles = StyleSheet.create({
|
|
16
|
-
card: { padding: 12, backgroundColor: '#fff', borderRadius: 12, marginTop: 12 },
|
|
17
|
-
title: { fontWeight: '700', textAlign: 'center' },
|
|
18
|
-
legend: { textAlign: 'center' },
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
export default BarChartReport3;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
const FreezeTableReport3 = ({ rows }) => {
|
|
5
|
-
return (
|
|
6
|
-
<View style={styles.container}>
|
|
7
|
-
{/* Frozen column */}
|
|
8
|
-
<View style={styles.left}>
|
|
9
|
-
<Text style={styles.header}>AY</Text>
|
|
10
|
-
{rows.map(r => (
|
|
11
|
-
<Text key={r.sortOrder} style={styles.cell}>{r.monthLabel}</Text>
|
|
12
|
-
))}
|
|
13
|
-
</View>
|
|
14
|
-
|
|
15
|
-
{/* Scrollable */}
|
|
16
|
-
<ScrollView horizontal>
|
|
17
|
-
<View>
|
|
18
|
-
<View style={styles.row}>
|
|
19
|
-
{[
|
|
20
|
-
'2024 Yük',
|
|
21
|
-
'2025 Yük',
|
|
22
|
-
'Yük %',
|
|
23
|
-
'2024 Gelir',
|
|
24
|
-
'2025 Gelir',
|
|
25
|
-
'Gelir %',
|
|
26
|
-
'2025 Bütçe',
|
|
27
|
-
'Bütçe %'
|
|
28
|
-
].map(h => (
|
|
29
|
-
<Text key={h} style={styles.header}>{h}</Text>
|
|
30
|
-
))}
|
|
31
|
-
</View>
|
|
32
|
-
|
|
33
|
-
{rows.map(r => (
|
|
34
|
-
<View key={r.sortOrder} style={styles.row}>
|
|
35
|
-
<Text style={styles.cell}>{r.loadCount2024}</Text>
|
|
36
|
-
<Text style={styles.cell}>{r.loadCount2025}</Text>
|
|
37
|
-
<Text style={styles.percent}>{r.loadCountChangePercent}%</Text>
|
|
38
|
-
<Text style={styles.cell}>{r.revenueTl2024}</Text>
|
|
39
|
-
<Text style={styles.cell}>{r.revenueTl2025}</Text>
|
|
40
|
-
<Text style={styles.percent}>{r.revenueChangePercent}%</Text>
|
|
41
|
-
<Text style={styles.cell}>{r.budgetRevenueTl2025}</Text>
|
|
42
|
-
<Text style={styles.percent}>{r.budgetChangePercent}%</Text>
|
|
43
|
-
</View>
|
|
44
|
-
))}
|
|
45
|
-
</View>
|
|
46
|
-
</ScrollView>
|
|
47
|
-
</View>
|
|
48
|
-
);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const styles = StyleSheet.create({
|
|
52
|
-
container: { flexDirection: 'row' },
|
|
53
|
-
left: { width: 90, backgroundColor: '#fff' },
|
|
54
|
-
header: { fontWeight: '700', padding: 8 },
|
|
55
|
-
cell: { padding: 8 },
|
|
56
|
-
percent: { padding: 8, fontWeight: '600' },
|
|
57
|
-
row: { flexDirection: 'row' },
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
export default FreezeTableReport3;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
const Cell = ({ children, bold }) => (
|
|
5
|
-
<View style={styles.cell}>
|
|
6
|
-
<Text style={[styles.text, bold && styles.bold]}>{children}</Text>
|
|
7
|
-
</View>
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
const FrozenTable = ({ rows = [] }) => {
|
|
11
|
-
if (!rows.length) {
|
|
12
|
-
return <Text style={styles.noData}>No data available</Text>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<View style={styles.container}>
|
|
17
|
-
{/* Frozen Column */}
|
|
18
|
-
<View style={styles.frozen}>
|
|
19
|
-
<Cell bold>AY</Cell>
|
|
20
|
-
{rows.map((r, i) => (
|
|
21
|
-
<Cell key={i} bold={r.monthLabel === 'Total'}>
|
|
22
|
-
{r.monthLabel}
|
|
23
|
-
</Cell>
|
|
24
|
-
))}
|
|
25
|
-
</View>
|
|
26
|
-
|
|
27
|
-
{/* Scrollable Columns */}
|
|
28
|
-
<ScrollView horizontal>
|
|
29
|
-
<View>
|
|
30
|
-
<View style={styles.headerRow}>
|
|
31
|
-
<Cell bold>2024 TEU</Cell>
|
|
32
|
-
<Cell bold>2025 TEU</Cell>
|
|
33
|
-
<Cell bold>TEU %</Cell>
|
|
34
|
-
</View>
|
|
35
|
-
|
|
36
|
-
{rows.map((r, i) => (
|
|
37
|
-
<View key={i} style={styles.row}>
|
|
38
|
-
<Cell>{r.teu2024}</Cell>
|
|
39
|
-
<Cell>{r.teu2025}</Cell>
|
|
40
|
-
<Cell style={r.teuChangePercent < 0 ? styles.down : styles.up}>
|
|
41
|
-
{r.teuChangePercent}%
|
|
42
|
-
</Cell>
|
|
43
|
-
</View>
|
|
44
|
-
))}
|
|
45
|
-
</View>
|
|
46
|
-
</ScrollView>
|
|
47
|
-
</View>
|
|
48
|
-
);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export default FrozenTable;
|
|
52
|
-
|
|
53
|
-
const styles = StyleSheet.create({
|
|
54
|
-
container: { flexDirection: 'row', marginVertical: 12 },
|
|
55
|
-
frozen: { width: 90, backgroundColor: '#f4f4f4' },
|
|
56
|
-
headerRow: { flexDirection: 'row', backgroundColor: '#f4f4f4' },
|
|
57
|
-
row: { flexDirection: 'row' },
|
|
58
|
-
cell: {
|
|
59
|
-
width: 90,
|
|
60
|
-
padding: 8,
|
|
61
|
-
borderBottomWidth: 1,
|
|
62
|
-
borderColor: '#ddd',
|
|
63
|
-
justifyContent: 'center',
|
|
64
|
-
alignItems: 'center'
|
|
65
|
-
},
|
|
66
|
-
text: { fontSize: 12 },
|
|
67
|
-
bold: { fontWeight: '700' },
|
|
68
|
-
up: { color: 'green' },
|
|
69
|
-
down: { color: 'red' },
|
|
70
|
-
noData: { textAlign: 'center', padding: 16 }
|
|
71
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
const LineChartReport3 = ({ data }) => {
|
|
5
|
-
return (
|
|
6
|
-
<View style={styles.card}>
|
|
7
|
-
<Text style={styles.title}>{data.title}</Text>
|
|
8
|
-
{/* Simple SVG-less visual representation */}
|
|
9
|
-
{data.series.map(s => (
|
|
10
|
-
<Text key={s.name} style={styles.legend}>{s.name}</Text>
|
|
11
|
-
))}
|
|
12
|
-
</View>
|
|
13
|
-
);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const styles = StyleSheet.create({
|
|
17
|
-
card: { padding: 12, backgroundColor: '#fff', borderRadius: 12, marginTop: 12 },
|
|
18
|
-
title: { fontWeight: '700', textAlign: 'center', marginBottom: 8 },
|
|
19
|
-
legend: { textAlign: 'center' },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export default LineChartReport3;
|
|
File without changes
|