@dhiraj0720/report1chart 3.0.5 → 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.
@@ -8,9 +8,12 @@ import {
8
8
  TouchableOpacity,
9
9
  View,
10
10
  } from 'react-native';
11
- import { getDivisions, getTable } from '../api/report2Fetcher';
11
+ import { getBar, getDivisions, getLine, getTable } from '../api/report2Fetcher';
12
12
  import DivisionFilterModal from '../components/DivisionFilterModal';
13
13
  import MonthFilterModal from '../components/MonthFilterModal';
14
+ import CompactAxisLineChart from '../components/CompactAxisLineChart';
15
+ import CompactAxisBarLineChart from '../components/CompactAxisBarLineChart';
16
+ import { filterChartByMonths } from '../utils/filterChartByMonths';
14
17
  import { formatNumber } from '../utils/formatNumber';
15
18
 
16
19
  const toNumber = (value) => {
@@ -52,70 +55,17 @@ const MetricCell = ({ label, value, accent }) => (
52
55
  </View>
53
56
  );
54
57
 
55
- const ModernBars = ({ rows, metric }) => {
56
- const key2024 = metric === 'profit' ? 'profitUsd2024' : 'teu2024';
57
- const key2025 = metric === 'profit' ? 'profitUsd2025' : 'teu2025';
58
- const maxValue = Math.max(
59
- 1,
60
- ...rows.map((row) => Math.max(toNumber(row[key2024]), toNumber(row[key2025]))),
61
- );
62
-
63
- return (
64
- <View style={styles.chartCard}>
65
- <View style={styles.chartHeader}>
66
- <Text style={styles.chartTitle}>
67
- {metric === 'profit' ? 'Profit Trend by Month' : 'TEU Trend by Month'}
68
- </Text>
69
- <View style={styles.legendRow}>
70
- <Text style={styles.legendTextOrange}>2024</Text>
71
- <Text style={styles.legendTextBlue}>2025</Text>
72
- </View>
73
- </View>
74
-
75
- <ScrollView horizontal showsHorizontalScrollIndicator={false}>
76
- <View style={styles.chartColumns}>
77
- {rows.map((row) => {
78
- const value2024 = toNumber(row[key2024]);
79
- const value2025 = toNumber(row[key2025]);
80
- const pct = toPercent(value2025, value2024);
81
- const bar2024Height = Math.max(6, (value2024 / maxValue) * 122);
82
- const bar2025Height = Math.max(6, (value2025 / maxValue) * 122);
83
-
84
- return (
85
- <View key={row.monthLabel} style={styles.chartGroup}>
86
- <View style={styles.barPair}>
87
- <View style={styles.barTrack}>
88
- <View style={[styles.barFill2024, { height: bar2024Height }]} />
89
- </View>
90
- <View style={styles.barTrack}>
91
- <View style={[styles.barFill2025, { height: bar2025Height }]} />
92
- </View>
93
- </View>
94
- <Text style={styles.chartMonthText}>{row.monthLabel.slice(0, 3)}</Text>
95
- <Text style={[styles.chartDelta, pct >= 0 ? styles.deltaUp : styles.deltaDown]}>
96
- {pct >= 0 ? '+' : ''}
97
- {pct.toFixed(0)}%
98
- </Text>
99
- </View>
100
- );
101
- })}
102
- </View>
103
- </ScrollView>
104
- </View>
105
- );
106
- };
107
-
108
58
  const Report2ModernScreen = ({ api, token, onBack }) => {
109
59
  const [divisionModal, setDivisionModal] = useState(false);
110
60
  const [monthsModal, setMonthsModal] = useState(false);
111
61
  const [divisions, setDivisions] = useState([]);
112
62
  const [division, setDivision] = useState(null);
113
63
  const [table, setTable] = useState(null);
64
+ const [lineData, setLineData] = useState(null);
65
+ const [barData, setBarData] = useState(null);
114
66
  const [loading, setLoading] = useState(true);
115
67
  const [refreshing, setRefreshing] = useState(false);
116
68
  const [selectedMonths, setSelectedMonths] = useState([]);
117
- const [focusMonth, setFocusMonth] = useState('ALL');
118
- const [metric, setMetric] = useState('profit');
119
69
 
120
70
  const loadDivisions = useCallback(async () => {
121
71
  const data = await getDivisions(api.divisions, token);
@@ -123,15 +73,29 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
123
73
  setDivision((prev) => prev || data[0]?.code || prev);
124
74
  }, [api.divisions, token]);
125
75
 
126
- const loadTable = useCallback(async (divisionCode) => {
127
- const reportTable = await getTable(api.table, divisionCode, token);
128
- setTable(reportTable);
129
- const monthLabels = (reportTable?.rows || [])
130
- .filter((row) => row.monthLabel && row.monthLabel !== 'Total')
131
- .map((row) => row.monthLabel);
132
- setSelectedMonths(monthLabels);
133
- setFocusMonth('ALL');
134
- }, [api.table, token]);
76
+ const loadReport = useCallback(
77
+ async (divisionCode, preserveSelection = false) => {
78
+ const [reportTable, reportLine, reportBar] = await Promise.all([
79
+ getTable(api.table, divisionCode, token),
80
+ getLine(api.line, divisionCode, token),
81
+ getBar(api.bar, divisionCode, token),
82
+ ]);
83
+
84
+ setTable(reportTable);
85
+ setLineData(reportLine);
86
+ setBarData(reportBar);
87
+
88
+ const monthLabels = reportLine?.labels || [];
89
+ setSelectedMonths((prev) => {
90
+ if (!preserveSelection) {
91
+ return monthLabels;
92
+ }
93
+ const kept = prev.filter((month) => monthLabels.includes(month));
94
+ return kept.length ? kept : monthLabels;
95
+ });
96
+ },
97
+ [api.bar, api.line, api.table, token],
98
+ );
135
99
 
136
100
  useEffect(() => {
137
101
  setLoading(true);
@@ -143,43 +107,53 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
143
107
  useEffect(() => {
144
108
  if (!division) return;
145
109
  setLoading(true);
146
- loadTable(division)
110
+ loadReport(division)
147
111
  .catch(() => {})
148
112
  .finally(() => setLoading(false));
149
- }, [division, loadTable]);
113
+ }, [division, loadReport]);
150
114
 
151
115
  const onRefresh = useCallback(async () => {
152
116
  if (!division) return;
153
117
  setRefreshing(true);
154
118
  try {
155
- await loadTable(division);
119
+ await loadReport(division, true);
156
120
  } finally {
157
121
  setRefreshing(false);
158
122
  }
159
- }, [division, loadTable]);
123
+ }, [division, loadReport]);
160
124
 
161
125
  const divisionName = useMemo(() => {
162
126
  return divisions.find((item) => item.code === division)?.displayName || 'Division';
163
127
  }, [division, divisions]);
164
128
 
129
+ const monthOptions = useMemo(() => {
130
+ return lineData?.labels || [];
131
+ }, [lineData]);
132
+
165
133
  const baseRows = useMemo(() => {
166
- return (table?.rows || []).filter((row) => row.monthLabel && row.monthLabel !== 'Total');
134
+ return table?.rows || [];
167
135
  }, [table]);
168
136
 
169
137
  const rows = useMemo(() => {
170
- let filtered = baseRows;
171
- if (selectedMonths.length > 0) {
172
- filtered = filtered.filter((row) => selectedMonths.includes(row.monthLabel));
173
- }
174
- if (focusMonth !== 'ALL') {
175
- filtered = filtered.filter((row) => row.monthLabel === focusMonth);
176
- }
177
- return filtered;
178
- }, [baseRows, focusMonth, selectedMonths]);
138
+ if (!selectedMonths.length) return [];
139
+ return baseRows.filter((row) => selectedMonths.includes(row.monthLabel));
140
+ }, [baseRows, selectedMonths]);
141
+
142
+ const filteredLine = useMemo(() => {
143
+ if (!lineData) return null;
144
+ return filterChartByMonths(lineData, selectedMonths);
145
+ }, [lineData, selectedMonths]);
146
+
147
+ const filteredBar = useMemo(() => {
148
+ if (!barData) return null;
149
+ return filterChartByMonths(barData, selectedMonths);
150
+ }, [barData, selectedMonths]);
179
151
 
180
152
  const total2024Profit = useMemo(() => rows.reduce((sum, row) => sum + toNumber(row.profitUsd2024), 0), [rows]);
181
153
  const total2025Profit = useMemo(() => rows.reduce((sum, row) => sum + toNumber(row.profitUsd2025), 0), [rows]);
182
154
  const total2025Budget = useMemo(() => rows.reduce((sum, row) => sum + toNumber(row.budgetProfitUsd2025), 0), [rows]);
155
+ const total2024Teu = useMemo(() => rows.reduce((sum, row) => sum + toNumber(row.teu2024), 0), [rows]);
156
+ const total2025Teu = useMemo(() => rows.reduce((sum, row) => sum + toNumber(row.teu2025), 0), [rows]);
183
157
  const budgetRate = total2025Budget ? (total2025Profit / total2025Budget) * 100 : 0;
184
158
  const yoyPercent = toPercent(total2025Profit, total2024Profit);
185
159
 
@@ -187,15 +161,11 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
187
161
  if (!rows.length) return null;
188
162
  return rows.reduce((best, row) => {
189
163
  if (!best) return row;
190
- const bestPct = toNumber(best.profitChangePercent);
191
- const currentPct = toNumber(row.profitChangePercent);
192
- return currentPct > bestPct ? row : best;
164
+ return toNumber(row.profitChangePercent) > toNumber(best.profitChangePercent) ? row : best;
193
165
  }, null);
194
166
  }, [rows]);
195
167
 
196
- const quickMonths = useMemo(() => ['ALL', ...selectedMonths.slice(0, 5)], [selectedMonths]);
197
-
198
- if (loading && !table) {
168
+ if (loading && (!table || !lineData || !barData)) {
199
169
  return (
200
170
  <View style={styles.loaderWrap}>
201
171
  <ActivityIndicator size="large" color="#144a8a" />
@@ -212,7 +182,7 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
212
182
  <Text style={styles.backIcon}>‹</Text>
213
183
  </TouchableOpacity>
214
184
  <Text style={styles.heroTitle}>Gross Profit Dashboard</Text>
215
- <Text style={styles.heroSubtitle}>Modern view - interactive monthly insights</Text>
185
+ <Text style={styles.heroSubtitle}>Compact analytical view with filtered month data</Text>
216
186
  </View>
217
187
 
218
188
  <ScrollView
@@ -236,7 +206,7 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
236
206
  >
237
207
  <Text style={styles.filterChipLabel}>Months</Text>
238
208
  <Text style={styles.filterChipValue}>
239
- {selectedMonths.length}/{baseRows.length || selectedMonths.length}
209
+ {selectedMonths.length}/{monthOptions.length || selectedMonths.length}
240
210
  </Text>
241
211
  </TouchableOpacity>
242
212
  </View>
@@ -271,42 +241,30 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
271
241
  <Text style={styles.insightTitle}>Best Month</Text>
272
242
  <Text style={styles.insightValue}>{bestMonth?.monthLabel || '-'}</Text>
273
243
  </View>
244
+ <View>
245
+ <Text style={styles.insightTitle}>TEU 2025</Text>
246
+ <Text style={styles.insightValue}>{formatNumber(total2025Teu)}</Text>
247
+ </View>
274
248
  </View>
275
249
 
276
- <View style={styles.metricSwitch}>
277
- <TouchableOpacity
278
- style={[styles.metricButton, metric === 'profit' && styles.metricButtonActive]}
279
- onPress={() => setMetric('profit')}
280
- >
281
- <Text style={[styles.metricButtonText, metric === 'profit' && styles.metricButtonTextActive]}>
282
- Profit
283
- </Text>
284
- </TouchableOpacity>
285
- <TouchableOpacity
286
- style={[styles.metricButton, metric === 'teu' && styles.metricButtonActive]}
287
- onPress={() => setMetric('teu')}
288
- >
289
- <Text style={[styles.metricButtonText, metric === 'teu' && styles.metricButtonTextActive]}>
290
- TEU
291
- </Text>
292
- </TouchableOpacity>
293
- </View>
294
-
295
- <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.monthQuickScroll}>
296
- {quickMonths.map((month) => (
297
- <TouchableOpacity
298
- key={month}
299
- style={[styles.monthChip, focusMonth === month && styles.monthChipActive]}
300
- onPress={() => setFocusMonth(month)}
301
- >
302
- <Text style={[styles.monthChipText, focusMonth === month && styles.monthChipTextActive]}>
303
- {month}
304
- </Text>
305
- </TouchableOpacity>
306
- ))}
307
- </ScrollView>
308
-
309
- <ModernBars rows={rows} metric={metric} />
250
+ <CompactAxisLineChart
251
+ data={filteredLine}
252
+ title="Profit Amount Trend (Line)"
253
+ legend={[
254
+ { label: filteredLine?.series?.[0]?.name || '2024 Profit', color: '#F29F45' },
255
+ { label: filteredLine?.series?.[1]?.name || '2025 Profit', color: '#2E7DD1' },
256
+ ]}
257
+ />
258
+
259
+ <CompactAxisBarLineChart
260
+ data={filteredBar}
261
+ title="Profit vs Budget (Compact)"
262
+ legend={[
263
+ { label: filteredBar?.series?.[0]?.name || '2024 Profit', color: '#F29F45' },
264
+ { label: filteredBar?.series?.[1]?.name || '2025 Profit', color: '#2E7DD1' },
265
+ { label: filteredBar?.series?.[2]?.name || 'Budget', color: '#8AB6E8' },
266
+ ]}
267
+ />
310
268
 
311
269
  <Text style={styles.sectionTitle}>Monthly insight cards</Text>
312
270
  {rows.map((row) => (
@@ -320,37 +278,20 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
320
278
  <MetricCell label="2024 Profit" value={formatNumber(row.profitUsd2024)} />
321
279
  <MetricCell label="2025 Profit" value={formatNumber(row.profitUsd2025)} />
322
280
  <MetricCell label="2025 Budget" value={formatNumber(row.budgetProfitUsd2025)} />
323
- <MetricCell
324
- label="Budget Delta"
325
- value={`${toNumber(row.budgetChangePercent).toFixed(1)}%`}
326
- accent
327
- />
328
- </View>
329
-
330
- <View style={styles.progressTrack}>
331
- <View
332
- style={[
333
- styles.progressFill,
334
- {
335
- width: `${Math.min(
336
- 100,
337
- Math.max(
338
- 0,
339
- (toNumber(row.profitUsd2025) / Math.max(1, toNumber(row.budgetProfitUsd2025))) * 100,
340
- ),
341
- )}%`,
342
- },
343
- ]}
344
- />
281
+ <MetricCell label="Budget Delta" value={`${toNumber(row.budgetChangePercent).toFixed(1)}%`} accent />
282
+ <MetricCell label="2024 TEU" value={formatNumber(row.teu2024)} />
283
+ <MetricCell label="2025 TEU" value={formatNumber(row.teu2025)} />
345
284
  </View>
346
285
  </View>
347
286
  ))}
348
287
 
349
288
  {!rows.length ? (
350
289
  <View style={styles.emptyWrap}>
351
- <Text style={styles.emptyText}>No months selected. Please update month filters.</Text>
290
+ <Text style={styles.emptyText}>No months selected. Please update month filter.</Text>
352
291
  </View>
353
292
  ) : null}
293
+
294
+ <View style={styles.footerSpacing} />
354
295
  </ScrollView>
355
296
 
356
297
  <DivisionFilterModal
@@ -363,7 +304,7 @@ const Report2ModernScreen = ({ api, token, onBack }) => {
363
304
 
364
305
  <MonthFilterModal
365
306
  visible={monthsModal}
366
- months={baseRows.map((row) => row.monthLabel)}
307
+ months={monthOptions}
367
308
  selected={selectedMonths}
368
309
  onApply={setSelectedMonths}
369
310
  onClose={() => setMonthsModal(false)}
@@ -546,136 +487,6 @@ const styles = StyleSheet.create({
546
487
  trendTextDown: {
547
488
  color: '#c0393f',
548
489
  },
549
- metricSwitch: {
550
- flexDirection: 'row',
551
- borderRadius: 12,
552
- borderWidth: 1,
553
- borderColor: '#d2deee',
554
- backgroundColor: '#fff',
555
- padding: 4,
556
- marginBottom: 10,
557
- },
558
- metricButton: {
559
- flex: 1,
560
- paddingVertical: 10,
561
- borderRadius: 10,
562
- alignItems: 'center',
563
- },
564
- metricButtonActive: {
565
- backgroundColor: '#174b8f',
566
- },
567
- metricButtonText: {
568
- fontSize: 13,
569
- fontWeight: '700',
570
- color: '#3a4a62',
571
- },
572
- metricButtonTextActive: {
573
- color: '#fff',
574
- },
575
- monthQuickScroll: {
576
- marginBottom: 10,
577
- },
578
- monthChip: {
579
- backgroundColor: '#fff',
580
- borderRadius: 999,
581
- borderWidth: 1,
582
- borderColor: '#d5dfed',
583
- paddingVertical: 7,
584
- paddingHorizontal: 12,
585
- marginRight: 8,
586
- },
587
- monthChipActive: {
588
- backgroundColor: '#133f79',
589
- borderColor: '#133f79',
590
- },
591
- monthChipText: {
592
- fontSize: 12,
593
- fontWeight: '700',
594
- color: '#3f4f64',
595
- },
596
- monthChipTextActive: {
597
- color: '#fff',
598
- },
599
- chartCard: {
600
- backgroundColor: '#fff',
601
- borderRadius: 14,
602
- borderWidth: 1,
603
- borderColor: '#d2deee',
604
- padding: 12,
605
- marginBottom: 14,
606
- },
607
- chartHeader: {
608
- flexDirection: 'row',
609
- justifyContent: 'space-between',
610
- alignItems: 'center',
611
- marginBottom: 8,
612
- },
613
- chartTitle: {
614
- fontSize: 14,
615
- fontWeight: '800',
616
- color: '#142a45',
617
- },
618
- legendRow: {
619
- flexDirection: 'row',
620
- },
621
- legendTextOrange: {
622
- marginRight: 12,
623
- color: '#e57d2f',
624
- fontSize: 12,
625
- fontWeight: '700',
626
- },
627
- legendTextBlue: {
628
- color: '#2e74c4',
629
- fontSize: 12,
630
- fontWeight: '700',
631
- },
632
- chartColumns: {
633
- flexDirection: 'row',
634
- paddingTop: 8,
635
- },
636
- chartGroup: {
637
- alignItems: 'center',
638
- width: 46,
639
- marginRight: 8,
640
- },
641
- barPair: {
642
- flexDirection: 'row',
643
- alignItems: 'flex-end',
644
- marginBottom: 7,
645
- },
646
- barTrack: {
647
- width: 12,
648
- height: 126,
649
- borderRadius: 10,
650
- justifyContent: 'flex-end',
651
- backgroundColor: '#eef3fa',
652
- marginHorizontal: 2,
653
- overflow: 'hidden',
654
- },
655
- barFill2024: {
656
- backgroundColor: '#f19a54',
657
- borderRadius: 10,
658
- },
659
- barFill2025: {
660
- backgroundColor: '#2f7cca',
661
- borderRadius: 10,
662
- },
663
- chartMonthText: {
664
- fontSize: 10,
665
- color: '#4f6078',
666
- fontWeight: '700',
667
- },
668
- chartDelta: {
669
- marginTop: 3,
670
- fontSize: 10,
671
- fontWeight: '700',
672
- },
673
- deltaUp: {
674
- color: '#229060',
675
- },
676
- deltaDown: {
677
- color: '#cb4346',
678
- },
679
490
  sectionTitle: {
680
491
  fontSize: 15,
681
492
  fontWeight: '800',
@@ -724,16 +535,6 @@ const styles = StyleSheet.create({
724
535
  metricValueAccent: {
725
536
  color: '#0f5ba5',
726
537
  },
727
- progressTrack: {
728
- height: 8,
729
- borderRadius: 999,
730
- backgroundColor: '#ecf2fa',
731
- overflow: 'hidden',
732
- },
733
- progressFill: {
734
- height: '100%',
735
- backgroundColor: '#3f8fe0',
736
- },
737
538
  emptyWrap: {
738
539
  paddingVertical: 18,
739
540
  alignItems: 'center',
@@ -742,6 +543,9 @@ const styles = StyleSheet.create({
742
543
  color: '#63748a',
743
544
  fontSize: 13,
744
545
  },
546
+ footerSpacing: {
547
+ height: 10,
548
+ },
745
549
  });
746
550
 
747
551
  export default Report2ModernScreen;