@dhiraj0720/report1chart 2.2.4 → 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 +5 -22
- 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 +3 -2
- package/src/components/Report1Card.jsx +11 -7
- package/src/components/ReportCard.jsx +25 -0
- package/src/index.jsx +20 -1
- package/src/screens/Report1Screen.jsx +10 -65
- package/src/screens/Report2Screen.jsx +69 -0
- package/src/screens/Report3Screen.jsx +13 -0
- package/src/screens/ReportsHome.jsx +17 -0
package/package.json
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
|
|
3
|
-
const fetchReport1 = async (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Accept: 'application/json',
|
|
9
|
-
},
|
|
10
|
-
timeout: 15000,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
if (!response.data || !response.data.rows) {
|
|
14
|
-
throw new Error('Invalid API response');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return response.data.rows;
|
|
18
|
-
} catch (error) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
error?.response?.data?.message ||
|
|
21
|
-
error.message ||
|
|
22
|
-
'Failed to fetch report'
|
|
23
|
-
);
|
|
24
|
-
}
|
|
3
|
+
const fetchReport1 = async (endpoint, token) => {
|
|
4
|
+
const res = await axios.get(endpoint, {
|
|
5
|
+
headers: { Authorization: token },
|
|
6
|
+
});
|
|
7
|
+
return res.data.rows || [];
|
|
25
8
|
};
|
|
26
9
|
|
|
27
10
|
export default fetchReport1;
|
|
@@ -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
|
+
});
|
|
@@ -16,12 +16,13 @@ export default ProgressBar;
|
|
|
16
16
|
const styles = StyleSheet.create({
|
|
17
17
|
track: {
|
|
18
18
|
height: 6,
|
|
19
|
-
backgroundColor: '#
|
|
19
|
+
backgroundColor: '#e5e5e5',
|
|
20
20
|
borderRadius: 4,
|
|
21
21
|
overflow: 'hidden',
|
|
22
|
+
marginTop: 6,
|
|
22
23
|
},
|
|
23
24
|
fill: {
|
|
24
25
|
height: '100%',
|
|
25
|
-
backgroundColor: '#f57c00',
|
|
26
|
+
backgroundColor: '#f57c00', // orange like original
|
|
26
27
|
},
|
|
27
28
|
});
|
|
@@ -13,11 +13,20 @@ const Trend = ({ value }) => {
|
|
|
13
13
|
);
|
|
14
14
|
};
|
|
15
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
|
+
|
|
16
23
|
const Report1Card = ({ item }) => {
|
|
17
24
|
return (
|
|
18
25
|
<View style={styles.card}>
|
|
26
|
+
{/* TITLE */}
|
|
19
27
|
<Text style={styles.title}>{item.name}</Text>
|
|
20
28
|
|
|
29
|
+
{/* VALUES */}
|
|
21
30
|
<Row label="2024 Actual" value={format(item.actual2024)} />
|
|
22
31
|
<Row label="2025 Actual" value={format(item.actual2025)} />
|
|
23
32
|
|
|
@@ -33,6 +42,7 @@ const Report1Card = ({ item }) => {
|
|
|
33
42
|
<Trend value={item.budgetVariancePercent} />
|
|
34
43
|
</View>
|
|
35
44
|
|
|
45
|
+
{/* OPEX / GROSS PROFIT */}
|
|
36
46
|
{item.opexToGrossProfitPercent !== undefined && (
|
|
37
47
|
<>
|
|
38
48
|
<View style={styles.row}>
|
|
@@ -41,6 +51,7 @@ const Report1Card = ({ item }) => {
|
|
|
41
51
|
{item.opexToGrossProfitPercent}%
|
|
42
52
|
</Text>
|
|
43
53
|
</View>
|
|
54
|
+
|
|
44
55
|
<ProgressBar value={item.opexToGrossProfitPercent} />
|
|
45
56
|
</>
|
|
46
57
|
)}
|
|
@@ -48,13 +59,6 @@ const Report1Card = ({ item }) => {
|
|
|
48
59
|
);
|
|
49
60
|
};
|
|
50
61
|
|
|
51
|
-
const Row = ({ label, value }) => (
|
|
52
|
-
<View style={styles.row}>
|
|
53
|
-
<Text style={styles.label}>{label}</Text>
|
|
54
|
-
<Text style={styles.value}>{value}</Text>
|
|
55
|
-
</View>
|
|
56
|
-
);
|
|
57
|
-
|
|
58
62
|
export default Report1Card;
|
|
59
63
|
|
|
60
64
|
const styles = StyleSheet.create({
|
|
@@ -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,3 +1,22 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import ReportsHome from './screens/ReportsHome';
|
|
1
3
|
import Report1Screen from './screens/Report1Screen';
|
|
4
|
+
import Report2Screen from './screens/Report2Screen';
|
|
5
|
+
import Report3Screen from './screens/Report3Screen';
|
|
2
6
|
|
|
3
|
-
|
|
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} />;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default AnalyticsReports;
|
|
@@ -1,80 +1,25 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
Text,
|
|
5
|
-
ScrollView,
|
|
6
|
-
ActivityIndicator,
|
|
7
|
-
StyleSheet,
|
|
8
|
-
} from 'react-native';
|
|
2
|
+
import { ScrollView, Text, ActivityIndicator } from 'react-native';
|
|
9
3
|
import fetchReport1 from '../api/report1Fetcher';
|
|
10
4
|
import Report1Card from '../components/Report1Card';
|
|
11
5
|
|
|
12
|
-
const Report1Screen = ({
|
|
13
|
-
const [
|
|
14
|
-
const [error, setError] = useState(null);
|
|
15
|
-
const [rows, setRows] = useState([]);
|
|
6
|
+
const Report1Screen = ({ report1Endpoint, token, onBack }) => {
|
|
7
|
+
const [rows, setRows] = useState(null);
|
|
16
8
|
|
|
17
9
|
useEffect(() => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const data = await fetchReport1(apiEndpoint, apiToken);
|
|
21
|
-
setRows(data);
|
|
22
|
-
} catch (e) {
|
|
23
|
-
setError(e.message);
|
|
24
|
-
} finally {
|
|
25
|
-
setLoading(false);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
10
|
+
fetchReport1(report1Endpoint, token).then(setRows);
|
|
11
|
+
}, []);
|
|
28
12
|
|
|
29
|
-
|
|
30
|
-
}, [apiEndpoint, apiToken]);
|
|
31
|
-
|
|
32
|
-
if (loading) {
|
|
33
|
-
return (
|
|
34
|
-
<View style={styles.center}>
|
|
35
|
-
<ActivityIndicator size="large" />
|
|
36
|
-
<Text style={styles.info}>Loading report...</Text>
|
|
37
|
-
</View>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (error) {
|
|
42
|
-
return (
|
|
43
|
-
<View style={styles.center}>
|
|
44
|
-
<Text style={styles.error}>{error}</Text>
|
|
45
|
-
</View>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
13
|
+
if (!rows) return <ActivityIndicator />;
|
|
48
14
|
|
|
49
15
|
return (
|
|
50
|
-
<ScrollView
|
|
51
|
-
{
|
|
52
|
-
|
|
16
|
+
<ScrollView>
|
|
17
|
+
<Text onPress={onBack}>‹ Back</Text>
|
|
18
|
+
{rows.map((r, i) => (
|
|
19
|
+
<Report1Card key={i} item={r} />
|
|
53
20
|
))}
|
|
54
21
|
</ScrollView>
|
|
55
22
|
);
|
|
56
23
|
};
|
|
57
24
|
|
|
58
25
|
export default Report1Screen;
|
|
59
|
-
|
|
60
|
-
const styles = StyleSheet.create({
|
|
61
|
-
container: {
|
|
62
|
-
backgroundColor: '#f5f5f5',
|
|
63
|
-
padding: 16,
|
|
64
|
-
},
|
|
65
|
-
center: {
|
|
66
|
-
flex: 1,
|
|
67
|
-
justifyContent: 'center',
|
|
68
|
-
alignItems: 'center',
|
|
69
|
-
padding: 20,
|
|
70
|
-
},
|
|
71
|
-
info: {
|
|
72
|
-
marginTop: 12,
|
|
73
|
-
color: '#666',
|
|
74
|
-
},
|
|
75
|
-
error: {
|
|
76
|
-
fontSize: 16,
|
|
77
|
-
color: '#d32f2f',
|
|
78
|
-
textAlign: 'center',
|
|
79
|
-
},
|
|
80
|
-
});
|
|
@@ -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
|
+
});
|