@dhiraj0720/report1chart 3.0.6 → 3.0.7
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/components/CompactAxisBarLineChart.jsx +320 -0
- package/src/components/CompactAxisLineChart.jsx +238 -0
- package/src/index.jsx +14 -1
- package/src/screens/Report1ModernScreen.jsx +551 -0
- package/src/screens/Report2ModernScreen.jsx +89 -337
- package/src/screens/Report3ModernScreen.jsx +81 -220
- package/src/screens/ReportListScreen.jsx +5 -0
|
@@ -8,10 +8,16 @@ import {
|
|
|
8
8
|
TouchableOpacity,
|
|
9
9
|
View,
|
|
10
10
|
} from 'react-native';
|
|
11
|
-
import Svg, { Circle
|
|
12
|
-
import {
|
|
11
|
+
import Svg, { Circle } from 'react-native-svg';
|
|
12
|
+
import {
|
|
13
|
+
fetchReport3Bar,
|
|
14
|
+
fetchReport3Line,
|
|
15
|
+
fetchReport3Table,
|
|
16
|
+
} from '../api/report3Fetcher';
|
|
13
17
|
import MonthFilterModal from '../components/MonthFilterModal';
|
|
14
|
-
import
|
|
18
|
+
import CompactAxisBarLineChart from '../components/CompactAxisBarLineChart';
|
|
19
|
+
import CompactAxisLineChart from '../components/CompactAxisLineChart';
|
|
20
|
+
import { filterChartByMonths } from '../utils/filterChartByMonths';
|
|
15
21
|
|
|
16
22
|
const toNumber = (value) => {
|
|
17
23
|
const numeric = Number(value);
|
|
@@ -45,7 +51,7 @@ const StatTile = ({ label, value, hint, tone }) => (
|
|
|
45
51
|
);
|
|
46
52
|
|
|
47
53
|
const RingGauge = ({ progress }) => {
|
|
48
|
-
const size =
|
|
54
|
+
const size = 58;
|
|
49
55
|
const stroke = 8;
|
|
50
56
|
const radius = (size - stroke) / 2;
|
|
51
57
|
const circumference = 2 * Math.PI * radius;
|
|
@@ -78,124 +84,37 @@ const RingGauge = ({ progress }) => {
|
|
|
78
84
|
);
|
|
79
85
|
};
|
|
80
86
|
|
|
81
|
-
const SignalChart = ({ rows, mode }) => {
|
|
82
|
-
if (!rows.length) return null;
|
|
83
|
-
|
|
84
|
-
const values2024 = rows.map((row) =>
|
|
85
|
-
mode === 'loads' ? toNumber(row.loadCount2024) : toNumber(row.revenueTl2024),
|
|
86
|
-
);
|
|
87
|
-
const values2025 = rows.map((row) =>
|
|
88
|
-
mode === 'loads' ? toNumber(row.loadCount2025) : toNumber(row.revenueTl2025),
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const width = Math.max(360, rows.length * 72);
|
|
92
|
-
const height = 190;
|
|
93
|
-
const left = 18;
|
|
94
|
-
const right = 12;
|
|
95
|
-
const top = 16;
|
|
96
|
-
const bottom = 38;
|
|
97
|
-
const chartHeight = height - top - bottom;
|
|
98
|
-
|
|
99
|
-
const all = [...values2024, ...values2025];
|
|
100
|
-
const min = Math.min(...all);
|
|
101
|
-
const max = Math.max(...all);
|
|
102
|
-
const pad = Math.max(1, (max - min) * 0.14);
|
|
103
|
-
const yMin = min - pad;
|
|
104
|
-
const yMax = max + pad;
|
|
105
|
-
|
|
106
|
-
const stepX = rows.length > 1 ? (width - left - right) / (rows.length - 1) : width / 2;
|
|
107
|
-
const toY = (value) => top + ((yMax - value) / Math.max(1, yMax - yMin)) * chartHeight;
|
|
108
|
-
const toX = (index) => left + index * stepX;
|
|
109
|
-
|
|
110
|
-
const points2024 = values2024.map((value, i) => `${toX(i)},${toY(value)}`).join(' ');
|
|
111
|
-
const points2025 = values2025.map((value, i) => `${toX(i)},${toY(value)}`).join(' ');
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<View style={styles.signalCard}>
|
|
115
|
-
<View style={styles.signalHeader}>
|
|
116
|
-
<Text style={styles.signalTitle}>
|
|
117
|
-
{mode === 'loads' ? 'Load Movement Signal' : 'Revenue Movement Signal'}
|
|
118
|
-
</Text>
|
|
119
|
-
<View style={styles.signalLegend}>
|
|
120
|
-
<Text style={styles.legend2024}>2024</Text>
|
|
121
|
-
<Text style={styles.legend2025}>2025</Text>
|
|
122
|
-
</View>
|
|
123
|
-
</View>
|
|
124
|
-
|
|
125
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
126
|
-
<Svg width={width} height={height}>
|
|
127
|
-
{[0, 0.5, 1].map((ratio) => {
|
|
128
|
-
const y = top + ratio * chartHeight;
|
|
129
|
-
return (
|
|
130
|
-
<Line
|
|
131
|
-
key={ratio}
|
|
132
|
-
x1={left}
|
|
133
|
-
y1={y}
|
|
134
|
-
x2={width - right}
|
|
135
|
-
y2={y}
|
|
136
|
-
stroke="#e7e3d9"
|
|
137
|
-
strokeDasharray="4 5"
|
|
138
|
-
strokeWidth={1}
|
|
139
|
-
/>
|
|
140
|
-
);
|
|
141
|
-
})}
|
|
142
|
-
|
|
143
|
-
<Polyline
|
|
144
|
-
points={points2024}
|
|
145
|
-
fill="none"
|
|
146
|
-
stroke="#f59f40"
|
|
147
|
-
strokeWidth={3}
|
|
148
|
-
strokeLinecap="round"
|
|
149
|
-
strokeLinejoin="round"
|
|
150
|
-
strokeDasharray="8 6"
|
|
151
|
-
/>
|
|
152
|
-
<Polyline
|
|
153
|
-
points={points2025}
|
|
154
|
-
fill="none"
|
|
155
|
-
stroke="#168a73"
|
|
156
|
-
strokeWidth={3}
|
|
157
|
-
strokeLinecap="round"
|
|
158
|
-
strokeLinejoin="round"
|
|
159
|
-
/>
|
|
160
|
-
|
|
161
|
-
{rows.map((row, index) => (
|
|
162
|
-
<React.Fragment key={`${row.monthLabel}-${index}`}>
|
|
163
|
-
<Circle cx={toX(index)} cy={toY(values2024[index])} r={4.8} fill="#f59f40" />
|
|
164
|
-
<Circle cx={toX(index)} cy={toY(values2025[index])} r={4.8} fill="#168a73" />
|
|
165
|
-
</React.Fragment>
|
|
166
|
-
))}
|
|
167
|
-
</Svg>
|
|
168
|
-
</ScrollView>
|
|
169
|
-
|
|
170
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.signalMonthStrip}>
|
|
171
|
-
{rows.map((row) => (
|
|
172
|
-
<View key={`month-${row.monthLabel}`} style={styles.signalMonthChip}>
|
|
173
|
-
<Text style={styles.signalMonthText}>{row.monthLabel}</Text>
|
|
174
|
-
</View>
|
|
175
|
-
))}
|
|
176
|
-
</ScrollView>
|
|
177
|
-
</View>
|
|
178
|
-
);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
87
|
const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
182
88
|
const [loading, setLoading] = useState(true);
|
|
183
89
|
const [refreshing, setRefreshing] = useState(false);
|
|
184
90
|
const [monthsModal, setMonthsModal] = useState(false);
|
|
185
91
|
const [table, setTable] = useState(null);
|
|
92
|
+
const [lineData, setLineData] = useState(null);
|
|
93
|
+
const [barData, setBarData] = useState(null);
|
|
186
94
|
const [selectedMonths, setSelectedMonths] = useState([]);
|
|
187
|
-
const [focusMonth, setFocusMonth] = useState('ALL');
|
|
188
95
|
const [mode, setMode] = useState('loads');
|
|
189
96
|
|
|
190
|
-
const loadData = useCallback(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
97
|
+
const loadData = useCallback(
|
|
98
|
+
async (preserveSelection = false) => {
|
|
99
|
+
const [tableRes, lineRes, barRes] = await Promise.all([
|
|
100
|
+
fetchReport3Table(api.table, token),
|
|
101
|
+
fetchReport3Line(api.line, token),
|
|
102
|
+
fetchReport3Bar(api.bar, token),
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
setTable(tableRes);
|
|
106
|
+
setLineData(lineRes);
|
|
107
|
+
setBarData(barRes);
|
|
108
|
+
|
|
109
|
+
const months = lineRes?.labels || [];
|
|
110
|
+
setSelectedMonths((prev) => {
|
|
111
|
+
if (!preserveSelection) return months;
|
|
112
|
+
const kept = prev.filter((month) => months.includes(month));
|
|
113
|
+
return kept.length ? kept : months;
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
[api.bar, api.line, api.table, token],
|
|
117
|
+
);
|
|
199
118
|
|
|
200
119
|
useEffect(() => {
|
|
201
120
|
setLoading(true);
|
|
@@ -207,28 +126,34 @@ const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
|
207
126
|
const onRefresh = useCallback(async () => {
|
|
208
127
|
setRefreshing(true);
|
|
209
128
|
try {
|
|
210
|
-
await loadData();
|
|
129
|
+
await loadData(true);
|
|
211
130
|
} finally {
|
|
212
131
|
setRefreshing(false);
|
|
213
132
|
}
|
|
214
133
|
}, [loadData]);
|
|
215
134
|
|
|
135
|
+
const monthOptions = useMemo(() => {
|
|
136
|
+
return lineData?.labels || [];
|
|
137
|
+
}, [lineData]);
|
|
138
|
+
|
|
216
139
|
const baseRows = useMemo(() => {
|
|
217
140
|
return (table?.rows || []).filter((row) => row.month !== 99 && row.monthLabel !== 'Total');
|
|
218
141
|
}, [table]);
|
|
219
142
|
|
|
220
143
|
const rows = useMemo(() => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
if (focusMonth !== 'ALL') {
|
|
226
|
-
filtered = filtered.filter((row) => row.monthLabel === focusMonth);
|
|
227
|
-
}
|
|
228
|
-
return filtered;
|
|
229
|
-
}, [baseRows, selectedMonths, focusMonth]);
|
|
144
|
+
if (!selectedMonths.length) return [];
|
|
145
|
+
return baseRows.filter((row) => selectedMonths.includes(row.monthLabel));
|
|
146
|
+
}, [baseRows, selectedMonths]);
|
|
230
147
|
|
|
231
|
-
const
|
|
148
|
+
const filteredLine = useMemo(() => {
|
|
149
|
+
if (!lineData) return null;
|
|
150
|
+
return filterChartByMonths(lineData, selectedMonths);
|
|
151
|
+
}, [lineData, selectedMonths]);
|
|
152
|
+
|
|
153
|
+
const filteredBar = useMemo(() => {
|
|
154
|
+
if (!barData) return null;
|
|
155
|
+
return filterChartByMonths(barData, selectedMonths);
|
|
156
|
+
}, [barData, selectedMonths]);
|
|
232
157
|
|
|
233
158
|
const load2024Total = useMemo(
|
|
234
159
|
() => rows.reduce((sum, row) => sum + toNumber(row.loadCount2024), 0),
|
|
@@ -267,15 +192,15 @@ const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
|
267
192
|
|
|
268
193
|
const maxLaneValue = useMemo(() => {
|
|
269
194
|
if (!rows.length) return 1;
|
|
270
|
-
const values = rows.flatMap((row) =>
|
|
195
|
+
const values = rows.flatMap((row) =>
|
|
271
196
|
mode === 'loads'
|
|
272
197
|
? [toNumber(row.loadCount2024), toNumber(row.loadCount2025)]
|
|
273
|
-
: [toNumber(row.revenueTl2024), toNumber(row.revenueTl2025), toNumber(row.budgetRevenueTl2025)]
|
|
274
|
-
)
|
|
198
|
+
: [toNumber(row.revenueTl2024), toNumber(row.revenueTl2025), toNumber(row.budgetRevenueTl2025)],
|
|
199
|
+
);
|
|
275
200
|
return Math.max(1, ...values);
|
|
276
201
|
}, [rows, mode]);
|
|
277
202
|
|
|
278
|
-
if (loading && !table) {
|
|
203
|
+
if (loading && (!table || !lineData || !barData)) {
|
|
279
204
|
return (
|
|
280
205
|
<View style={styles.loaderWrap}>
|
|
281
206
|
<ActivityIndicator size="large" color="#13866f" />
|
|
@@ -292,7 +217,7 @@ const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
|
292
217
|
<Text style={styles.backIcon}>‹</Text>
|
|
293
218
|
</TouchableOpacity>
|
|
294
219
|
<Text style={styles.heroTitle}>Transportation Command Center</Text>
|
|
295
|
-
<Text style={styles.heroSubtitle}>
|
|
220
|
+
<Text style={styles.heroSubtitle}>Filtered API visuals with compact axis charts</Text>
|
|
296
221
|
</View>
|
|
297
222
|
|
|
298
223
|
<ScrollView
|
|
@@ -308,7 +233,7 @@ const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
|
308
233
|
>
|
|
309
234
|
<Text style={styles.controlChipLabel}>Months</Text>
|
|
310
235
|
<Text style={styles.controlChipValue}>
|
|
311
|
-
{selectedMonths.length}/{
|
|
236
|
+
{selectedMonths.length}/{monthOptions.length || selectedMonths.length}
|
|
312
237
|
</Text>
|
|
313
238
|
</TouchableOpacity>
|
|
314
239
|
|
|
@@ -332,20 +257,6 @@ const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
|
332
257
|
</View>
|
|
333
258
|
</View>
|
|
334
259
|
|
|
335
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.quickMonthRow}>
|
|
336
|
-
{quickMonths.map((month) => (
|
|
337
|
-
<TouchableOpacity
|
|
338
|
-
key={month}
|
|
339
|
-
style={[styles.monthChip, focusMonth === month && styles.monthChipActive]}
|
|
340
|
-
onPress={() => setFocusMonth(month)}
|
|
341
|
-
>
|
|
342
|
-
<Text style={[styles.monthChipText, focusMonth === month && styles.monthChipTextActive]}>
|
|
343
|
-
{month}
|
|
344
|
-
</Text>
|
|
345
|
-
</TouchableOpacity>
|
|
346
|
-
))}
|
|
347
|
-
</ScrollView>
|
|
348
|
-
|
|
349
260
|
<View style={styles.statRow}>
|
|
350
261
|
<StatTile
|
|
351
262
|
label="Load YoY"
|
|
@@ -375,7 +286,24 @@ const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
|
375
286
|
/>
|
|
376
287
|
</View>
|
|
377
288
|
|
|
378
|
-
<
|
|
289
|
+
<CompactAxisLineChart
|
|
290
|
+
data={filteredLine}
|
|
291
|
+
title="Load Amount Trend (Line)"
|
|
292
|
+
legend={[
|
|
293
|
+
{ label: filteredLine?.series?.[0]?.name || '2024 Load', color: '#F29F45' },
|
|
294
|
+
{ label: filteredLine?.series?.[1]?.name || '2025 Load', color: '#2E7DD1' },
|
|
295
|
+
]}
|
|
296
|
+
/>
|
|
297
|
+
|
|
298
|
+
<CompactAxisBarLineChart
|
|
299
|
+
data={filteredBar}
|
|
300
|
+
title="Revenue vs Budget (Compact)"
|
|
301
|
+
legend={[
|
|
302
|
+
{ label: filteredBar?.series?.[0]?.name || '2024 Revenue', color: '#F29F45' },
|
|
303
|
+
{ label: filteredBar?.series?.[1]?.name || '2025 Revenue', color: '#2E7DD1' },
|
|
304
|
+
{ label: filteredBar?.series?.[2]?.name || '2025 Budget', color: '#8AB6E8' },
|
|
305
|
+
]}
|
|
306
|
+
/>
|
|
379
307
|
|
|
380
308
|
<Text style={styles.sectionTitle}>Operational lanes</Text>
|
|
381
309
|
{rows.map((row) => {
|
|
@@ -429,14 +357,16 @@ const Report3ModernScreen = ({ api, token, onBack }) => {
|
|
|
429
357
|
|
|
430
358
|
{!rows.length ? (
|
|
431
359
|
<View style={styles.emptyWrap}>
|
|
432
|
-
<Text style={styles.emptyText}>No data for selected
|
|
360
|
+
<Text style={styles.emptyText}>No data for selected month filter.</Text>
|
|
433
361
|
</View>
|
|
434
362
|
) : null}
|
|
363
|
+
|
|
364
|
+
<View style={styles.footerSpacing} />
|
|
435
365
|
</ScrollView>
|
|
436
366
|
|
|
437
367
|
<MonthFilterModal
|
|
438
368
|
visible={monthsModal}
|
|
439
|
-
months={
|
|
369
|
+
months={monthOptions}
|
|
440
370
|
selected={selectedMonths}
|
|
441
371
|
onApply={setSelectedMonths}
|
|
442
372
|
onClose={() => setMonthsModal(false)}
|
|
@@ -563,30 +493,6 @@ const styles = StyleSheet.create({
|
|
|
563
493
|
modeButtonTextActive: {
|
|
564
494
|
color: '#fff',
|
|
565
495
|
},
|
|
566
|
-
quickMonthRow: {
|
|
567
|
-
marginBottom: 12,
|
|
568
|
-
},
|
|
569
|
-
monthChip: {
|
|
570
|
-
backgroundColor: '#fffdf8',
|
|
571
|
-
borderWidth: 1,
|
|
572
|
-
borderColor: '#ddd2be',
|
|
573
|
-
borderRadius: 999,
|
|
574
|
-
paddingVertical: 7,
|
|
575
|
-
paddingHorizontal: 12,
|
|
576
|
-
marginRight: 8,
|
|
577
|
-
},
|
|
578
|
-
monthChipActive: {
|
|
579
|
-
backgroundColor: '#e3912f',
|
|
580
|
-
borderColor: '#e3912f',
|
|
581
|
-
},
|
|
582
|
-
monthChipText: {
|
|
583
|
-
fontSize: 12,
|
|
584
|
-
color: '#635640',
|
|
585
|
-
fontWeight: '700',
|
|
586
|
-
},
|
|
587
|
-
monthChipTextActive: {
|
|
588
|
-
color: '#fff',
|
|
589
|
-
},
|
|
590
496
|
statRow: {
|
|
591
497
|
flexDirection: 'row',
|
|
592
498
|
marginBottom: 10,
|
|
@@ -626,54 +532,6 @@ const styles = StyleSheet.create({
|
|
|
626
532
|
fontSize: 11,
|
|
627
533
|
color: '#6b6860',
|
|
628
534
|
},
|
|
629
|
-
signalCard: {
|
|
630
|
-
backgroundColor: '#fffdf8',
|
|
631
|
-
borderWidth: 1,
|
|
632
|
-
borderColor: '#e0d7c6',
|
|
633
|
-
borderRadius: 16,
|
|
634
|
-
padding: 12,
|
|
635
|
-
marginBottom: 12,
|
|
636
|
-
},
|
|
637
|
-
signalHeader: {
|
|
638
|
-
flexDirection: 'row',
|
|
639
|
-
justifyContent: 'space-between',
|
|
640
|
-
alignItems: 'center',
|
|
641
|
-
marginBottom: 8,
|
|
642
|
-
},
|
|
643
|
-
signalTitle: {
|
|
644
|
-
fontSize: 14,
|
|
645
|
-
fontWeight: '800',
|
|
646
|
-
color: '#2c2a22',
|
|
647
|
-
},
|
|
648
|
-
signalLegend: {
|
|
649
|
-
flexDirection: 'row',
|
|
650
|
-
},
|
|
651
|
-
legend2024: {
|
|
652
|
-
marginRight: 10,
|
|
653
|
-
color: '#d78835',
|
|
654
|
-
fontWeight: '700',
|
|
655
|
-
fontSize: 12,
|
|
656
|
-
},
|
|
657
|
-
legend2025: {
|
|
658
|
-
color: '#15896f',
|
|
659
|
-
fontWeight: '700',
|
|
660
|
-
fontSize: 12,
|
|
661
|
-
},
|
|
662
|
-
signalMonthStrip: {
|
|
663
|
-
marginTop: 6,
|
|
664
|
-
},
|
|
665
|
-
signalMonthChip: {
|
|
666
|
-
backgroundColor: '#f5efe2',
|
|
667
|
-
borderRadius: 999,
|
|
668
|
-
paddingVertical: 5,
|
|
669
|
-
paddingHorizontal: 10,
|
|
670
|
-
marginRight: 6,
|
|
671
|
-
},
|
|
672
|
-
signalMonthText: {
|
|
673
|
-
color: '#635742',
|
|
674
|
-
fontSize: 11,
|
|
675
|
-
fontWeight: '700',
|
|
676
|
-
},
|
|
677
535
|
sectionTitle: {
|
|
678
536
|
fontSize: 15,
|
|
679
537
|
fontWeight: '800',
|
|
@@ -780,6 +638,9 @@ const styles = StyleSheet.create({
|
|
|
780
638
|
fontSize: 13,
|
|
781
639
|
color: '#6d675a',
|
|
782
640
|
},
|
|
641
|
+
footerSpacing: {
|
|
642
|
+
height: 10,
|
|
643
|
+
},
|
|
783
644
|
});
|
|
784
645
|
|
|
785
646
|
export default Report3ModernScreen;
|