@dhiraj0720/report1chart 2.2.3 → 2.2.5
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 +1 -1
- package/src/api/report1Fetcher.jsx +10 -0
- package/src/api/report2Fetcher.jsx +29 -0
- package/src/components/BarChart.jsx +48 -0
- package/src/components/DivisionSelector.jsx +34 -0
- package/src/components/FrozenTable.jsx +71 -0
- package/src/components/LineChart.jsx +46 -0
- package/src/components/MonthSelector.jsx +34 -0
- package/src/components/ProgressBar.jsx +28 -0
- package/src/components/Report1Card.jsx +103 -0
- package/src/components/ReportCard.jsx +25 -0
- package/src/index.jsx +19 -15
- package/src/screens/Report1Screen.jsx +25 -0
- package/src/screens/Report2Screen.jsx +69 -0
- package/src/screens/Report3Screen.jsx +13 -0
- package/src/screens/ReportsHome.jsx +17 -0
- package/src/api/fetcher.jsx +0 -70
- package/src/components/ChartRenderer.jsx +0 -388
- package/src/components/ReportList.jsx +0 -135
- package/src/components/ReportView.jsx +0 -93
- package/src/components/TableRenderer.jsx +0 -647
- package/src/screens/ReportScreen.jsx +0 -417
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
export const fetchDivisions = async (endpoint, token) => {
|
|
4
|
+
const res = await axios.get(endpoint, {
|
|
5
|
+
headers: { Authorization: token },
|
|
6
|
+
});
|
|
7
|
+
return res.data.data || [];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const fetchTable = async (endpoint, division, token) => {
|
|
11
|
+
const res = await axios.get(`${endpoint}?division=${division}`, {
|
|
12
|
+
headers: { Authorization: token },
|
|
13
|
+
});
|
|
14
|
+
return res.data;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const fetchLineChart = async (endpoint, division, token) => {
|
|
18
|
+
const res = await axios.get(`${endpoint}?division=${division}`, {
|
|
19
|
+
headers: { Authorization: token },
|
|
20
|
+
});
|
|
21
|
+
return res.data;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const fetchBarChart = async (endpoint, division, token) => {
|
|
25
|
+
const res = await axios.get(`${endpoint}?division=${division}`, {
|
|
26
|
+
headers: { Authorization: token },
|
|
27
|
+
});
|
|
28
|
+
return res.data;
|
|
29
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const DivisionSelector = ({ divisions, selected, onSelect }) => (
|
|
5
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
6
|
+
{divisions.map(d => (
|
|
7
|
+
<TouchableOpacity
|
|
8
|
+
key={d.code}
|
|
9
|
+
style={[styles.card, selected === d.code && styles.active]}
|
|
10
|
+
onPress={() => onSelect(d.code)}
|
|
11
|
+
>
|
|
12
|
+
<Text style={[styles.text, selected === d.code && styles.activeText]}>
|
|
13
|
+
{d.displayName}
|
|
14
|
+
</Text>
|
|
15
|
+
</TouchableOpacity>
|
|
16
|
+
))}
|
|
17
|
+
</ScrollView>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export default DivisionSelector;
|
|
21
|
+
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
card: {
|
|
24
|
+
paddingHorizontal: 14,
|
|
25
|
+
paddingVertical: 8,
|
|
26
|
+
borderRadius: 16,
|
|
27
|
+
borderWidth: 1,
|
|
28
|
+
borderColor: '#ccc',
|
|
29
|
+
marginRight: 8,
|
|
30
|
+
},
|
|
31
|
+
active: { backgroundColor: '#000' },
|
|
32
|
+
text: { fontSize: 12 },
|
|
33
|
+
activeText: { color: '#fff', fontWeight: '700' },
|
|
34
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const LineChart = ({ data }) => {
|
|
5
|
+
if (!data?.series) 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
|
+
<ScrollView horizontal>
|
|
13
|
+
<View style={styles.chart}>
|
|
14
|
+
{data.labels.map((label, i) => (
|
|
15
|
+
<View key={i} style={styles.column}>
|
|
16
|
+
{data.series.map((s, idx) => (
|
|
17
|
+
<View
|
|
18
|
+
key={idx}
|
|
19
|
+
style={[
|
|
20
|
+
styles.bar,
|
|
21
|
+
{
|
|
22
|
+
height: (s.data[i] / max) * 120,
|
|
23
|
+
backgroundColor: idx === 0 ? '#2962FF' : '#9E9E9E'
|
|
24
|
+
}
|
|
25
|
+
]}
|
|
26
|
+
/>
|
|
27
|
+
))}
|
|
28
|
+
<Text style={styles.label}>{label}</Text>
|
|
29
|
+
</View>
|
|
30
|
+
))}
|
|
31
|
+
</View>
|
|
32
|
+
</ScrollView>
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default LineChart;
|
|
38
|
+
|
|
39
|
+
const styles = StyleSheet.create({
|
|
40
|
+
container: { marginVertical: 12 },
|
|
41
|
+
title: { fontWeight: '700', marginBottom: 8 },
|
|
42
|
+
chart: { flexDirection: 'row', alignItems: 'flex-end' },
|
|
43
|
+
column: { alignItems: 'center', marginHorizontal: 6 },
|
|
44
|
+
bar: { width: 8, marginHorizontal: 1, borderRadius: 2 },
|
|
45
|
+
label: { fontSize: 10, marginTop: 4 }
|
|
46
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const MonthSelector = ({ months, selected, onSelect }) => (
|
|
5
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
6
|
+
{months.map(m => (
|
|
7
|
+
<TouchableOpacity
|
|
8
|
+
key={m}
|
|
9
|
+
style={[styles.card, selected === m && styles.active]}
|
|
10
|
+
onPress={() => onSelect(m)}
|
|
11
|
+
>
|
|
12
|
+
<Text style={[styles.text, selected === m && styles.activeText]}>
|
|
13
|
+
{m}
|
|
14
|
+
</Text>
|
|
15
|
+
</TouchableOpacity>
|
|
16
|
+
))}
|
|
17
|
+
</ScrollView>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export default MonthSelector;
|
|
21
|
+
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
card: {
|
|
24
|
+
paddingHorizontal: 14,
|
|
25
|
+
paddingVertical: 8,
|
|
26
|
+
borderRadius: 16,
|
|
27
|
+
borderWidth: 1,
|
|
28
|
+
borderColor: '#ccc',
|
|
29
|
+
marginRight: 8,
|
|
30
|
+
},
|
|
31
|
+
active: { backgroundColor: '#000' },
|
|
32
|
+
text: { fontSize: 13 },
|
|
33
|
+
activeText: { color: '#fff', fontWeight: '700' },
|
|
34
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const ProgressBar = ({ value }) => {
|
|
5
|
+
const width = Math.min(Math.max(value || 0, 0), 100);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<View style={styles.track}>
|
|
9
|
+
<View style={[styles.fill, { width: `${width}%` }]} />
|
|
10
|
+
</View>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default ProgressBar;
|
|
15
|
+
|
|
16
|
+
const styles = StyleSheet.create({
|
|
17
|
+
track: {
|
|
18
|
+
height: 6,
|
|
19
|
+
backgroundColor: '#e5e5e5',
|
|
20
|
+
borderRadius: 4,
|
|
21
|
+
overflow: 'hidden',
|
|
22
|
+
marginTop: 6,
|
|
23
|
+
},
|
|
24
|
+
fill: {
|
|
25
|
+
height: '100%',
|
|
26
|
+
backgroundColor: '#f57c00', // orange like original
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import ProgressBar from './ProgressBar';
|
|
4
|
+
|
|
5
|
+
const format = (num) => (num ?? 0).toLocaleString();
|
|
6
|
+
|
|
7
|
+
const Trend = ({ value }) => {
|
|
8
|
+
const positive = value >= 0;
|
|
9
|
+
return (
|
|
10
|
+
<Text style={[styles.trend, positive ? styles.up : styles.down]}>
|
|
11
|
+
{positive ? '↑' : '↓'} {Math.abs(value)}%
|
|
12
|
+
</Text>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const Row = ({ label, value }) => (
|
|
17
|
+
<View style={styles.row}>
|
|
18
|
+
<Text style={styles.label}>{label}</Text>
|
|
19
|
+
<Text style={styles.value}>{value}</Text>
|
|
20
|
+
</View>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const Report1Card = ({ item }) => {
|
|
24
|
+
return (
|
|
25
|
+
<View style={styles.card}>
|
|
26
|
+
{/* TITLE */}
|
|
27
|
+
<Text style={styles.title}>{item.name}</Text>
|
|
28
|
+
|
|
29
|
+
{/* VALUES */}
|
|
30
|
+
<Row label="2024 Actual" value={format(item.actual2024)} />
|
|
31
|
+
<Row label="2025 Actual" value={format(item.actual2025)} />
|
|
32
|
+
|
|
33
|
+
<View style={styles.row}>
|
|
34
|
+
<Text style={styles.label}>Change YoY</Text>
|
|
35
|
+
<Trend value={item.actualChangePercent} />
|
|
36
|
+
</View>
|
|
37
|
+
|
|
38
|
+
<Row label="2025 Budget" value={format(item.budget2025)} />
|
|
39
|
+
|
|
40
|
+
<View style={styles.row}>
|
|
41
|
+
<Text style={styles.label}>Budget Variance</Text>
|
|
42
|
+
<Trend value={item.budgetVariancePercent} />
|
|
43
|
+
</View>
|
|
44
|
+
|
|
45
|
+
{/* OPEX / GROSS PROFIT */}
|
|
46
|
+
{item.opexToGrossProfitPercent !== undefined && (
|
|
47
|
+
<>
|
|
48
|
+
<View style={styles.row}>
|
|
49
|
+
<Text style={styles.label}>OPEX / Gross Profit</Text>
|
|
50
|
+
<Text style={styles.value}>
|
|
51
|
+
{item.opexToGrossProfitPercent}%
|
|
52
|
+
</Text>
|
|
53
|
+
</View>
|
|
54
|
+
|
|
55
|
+
<ProgressBar value={item.opexToGrossProfitPercent} />
|
|
56
|
+
</>
|
|
57
|
+
)}
|
|
58
|
+
</View>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default Report1Card;
|
|
63
|
+
|
|
64
|
+
const styles = StyleSheet.create({
|
|
65
|
+
card: {
|
|
66
|
+
backgroundColor: '#fff',
|
|
67
|
+
borderRadius: 12,
|
|
68
|
+
padding: 16,
|
|
69
|
+
marginBottom: 12,
|
|
70
|
+
borderWidth: 1,
|
|
71
|
+
borderColor: '#e0e0e0',
|
|
72
|
+
},
|
|
73
|
+
title: {
|
|
74
|
+
fontSize: 16,
|
|
75
|
+
fontWeight: '700',
|
|
76
|
+
marginBottom: 12,
|
|
77
|
+
color: '#000',
|
|
78
|
+
},
|
|
79
|
+
row: {
|
|
80
|
+
flexDirection: 'row',
|
|
81
|
+
justifyContent: 'space-between',
|
|
82
|
+
marginBottom: 6,
|
|
83
|
+
},
|
|
84
|
+
label: {
|
|
85
|
+
fontSize: 13,
|
|
86
|
+
color: '#666',
|
|
87
|
+
},
|
|
88
|
+
value: {
|
|
89
|
+
fontSize: 13,
|
|
90
|
+
fontWeight: '600',
|
|
91
|
+
color: '#000',
|
|
92
|
+
},
|
|
93
|
+
trend: {
|
|
94
|
+
fontSize: 13,
|
|
95
|
+
fontWeight: '700',
|
|
96
|
+
},
|
|
97
|
+
up: {
|
|
98
|
+
color: '#2e7d32',
|
|
99
|
+
},
|
|
100
|
+
down: {
|
|
101
|
+
color: '#d32f2f',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const ReportCard = ({ title, onPress }) => (
|
|
5
|
+
<TouchableOpacity style={styles.card} onPress={onPress}>
|
|
6
|
+
<Text style={styles.text}>{title}</Text>
|
|
7
|
+
</TouchableOpacity>
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export default ReportCard;
|
|
11
|
+
|
|
12
|
+
const styles = StyleSheet.create({
|
|
13
|
+
card: {
|
|
14
|
+
padding: 18,
|
|
15
|
+
borderRadius: 12,
|
|
16
|
+
backgroundColor: '#fff',
|
|
17
|
+
marginBottom: 12,
|
|
18
|
+
borderWidth: 1,
|
|
19
|
+
borderColor: '#ddd',
|
|
20
|
+
},
|
|
21
|
+
text: {
|
|
22
|
+
fontSize: 16,
|
|
23
|
+
fontWeight: '700',
|
|
24
|
+
},
|
|
25
|
+
});
|
package/src/index.jsx
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import ReportsHome from './screens/ReportsHome';
|
|
3
|
+
import Report1Screen from './screens/Report1Screen';
|
|
4
|
+
import Report2Screen from './screens/Report2Screen';
|
|
5
|
+
import Report3Screen from './screens/Report3Screen';
|
|
3
6
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const AnalyticsReports = (props) => {
|
|
8
|
+
const [active, setActive] = useState(null);
|
|
9
|
+
|
|
10
|
+
if (active === 1)
|
|
11
|
+
return <Report1Screen {...props} onBack={() => setActive(null)} />;
|
|
12
|
+
|
|
13
|
+
if (active === 2)
|
|
14
|
+
return <Report2Screen {...props} onBack={() => setActive(null)} />;
|
|
15
|
+
|
|
16
|
+
if (active === 3)
|
|
17
|
+
return <Report3Screen onBack={() => setActive(null)} />;
|
|
18
|
+
|
|
19
|
+
return <ReportsHome onSelect={setActive} />;
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
export default
|
|
22
|
+
export default AnalyticsReports;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { ScrollView, Text, ActivityIndicator } from 'react-native';
|
|
3
|
+
import fetchReport1 from '../api/report1Fetcher';
|
|
4
|
+
import Report1Card from '../components/Report1Card';
|
|
5
|
+
|
|
6
|
+
const Report1Screen = ({ report1Endpoint, token, onBack }) => {
|
|
7
|
+
const [rows, setRows] = useState(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
fetchReport1(report1Endpoint, token).then(setRows);
|
|
11
|
+
}, []);
|
|
12
|
+
|
|
13
|
+
if (!rows) return <ActivityIndicator />;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<ScrollView>
|
|
17
|
+
<Text onPress={onBack}>‹ Back</Text>
|
|
18
|
+
{rows.map((r, i) => (
|
|
19
|
+
<Report1Card key={i} item={r} />
|
|
20
|
+
))}
|
|
21
|
+
</ScrollView>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default Report1Screen;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { ScrollView, Text, ActivityIndicator } from 'react-native';
|
|
3
|
+
import { getDivisions, getTable, getLine, getBar } from '../api/report2Fetcher';
|
|
4
|
+
import MonthSelector from '../components/MonthSelector';
|
|
5
|
+
import DivisionSelector from '../components/DivisionSelector';
|
|
6
|
+
import FrozenTable from '../components/FrozenTable';
|
|
7
|
+
import LineChart from '../components/LineChart';
|
|
8
|
+
import BarChart from '../components/BarChart';
|
|
9
|
+
|
|
10
|
+
const Report2Screen = ({ report2, token, onBack }) => {
|
|
11
|
+
const [divisions, setDivisions] = useState([]);
|
|
12
|
+
const [division, setDivision] = useState(null);
|
|
13
|
+
const [table, setTable] = useState(null);
|
|
14
|
+
const [line, setLine] = useState(null);
|
|
15
|
+
const [bar, setBar] = useState(null);
|
|
16
|
+
const [month, setMonth] = useState(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
getDivisions(report2.divisions, token).then(d => {
|
|
20
|
+
setDivisions(d);
|
|
21
|
+
setDivision(d[0]?.code);
|
|
22
|
+
});
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!division) return;
|
|
27
|
+
Promise.all([
|
|
28
|
+
getTable(report2.table, division, token),
|
|
29
|
+
getLine(report2.lineChart, division, token),
|
|
30
|
+
getBar(report2.barChart, division, token)
|
|
31
|
+
]).then(([t, l, b]) => {
|
|
32
|
+
setTable(t);
|
|
33
|
+
setLine(l);
|
|
34
|
+
setBar(b);
|
|
35
|
+
setMonth(null);
|
|
36
|
+
});
|
|
37
|
+
}, [division]);
|
|
38
|
+
|
|
39
|
+
if (!table || !line || !bar) return <ActivityIndicator />;
|
|
40
|
+
|
|
41
|
+
const rows = month
|
|
42
|
+
? table.rows.filter(r => r.monthLabel === month)
|
|
43
|
+
: table.rows;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<ScrollView>
|
|
47
|
+
<Text onPress={onBack}>‹ Back</Text>
|
|
48
|
+
|
|
49
|
+
<MonthSelector
|
|
50
|
+
months={line.labels}
|
|
51
|
+
selected={month}
|
|
52
|
+
onSelect={setMonth}
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<FrozenTable rows={rows} />
|
|
56
|
+
|
|
57
|
+
<LineChart data={line} />
|
|
58
|
+
<BarChart data={bar} />
|
|
59
|
+
|
|
60
|
+
<DivisionSelector
|
|
61
|
+
divisions={divisions}
|
|
62
|
+
selected={division}
|
|
63
|
+
onSelect={setDivision}
|
|
64
|
+
/>
|
|
65
|
+
</ScrollView>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default Report2Screen;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const Report3Screen = ({ onBack }) => (
|
|
5
|
+
<View style={{ padding: 16 }}>
|
|
6
|
+
<Text onPress={onBack}>‹ Back</Text>
|
|
7
|
+
<Text style={{ marginTop: 20, fontSize: 16 }}>
|
|
8
|
+
Report 3 – Coming Soon
|
|
9
|
+
</Text>
|
|
10
|
+
</View>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export default Report3Screen;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import ReportCard from '../components/ReportCard';
|
|
4
|
+
|
|
5
|
+
const ReportsHome = ({ onSelect }) => (
|
|
6
|
+
<View style={styles.container}>
|
|
7
|
+
<ReportCard title="Report 1 – Operating Profit" onPress={() => onSelect(1)} />
|
|
8
|
+
<ReportCard title="Report 2 – TEU & Profit" onPress={() => onSelect(2)} />
|
|
9
|
+
<ReportCard title="Report 3 – Coming Soon" onPress={() => onSelect(3)} />
|
|
10
|
+
</View>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export default ReportsHome;
|
|
14
|
+
|
|
15
|
+
const styles = StyleSheet.create({
|
|
16
|
+
container: { padding: 16 },
|
|
17
|
+
});
|
package/src/api/fetcher.jsx
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
|
|
3
|
-
export class ApiFetcher {
|
|
4
|
-
static async fetchReportData(reportConfig) {
|
|
5
|
-
try {
|
|
6
|
-
console.log('Fetching report data from:', reportConfig.apiConfig.url);
|
|
7
|
-
|
|
8
|
-
const headers = {
|
|
9
|
-
'Content-Type': 'application/json',
|
|
10
|
-
'Accept': 'application/json'
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// Add token if provided
|
|
14
|
-
if (reportConfig.apiConfig.token) {
|
|
15
|
-
headers['Authorization'] = `Bearer ${reportConfig.apiConfig.token}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const response = await axios.get(reportConfig.apiConfig.url, {
|
|
19
|
-
timeout: 15000,
|
|
20
|
-
headers: headers
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
console.log('API Response received:', response.status);
|
|
24
|
-
|
|
25
|
-
// Transform data based on report type
|
|
26
|
-
return this.transformData(response.data, reportConfig);
|
|
27
|
-
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error('API Fetch Error:', error);
|
|
30
|
-
throw new Error(`Failed to fetch report: ${error.message}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static transformData(data, reportConfig) {
|
|
35
|
-
// For performance reports (type 1, 1.1, 1.2)
|
|
36
|
-
if (reportConfig.id === 1 || reportConfig.id === 1.1 || reportConfig.id === 1.2) {
|
|
37
|
-
return {
|
|
38
|
-
data: data.data || [],
|
|
39
|
-
title: reportConfig.title,
|
|
40
|
-
type: 'table',
|
|
41
|
-
subType: reportConfig.subType
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// For report 2 (Revenue by Mode)
|
|
46
|
-
if (reportConfig.id === 2) {
|
|
47
|
-
return {
|
|
48
|
-
data: Array.isArray(data) ? data : data.data || [],
|
|
49
|
-
title: reportConfig.title,
|
|
50
|
-
type: 'bar'
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// For report 3 (Shipment by Direction)
|
|
55
|
-
if (reportConfig.id === 3) {
|
|
56
|
-
return {
|
|
57
|
-
data: Array.isArray(data) ? data : data.data || [],
|
|
58
|
-
title: reportConfig.title,
|
|
59
|
-
type: 'pie'
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Default transformation
|
|
64
|
-
return {
|
|
65
|
-
data: data,
|
|
66
|
-
title: reportConfig.title,
|
|
67
|
-
type: reportConfig.type || 'bar'
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|