@dhiraj0720/report1chart 2.2.8 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhiraj0720/report1chart",
3
- "version": "2.2.8",
3
+ "version": "2.3.0",
4
4
  "main": "src/index.jsx",
5
5
  "scripts": {
6
6
  "test": "echo 'No tests'"
@@ -5,6 +5,16 @@ const Cell = ({ children, bold }) => (
5
5
  <Text style={[styles.cell, bold && styles.bold]}>{children}</Text>
6
6
  );
7
7
 
8
+ const PercentCell = ({ value }) => {
9
+ const positive = value >= 0;
10
+ return (
11
+ <Text style={{ color: positive ? '#2e7d32' : '#d32f2f', fontWeight: '700' }}>
12
+ {positive ? '↑' : '↓'} {Math.abs(value)}%
13
+ </Text>
14
+ );
15
+ };
16
+
17
+
8
18
  const FrozenTableReport2 = ({ rows }) => (
9
19
  <View style={styles.container}>
10
20
  <View style={styles.frozen}>
@@ -18,9 +28,9 @@ const FrozenTableReport2 = ({ rows }) => (
18
28
  <View>
19
29
  <View style={styles.headerRow}>
20
30
  {[
21
- '2024 TEU','2025 TEU','TEU %',
22
- '2024 Kar','2025 Kar','Kar %',
23
- '2025 Bütçe','Bütçe %'
31
+ '2024 TEU', '2025 TEU', 'TEU %',
32
+ '2024 Kar', '2025 Kar', 'Kar %',
33
+ '2025 Bütçe', 'Bütçe %'
24
34
  ].map(h => <Cell key={h} bold>{h}</Cell>)}
25
35
  </View>
26
36
 
@@ -28,12 +38,21 @@ const FrozenTableReport2 = ({ rows }) => (
28
38
  <View key={i} style={styles.row}>
29
39
  <Cell>{r.teu2024}</Cell>
30
40
  <Cell>{r.teu2025}</Cell>
31
- <Cell>{r.teuChangePercent}%</Cell>
41
+ <Cell>
42
+ <PercentCell value={r.teuChangePercent} />
43
+ </Cell>
44
+
32
45
  <Cell>{r.profitUsd2024}</Cell>
33
46
  <Cell>{r.profitUsd2025}</Cell>
34
- <Cell>{r.profitChangePercent}%</Cell>
47
+ <Cell>
48
+ <PercentCell value={r.profitChangePercent} />
49
+ </Cell>
50
+
35
51
  <Cell>{r.budgetProfitUsd2025}</Cell>
36
- <Cell>{r.budgetChangePercent}%</Cell>
52
+
53
+ <Cell>
54
+ <PercentCell value={r.budgetChangePercent} />
55
+ </Cell>
37
56
  </View>
38
57
  ))}
39
58
  </View>
@@ -5,6 +5,15 @@ const Cell = ({ children, bold }) => (
5
5
  <Text style={[styles.cell, bold && styles.bold]}>{children}</Text>
6
6
  );
7
7
 
8
+ const PercentCell = ({ value }) => {
9
+ const positive = value >= 0;
10
+ return (
11
+ <Text style={{ color: positive ? '#2e7d32' : '#d32f2f', fontWeight: '700' }}>
12
+ {positive ? '↑' : '↓'} {Math.abs(value)}%
13
+ </Text>
14
+ );
15
+ };
16
+
8
17
  const FrozenTableReport3 = ({ rows }) => (
9
18
  <View style={styles.container}>
10
19
  <View style={styles.frozen}>
@@ -28,12 +37,21 @@ const FrozenTableReport3 = ({ rows }) => (
28
37
  <View key={i} style={styles.row}>
29
38
  <Cell>{r.loadCount2024}</Cell>
30
39
  <Cell>{r.loadCount2025}</Cell>
31
- <Cell>{r.loadCountChangePercent}%</Cell>
40
+
41
+ <Cell>
42
+ <PercentCell value={r.loadCountChangePercent} />
43
+ </Cell>
32
44
  <Cell>{r.revenueTl2024}</Cell>
33
45
  <Cell>{r.revenueTl2025}</Cell>
34
- <Cell>{r.revenueChangePercent}%</Cell>
46
+
47
+ <Cell>
48
+ <PercentCell value={r.revenueChangePercent} />
49
+ </Cell>
35
50
  <Cell>{r.budgetRevenueTl2025}</Cell>
36
- <Cell>{r.budgetChangePercent}%</Cell>
51
+
52
+ <Cell>
53
+ <PercentCell value={r.budgetChangePercent} />
54
+ </Cell>
37
55
  </View>
38
56
  ))}
39
57
  </View>
@@ -1,84 +1,216 @@
1
- import React from 'react';
2
- import { View, Text } from 'react-native';
3
- import Svg, { Rect, Polyline, Circle, Text as SvgText } from 'react-native-svg';
1
+ import React, { useState } from 'react';
2
+ import { View, Text, ScrollView } from 'react-native';
3
+ import Svg, {
4
+ Rect,
5
+ Line,
6
+ Circle,
7
+ Text as SvgText,
8
+ } from 'react-native-svg';
9
+ import { formatNumber } from '../utils/formatNumber';
4
10
 
5
11
  const SvgBarLineChart = ({ data }) => {
6
- if (!data?.series?.length) return null;
12
+ if (!data?.series || !data.labels?.length) return null;
7
13
 
8
- const width = 360;
9
- const height = 190;
10
- const padding = 30;
11
- const barWidth = 12;
14
+ const [activePoint, setActivePoint] = useState(null);
12
15
 
13
- const max = Math.max(...data.series.flatMap(s => s.data));
14
16
 
15
- const scaleX = i =>
16
- padding + i * ((width - padding * 2) / data.labels.length);
17
+ const width = Math.max(360, data.labels.length * 90);
18
+ const height = 240;
19
+ const padding = 40;
20
+ const chartHeight = height - padding * 2;
21
+
22
+ const max = Math.max(...data.series.flatMap(s => s.data));
23
+ const barWidth = 14;
24
+ const step = 70;
17
25
 
18
- const scaleY = v =>
19
- height - padding - (v / max) * (height - padding * 2);
26
+ const scaleY = v => (v / max) * chartHeight;
27
+ const yPos = v => height - padding - scaleY(v);
20
28
 
21
29
  return (
22
- <View style={{ backgroundColor: '#fff', padding: 12, borderRadius: 12 }}>
23
- <Text style={{ fontWeight: '700', textAlign: 'center', marginBottom: 8 }}>
30
+ <View style={{ marginVertical: 16 }}>
31
+ <Text style={{ fontWeight: '700', marginBottom: 8 }}>
24
32
  {data.title}
25
33
  </Text>
26
34
 
27
- <Svg width={width} height={height}>
28
- {/* Bars */}
29
- {data.labels.map((_, i) =>
30
- data.series.slice(0, 2).map((s, idx) => (
31
- <Rect
32
- key={`${i}-${idx}`}
33
- x={scaleX(i) + idx * (barWidth + 4)}
34
- y={scaleY(s.data[i])}
35
- width={barWidth}
36
- height={height - padding - scaleY(s.data[i])}
37
- fill={idx === 0 ? '#FB8C00' : '#1E88E5'}
38
- rx="2"
39
- />
40
- ))
41
- )}
42
-
43
- {/* Budget dotted line */}
44
- {data.series[2] && (
45
- <>
46
- <Polyline
47
- points={data.series[2].data
48
- .map((v, i) => `${scaleX(i) + barWidth},${scaleY(v)}`)
49
- .join(' ')}
50
- fill="none"
51
- stroke="#90CAF9"
52
- strokeWidth="2"
53
- strokeDasharray="4,4"
54
- />
55
-
56
- {data.series[2].data.map((v, i) => (
57
- <Circle
58
- key={i}
59
- cx={scaleX(i) + barWidth}
60
- cy={scaleY(v)}
61
- r="4"
62
- fill="#90CAF9"
35
+ <ScrollView horizontal>
36
+ <Svg width={width} height={height} onPress={() => setActivePoint(null)}>
37
+ {/* Y AXIS GRID + LABELS */}
38
+ {[0, 0.5, 1].map((p, i) => {
39
+ const y = height - padding - chartHeight * p;
40
+ return (
41
+ <React.Fragment key={i}>
42
+ <Line
43
+ x1={padding}
44
+ x2={width - padding}
45
+ y1={y}
46
+ y2={y}
47
+ stroke="#ccc"
48
+ strokeDasharray="4"
49
+ />
50
+ <SvgText
51
+ x={padding - 8}
52
+ y={y + 4}
53
+ fontSize="10"
54
+ textAnchor="end"
55
+ >
56
+ {formatNumber(max * p)}
57
+ </SvgText>
58
+ </React.Fragment>
59
+ );
60
+ })}
61
+
62
+ {/* BARS + BUDGET LINE */}
63
+ {data.labels.map((label, i) => {
64
+ const x = padding + i * step;
65
+
66
+ return (
67
+ <React.Fragment key={i}>
68
+ {/* 2024 BAR */}
69
+ <Rect
70
+ x={x}
71
+ y={yPos(data.series[0].data[i])}
72
+ width={barWidth}
73
+ height={scaleY(dat.series[0].data[i])}
74
+ fill="#E07A3F"
75
+ onPress={() =>
76
+ setActivePoint({
77
+ x: x + barWidth / 2,
78
+ y: yPos(data.series[0].data[i]),
79
+ value: data.series[0].data[i],
80
+ })
81
+ }
82
+ />
83
+ <SvgText
84
+ x={x + barWidth / 2}
85
+ y={yPos(data.series[0].data[i]) - 6}
86
+ fontSize="10"
87
+ textAnchor="middle"
88
+ >
89
+ {formatNumber(data.series[0].data[i])}
90
+ </SvgText>
91
+
92
+ {/* 2025 BAR */}
93
+ <Rect
94
+ x={x + barWidth + 4}
95
+ y={yPos(data.series[1].data[i])}
96
+ width={barWidth}
97
+ height={scaleY(data.series[1].data[i])}
98
+ fill="#4E79A7"
99
+ onPress={() =>
100
+ setActivePoint({
101
+ x: x + barWidth + 4 + barWidth / 2,
102
+ y: yPos(data.series[1].data[i]),
103
+ value: data.series[1].data[i],
104
+ })
105
+ }
106
+ />
107
+ <SvgText
108
+ x={x + barWidth + 4 + barWidth / 2}
109
+ y={yPos(data.series[1].data[i]) - 6}
110
+ fontSize="10"
111
+ textAnchor="middle"
112
+ >
113
+ {formatNumber(data.series[1].data[i])}
114
+ </SvgText>
115
+
116
+ {/* BUDGET DOT */}
117
+ <Circle
118
+ cx={x + barWidth + 2}
119
+ cy={yPos(data.series[2].data[i])}
120
+ r={4}
121
+ fill="#8AB6E8"
122
+ onPress={() =>
123
+ setActivePoint({
124
+ x: x + barWidth + 2,
125
+ y: yPos(data.series[2].data[i]),
126
+ value: data.series[2].data[i],
127
+ })
128
+ }
129
+ />
130
+
131
+ {/* BUDGET VALUE */}
132
+ <SvgText
133
+ x={x + barWidth + 2}
134
+ y={yPos(data.series[2].data[i]) - 8}
135
+ fontSize="10"
136
+ textAnchor="middle"
137
+ >
138
+ {formatNumber(data.series[2].data[i])}
139
+ </SvgText>
140
+
141
+ {/* X LABEL */}
142
+ <SvgText
143
+ x={x + barWidth}
144
+ y={height - 10}
145
+ fontSize="10"
146
+ textAnchor="middle"
147
+ transform={`rotate(-35 ${x + barWidth} ${height - 10})`}
148
+ >
149
+ {label}
150
+ </SvgText>
151
+ </React.Fragment>
152
+ );
153
+ })}
154
+
155
+ {activePoint && (
156
+ <>
157
+ <Rect
158
+ x={activePoint.x - 32}
159
+ y={activePoint.y - 44}
160
+ width={64}
161
+ height={26}
162
+ rx={6}
163
+ fill="#000"
164
+ opacity={0.8}
63
165
  />
64
- ))}
65
- </>
66
- )}
67
-
68
- {/* Value labels */}
69
- {data.series[2]?.data.map((v, i) => (
70
- <SvgText
71
- key={i}
72
- x={scaleX(i) + barWidth}
73
- y={scaleY(v) - 8}
74
- fontSize="9"
75
- fill="#333"
76
- textAnchor="middle"
77
- >
78
- {(v / 1_000_000).toFixed(0)}M
79
- </SvgText>
166
+ <SvgText
167
+ x={activePoint.x}
168
+ y={activePoint.y - 26}
169
+ fontSize="10"
170
+ fill="#fff"
171
+ textAnchor="middle"
172
+ >
173
+ {formatNumber(activePoint.value)}
174
+ </SvgText>
175
+ </>
176
+ )}
177
+
178
+
179
+ {/* BUDGET DOTTED LINE */}
180
+ {data.series[2].data.map((v, i) => {
181
+ if (i === data.series[2].data.length - 1) return null;
182
+ return (
183
+ <Line
184
+ key={`line-${i}`}
185
+ x1={padding + i * step + barWidth + 2}
186
+ y1={yPos(v)}
187
+ x2={padding + (i + 1) * step + barWidth + 2}
188
+ y2={yPos(data.series[2].data[i + 1])}
189
+ stroke="#8AB6E8"
190
+ strokeWidth={2}
191
+ strokeDasharray="4"
192
+ />
193
+ );
194
+ })}
195
+ </Svg>
196
+ </ScrollView>
197
+
198
+ {/* LEGEND */}
199
+ <View style={{ flexDirection: 'row', marginTop: 8 }}>
200
+ {data.series.map((s, i) => (
201
+ <Text key={i} style={{ marginRight: 16 }}>
202
+ <Text
203
+ style={{
204
+ color:
205
+ i === 0 ? '#E07A3F' : i === 1 ? '#4E79A7' : '#8AB6E8',
206
+ }}
207
+ >
208
+
209
+ </Text>{' '}
210
+ {s.name}
211
+ </Text>
80
212
  ))}
81
- </Svg>
213
+ </View>
82
214
  </View>
83
215
  );
84
216
  };
@@ -1,84 +1,166 @@
1
- import React from 'react';
2
- import { View, Text } from 'react-native';
3
- import Svg, { Polyline, Circle, Line, Text as SvgText } from 'react-native-svg';
1
+ import React, { useState } from 'react';
2
+ import { View, Text, ScrollView } from 'react-native';
3
+ import Svg, {
4
+ Line,
5
+ Circle,
6
+ Rect,
7
+ Text as SvgText,
8
+ } from 'react-native-svg';
9
+ import { formatNumber } from '../utils/formatNumber';
4
10
 
5
11
  const SvgLineChart = ({ data }) => {
6
- if (!data?.series?.length) return null;
12
+ if (!data?.series || !data.labels?.length) return null;
7
13
 
8
- const width = 340;
9
- const height = 180;
10
- const padding = 30;
14
+ const [activePoint, setActivePoint] = useState(null);
11
15
 
12
- const allValues = data.series.flatMap(s => s.data);
13
- const max = Math.max(...allValues);
14
- const min = Math.min(...allValues);
16
+ const width = Math.max(360, data.labels.length * 80);
17
+ const height = 220;
18
+ const padding = 40;
19
+ const chartHeight = height - padding * 2;
15
20
 
16
- const scaleX = i =>
17
- padding + (i * (width - padding * 2)) / (data.labels.length - 1);
21
+ const values = data.series.flatMap(s => s.data);
22
+ const max = Math.max(...values);
23
+ const min = 0;
18
24
 
19
- const scaleY = v =>
20
- height - padding - ((v - min) / (max - min)) * (height - padding * 2);
25
+ const y = v =>
26
+ height - padding - ((v - min) / (max - min || 1)) * chartHeight;
27
+
28
+ const xStep =
29
+ data.labels.length === 1
30
+ ? 0
31
+ : (width - padding * 2) / (data.labels.length - 1);
32
+
33
+ const colors = ['#E07A3F', '#4E79A7'];
21
34
 
22
35
  return (
23
- <View style={{ backgroundColor: '#fff', padding: 12, borderRadius: 12 }}>
24
- <Text style={{ fontWeight: '700', textAlign: 'center', marginBottom: 8 }}>
36
+ <View style={{ marginVertical: 16 }}>
37
+ <Text style={{ fontWeight: '700', marginBottom: 8 }}>
25
38
  {data.title}
26
39
  </Text>
27
40
 
28
- <Svg width={width} height={height}>
29
- {/* Grid */}
30
- {[0.25, 0.5, 0.75].map(p => (
31
- <Line
32
- key={p}
33
- x1={padding}
34
- x2={width - padding}
35
- y1={padding + p * (height - padding * 2)}
36
- y2={padding + p * (height - padding * 2)}
37
- stroke="#e0e0e0"
38
- strokeDasharray="4,4"
39
- />
40
- ))}
41
+ <ScrollView horizontal>
42
+ <Svg width={width} height={height} onPress={() => setActivePoint(null)}>
43
+ {/* GRID + Y LABELS */}
44
+ {[0.25, 0.5, 0.75].map((p, i) => {
45
+ const yPos = padding + chartHeight * p;
46
+ const labelValue = Math.round(max * (1 - p));
47
+
48
+ return (
49
+ <React.Fragment key={i}>
50
+ <Line
51
+ x1={padding}
52
+ x2={width - padding}
53
+ y1={yPos}
54
+ y2={yPos}
55
+ stroke="#ccc"
56
+ strokeDasharray="4"
57
+ />
58
+ <SvgText
59
+ x={padding - 8}
60
+ y={yPos + 4}
61
+ fontSize="10"
62
+ textAnchor="end"
63
+ >
64
+ {formatNumber(labelValue)}
65
+ </SvgText>
66
+ </React.Fragment>
67
+ );
68
+ })}
69
+
70
+ {/* LINES + DOTS */}
71
+ {data.series.map((s, si) =>
72
+ s.data.map((v, i) => {
73
+ const x = padding + i * xStep;
74
+ const yVal = y(v);
75
+
76
+ return (
77
+ <React.Fragment key={`${si}-${i}`}>
78
+ {i < s.data.length - 1 && (
79
+ <Line
80
+ x1={x}
81
+ y1={yVal}
82
+ x2={padding + (i + 1) * xStep}
83
+ y2={y(s.data[i + 1])}
84
+ stroke={colors[si]}
85
+ strokeWidth={2}
86
+ strokeDasharray={si === 1 ? '4' : '0'}
87
+ />
88
+ )}
89
+
90
+ <Circle
91
+ cx={x}
92
+ cy={yVal}
93
+ r={5}
94
+ fill={colors[si]}
95
+ onPress={() =>
96
+ setActivePoint({ x, y: yVal, value: v })
97
+ }
98
+ />
99
+
100
+ <SvgText
101
+ x={x}
102
+ y={yVal - 8}
103
+ fontSize="10"
104
+ textAnchor="middle"
105
+ >
106
+ {formatNumber(v)}
107
+ </SvgText>
108
+ </React.Fragment>
109
+ );
110
+ })
111
+ )}
41
112
 
42
- {/* Lines */}
43
- {data.series.map((s, idx) => {
44
- const points = s.data
45
- .map((v, i) => `${scaleX(i)},${scaleY(v)}`)
46
- .join(' ');
47
-
48
- return (
49
- <Polyline
50
- key={s.name}
51
- points={points}
52
- fill="none"
53
- stroke={idx === 0 ? '#FB8C00' : '#1E88E5'}
54
- strokeWidth="2"
55
- />
56
- );
57
- })}
58
-
59
- {/* Points + Labels */}
60
- {data.series.map((s, idx) =>
61
- s.data.map((v, i) => (
62
- <React.Fragment key={`${idx}-${i}`}>
63
- <Circle
64
- cx={scaleX(i)}
65
- cy={scaleY(v)}
66
- r="4"
67
- fill={idx === 0 ? '#FB8C00' : '#1E88E5'}
113
+ {/* TOOLTIP (ONLY ONCE) */}
114
+ {activePoint && (
115
+ <>
116
+ <Rect
117
+ x={activePoint.x - 30}
118
+ y={activePoint.y - 40}
119
+ width={60}
120
+ height={24}
121
+ rx={6}
122
+ fill="#000"
123
+ opacity={0.75}
68
124
  />
69
125
  <SvgText
70
- x={scaleX(i)}
71
- y={scaleY(v) - 8}
72
- fontSize="9"
73
- fill="#333"
126
+ x={activePoint.x}
127
+ y={activePoint.y - 24}
128
+ fontSize="10"
129
+ fill="#fff"
74
130
  textAnchor="middle"
75
131
  >
76
- {v}
132
+ {formatNumber(activePoint.value)}
77
133
  </SvgText>
78
- </React.Fragment>
79
- ))
80
- )}
81
- </Svg>
134
+ </>
135
+ )}
136
+
137
+ {/* X LABELS */}
138
+ {data.labels.map((label, i) => {
139
+ const x = padding + i * xStep;
140
+ return (
141
+ <SvgText
142
+ key={i}
143
+ x={x}
144
+ y={height - 10}
145
+ fontSize="10"
146
+ textAnchor="middle"
147
+ transform={`rotate(-35 ${x} ${height - 10})`}
148
+ >
149
+ {label}
150
+ </SvgText>
151
+ );
152
+ })}
153
+ </Svg>
154
+ </ScrollView>
155
+
156
+ {/* LEGEND */}
157
+ <View style={{ flexDirection: 'row', marginTop: 8 }}>
158
+ {data.series.map((s, i) => (
159
+ <Text key={i} style={{ marginRight: 16, color: colors[i] }}>
160
+ ● {s.name}
161
+ </Text>
162
+ ))}
163
+ </View>
82
164
  </View>
83
165
  );
84
166
  };
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';
@@ -7,11 +8,38 @@ import Report3Screen from './screens/Report3Screen';
7
8
 
8
9
  const AnalyticsReports = ({ config }) => {
9
10
  const [active, setActive] = useState(null);
11
+ const [loading, setLoading] = useState(true);
10
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
11
38
  if (!active) {
12
39
  return <ReportListScreen onSelect={setActive} />;
13
40
  }
14
41
 
42
+ // 👉 REPORT 1
15
43
  if (active === 1) {
16
44
  return (
17
45
  <Report1Screen
@@ -22,6 +50,7 @@ const AnalyticsReports = ({ config }) => {
22
50
  );
23
51
  }
24
52
 
53
+ // 👉 REPORT 2
25
54
  if (active === 2) {
26
55
  return (
27
56
  <Report2Screen
@@ -32,6 +61,7 @@ const AnalyticsReports = ({ config }) => {
32
61
  );
33
62
  }
34
63
 
64
+ // 👉 REPORT 3
35
65
  if (active === 3) {
36
66
  return (
37
67
  <Report3Screen
@@ -39,11 +39,31 @@ const Report2Screen = ({ api, token, onBack }) => {
39
39
 
40
40
  if (!table || !line || !bar) return <ActivityIndicator />;
41
41
 
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
+
42
58
  const rows =
43
59
  month === 'ALL'
44
60
  ? table.rows
45
61
  : table.rows.filter(r => r.monthLabel === month);
46
62
 
63
+ const filteredLine = filterChartByMonth(line, month);
64
+ const filteredBar = filterChartByMonth(bar, month);
65
+
66
+
47
67
  return (
48
68
  <ScrollView style={{ padding: 16 }}>
49
69
  <Text onPress={onBack}>‹ Back</Text>
@@ -58,16 +78,18 @@ const Report2Screen = ({ api, token, onBack }) => {
58
78
  onSelect={setMonth}
59
79
  />
60
80
 
61
- <FrozenTableReport2 rows={rows} />
62
-
63
- <SvgLineChart data={line} />
64
- <SvgBarLineChart data={bar} />
65
-
66
81
  <DivisionSelector
67
82
  divisions={divisions}
68
83
  selected={division}
69
84
  onSelect={setDivision}
70
85
  />
86
+
87
+ <FrozenTableReport2 rows={rows} />
88
+
89
+ <SvgLineChart data={filteredLine} />
90
+ <SvgBarLineChart data={filteredBar} />
91
+
92
+
71
93
  </ScrollView>
72
94
  );
73
95
  };
@@ -26,6 +26,21 @@ const Report3Screen = ({ api, token, onBack }) => {
26
26
 
27
27
  if (!table || !line || !bar) return <ActivityIndicator />;
28
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
+
29
44
  const months = table.rows.filter(r => r.month !== 99).map(r => r.monthLabel);
30
45
 
31
46
  const rows =
@@ -33,6 +48,10 @@ const Report3Screen = ({ api, token, onBack }) => {
33
48
  ? table.rows
34
49
  : table.rows.filter(r => r.monthLabel === month);
35
50
 
51
+ const filteredLine = filterChartByMonth(line, month);
52
+ const filteredBar = filterChartByMonth(bar, month);
53
+
54
+
36
55
  return (
37
56
  <ScrollView style={{ padding: 16 }}>
38
57
  <Text onPress={onBack}>‹ Back</Text>
@@ -49,8 +68,8 @@ const Report3Screen = ({ api, token, onBack }) => {
49
68
 
50
69
  <FrozenTableReport3 rows={rows} />
51
70
 
52
- <SvgLineChart data={line} />
53
- <SvgBarLineChart data={bar} />
71
+ <SvgLineChart data={filteredLine} />
72
+ <SvgBarLineChart data={filteredBar} />
54
73
  </ScrollView>
55
74
  );
56
75
  };
@@ -0,0 +1,12 @@
1
+ export const formatNumber = (value) => {
2
+ if (value >= 1_000_000_000) {
3
+ return (value / 1_000_000_000).toFixed(1) + 'bn';
4
+ }
5
+ if (value >= 1_000_000) {
6
+ return (value / 1_000_000).toFixed(1) + 'M';
7
+ }
8
+ if (value >= 1_000) {
9
+ return (value / 1_000).toFixed(0) + 'k';
10
+ }
11
+ return value.toString();
12
+ };