@dhiraj0720/report1chart 1.0.6 → 2.1.1

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,15 +1,55 @@
1
1
  {
2
2
  "name": "@dhiraj0720/report1chart",
3
- "version": "1.0.6",
3
+ "version": "2.1.1",
4
4
  "main": "src/index.jsx",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
5
  "scripts": {
9
- "test": "echo \"Error: no test specified\" && exit 1"
6
+ "build": "echo 'Build step would go here'",
7
+ "test": "jest",
8
+ "lint": "eslint src/",
9
+ "prepare": "npm run build"
10
+ },
11
+ "dependencies": {
12
+ "axios": "^1.6.0",
13
+ "react": ">=16.8.0",
14
+ "react-native": ">=0.60.0",
15
+ "react-native-device-info": "^15.0.1"
16
+ },
17
+ "peerDependencies": {
18
+ "react": ">=16.8.0",
19
+ "react-native": ">=0.60.0"
10
20
  },
11
- "keywords": [],
12
- "author": "",
21
+ "devDependencies": {
22
+ "@babel/core": "^7.23.0",
23
+ "@babel/preset-env": "^7.22.0",
24
+ "@babel/preset-react": "^7.22.0",
25
+ "eslint": "^8.53.0",
26
+ "eslint-plugin-react": "^7.33.2",
27
+ "jest": "^29.7.0"
28
+ },
29
+ "keywords": [
30
+ "reports",
31
+ "charts",
32
+ "tables",
33
+ "analytics",
34
+ "dashboard",
35
+ "react-native",
36
+ "api",
37
+ "data-visualization"
38
+ ],
39
+ "author": "Dhiraj",
13
40
  "license": "MIT",
14
- "description": ""
41
+ "description": "A comprehensive report chart and table package for React Native with built-in API fetching, data transformation, and error handling",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/dhiraj0720/report1chart.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/dhiraj0720/report1chart/issues"
48
+ },
49
+ "homepage": "https://github.com/dhiraj0720/report1chart#readme",
50
+ "files": [
51
+ "src/",
52
+ "README.md",
53
+ "LICENSE"
54
+ ]
15
55
  }
@@ -0,0 +1,180 @@
1
+ import axios from 'axios';
2
+
3
+ class ApiService {
4
+ constructor() {
5
+ this.api = axios.create({
6
+ timeout: 15000,
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ 'Accept': 'application/json'
10
+ }
11
+ });
12
+ }
13
+
14
+ async fetchReportData(apiConfig) {
15
+ try {
16
+ console.log('Fetching from npm package:', apiConfig.url);
17
+
18
+ const config = {
19
+ url: apiConfig.url,
20
+ method: apiConfig.method || 'GET',
21
+ timeout: apiConfig.timeout || 15000,
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ 'Accept': 'application/json',
25
+ ...(apiConfig.token && {
26
+ 'Authorization': `Bearer ${apiConfig.token}`
27
+ })
28
+ }
29
+ };
30
+
31
+ const response = await this.api(config);
32
+
33
+ // Transform data based on endpoint
34
+ return this.transformData(response.data, apiConfig.url);
35
+
36
+ } catch (error) {
37
+ console.error('NPM Package API Error:', error);
38
+ throw this.handleError(error, apiConfig.url);
39
+ }
40
+ }
41
+
42
+ transformData(data, url) {
43
+ // Transform data based on endpoint patterns
44
+ if (url.includes('cumulative-operating-profit')) {
45
+ return this.transformPerformanceData(data);
46
+ } else if (url.includes('revenue-by-mode')) {
47
+ return this.transformRevenueByMode(data);
48
+ } else if (url.includes('shipment-by-direction')) {
49
+ return this.transformShipmentByDirection(data);
50
+ } else if (url.includes('revenue-trend')) {
51
+ return this.transformRevenueTrend(data);
52
+ }
53
+
54
+ // Return as-is for unknown formats
55
+ return data;
56
+ }
57
+
58
+ transformPerformanceData(data) {
59
+ // Transform performance report data to match table structure
60
+ if (!data || !data.data) return [];
61
+
62
+ return data.data.map(item => ({
63
+ name: item.name || item.activity || 'Unknown',
64
+ actual2024: item.actual2024 || item.lastYear || 0,
65
+ actual2025: item.actual2025 || item.currentYear || 0,
66
+ budget2025: item.budget2025 || item.budget || 0,
67
+ yoYChangePercent: this.calculatePercentageChange(
68
+ item.actual2024 || item.lastYear || 0,
69
+ item.actual2025 || item.currentYear || 0
70
+ ),
71
+ budgetVariancePercent: this.calculatePercentageChange(
72
+ item.budget2025 || item.budget || 0,
73
+ item.actual2025 || item.currentYear || 0
74
+ ),
75
+ grossMarginPercent: item.grossMargin || item.margin || 0,
76
+ isTotal: item.isTotal || item.name?.toLowerCase().includes('total')
77
+ }));
78
+ }
79
+
80
+ transformRevenueByMode(data) {
81
+ // Transform revenue by mode data
82
+ if (Array.isArray(data)) {
83
+ return data.map(item => ({
84
+ mode: item.mode || item.transportMode || 'Unknown',
85
+ revenue: item.revenue || item.value || 0
86
+ }));
87
+ }
88
+
89
+ // Handle different response formats
90
+ if (data.data && Array.isArray(data.data)) {
91
+ return data.data;
92
+ }
93
+
94
+ return [];
95
+ }
96
+
97
+ transformShipmentByDirection(data) {
98
+ // Transform shipment by direction data
99
+ if (Array.isArray(data)) {
100
+ return data.map(item => ({
101
+ direction: item.direction || item.type || 'Unknown',
102
+ shipments: item.shipments || item.count || item.value || 0
103
+ }));
104
+ }
105
+
106
+ if (data.data && Array.isArray(data.data)) {
107
+ return data.data;
108
+ }
109
+
110
+ return [];
111
+ }
112
+
113
+ transformRevenueTrend(data) {
114
+ // Transform revenue trend data
115
+ if (data.labels && data.values) {
116
+ return {
117
+ labels: data.labels,
118
+ values: data.values
119
+ };
120
+ }
121
+
122
+ return data;
123
+ }
124
+
125
+ calculatePercentageChange(oldValue, newValue) {
126
+ if (!oldValue || oldValue === 0) return 0;
127
+ return ((newValue - oldValue) / Math.abs(oldValue)) * 100;
128
+ }
129
+
130
+ handleError(error, url) {
131
+ let errorMessage = 'Failed to fetch report data';
132
+
133
+ if (error.response) {
134
+ // Server responded with error
135
+ errorMessage = `Server Error: ${error.response.status} - ${error.response.data?.message || 'Unknown error'}`;
136
+ } else if (error.request) {
137
+ // Request made but no response
138
+ errorMessage = 'Network Error: No response from server';
139
+ } else {
140
+ // Something else happened
141
+ errorMessage = error.message || 'Unknown error occurred';
142
+ }
143
+
144
+ // Fallback mock data for development
145
+ console.warn('Using fallback data for:', url);
146
+ return {
147
+ error: errorMessage,
148
+ fallbackData: this.getFallbackData(url)
149
+ };
150
+ }
151
+
152
+ getFallbackData(url) {
153
+ // Return mock data for development
154
+ if (url.includes('cumulative-operating-profit')) {
155
+ return [
156
+ { name: "Ocean Transportation", actual2024: 45000000, actual2025: 52000000, budget2025: 50000000, grossMarginPercent: 25, isTotal: false },
157
+ { name: "Air Freight", actual2024: 18000000, actual2025: 21000000, budget2025: 20000000, grossMarginPercent: 22, isTotal: false },
158
+ { name: "Road Logistics", actual2024: 32000000, actual2025: 38000000, budget2025: 35000000, grossMarginPercent: 28, isTotal: false },
159
+ { name: "Warehousing", actual2024: 12000000, actual2025: 15000000, budget2025: 14000000, grossMarginPercent: 35, isTotal: false },
160
+ { name: "Customs Clearance", actual2024: 8000000, actual2025: 9500000, budget2025: 9000000, grossMarginPercent: 40, isTotal: false },
161
+ { name: "TOTAL", actual2024: 115000000, actual2025: 135500000, budget2025: 128000000, grossMarginPercent: 28, isTotal: true }
162
+ ];
163
+ } else if (url.includes('revenue-by-mode')) {
164
+ return [
165
+ { mode: "Ocean", revenue: 101951456 },
166
+ { mode: "Road", revenue: 89158437 },
167
+ { mode: "Air", revenue: 40171032 }
168
+ ];
169
+ } else if (url.includes('shipment-by-direction')) {
170
+ return [
171
+ { direction: "Export", shipments: 568 },
172
+ { direction: "Import", shipments: 546 }
173
+ ];
174
+ }
175
+
176
+ return [];
177
+ }
178
+ }
179
+
180
+ export default new ApiService();
@@ -0,0 +1,27 @@
1
+ import ApiService from './ApiService';
2
+
3
+ describe('ApiService', () => {
4
+ it('should transform performance data correctly', () => {
5
+ const mockData = {
6
+ data: [
7
+ { name: 'Ocean', actual2024: 100, actual2025: 120, budget2025: 110, grossMargin: 25 }
8
+ ]
9
+ };
10
+
11
+ const result = ApiService.transformPerformanceData(mockData);
12
+
13
+ expect(result).toHaveLength(1);
14
+ expect(result[0].name).toBe('Ocean');
15
+ expect(result[0].yoYChangePercent).toBe(20);
16
+ expect(result[0].budgetVariancePercent).toBeCloseTo(9.09);
17
+ });
18
+
19
+ it('should handle API errors gracefully', async () => {
20
+ const apiConfig = {
21
+ url: 'http://invalid-url',
22
+ method: 'GET'
23
+ };
24
+
25
+ await expect(ApiService.fetchReportData(apiConfig)).rejects.toThrow();
26
+ });
27
+ });
@@ -0,0 +1,121 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ ScrollView,
6
+ Dimensions,
7
+ StyleSheet,
8
+ } from 'react-native';
9
+
10
+ const BarChart = ({ data, title }) => {
11
+ const formatNumber = (num) => {
12
+ return num.toLocaleString();
13
+ };
14
+
15
+ const getColor = (index) => {
16
+ const colors = ['#4CAF50', '#2196F3', '#FF9800', '#E91E63', '#9C27B0'];
17
+ return colors[index % colors.length];
18
+ };
19
+
20
+ if (!Array.isArray(data) || data.length === 0) {
21
+ return (
22
+ <View style={styles.noDataContainer}>
23
+ <Text style={styles.noDataText}>No data available</Text>
24
+ </View>
25
+ );
26
+ }
27
+
28
+ const values = data.map(d => d.revenue || d.value || d.shipments || 0);
29
+ const labels = data.map(d => d.mode || d.label || d.direction || '');
30
+
31
+ const maxValue = Math.max(...values, 1);
32
+ const chartHeight = 200;
33
+ const chartWidth = Dimensions.get('window').width - 64;
34
+ const barWidth = Math.min(40, (chartWidth / values.length) - 10);
35
+
36
+ return (
37
+ <View style={styles.container}>
38
+ {title && <Text style={styles.title}>{title}</Text>}
39
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
40
+ <View style={[styles.chart, { height: chartHeight, width: chartWidth }]}>
41
+ {values.map((value, index) => {
42
+ const height = (value / maxValue) * chartHeight;
43
+ const label = labels[index] || `Item ${index + 1}`;
44
+
45
+ return (
46
+ <View key={index} style={styles.barWrapper}>
47
+ <View style={[styles.bar, {
48
+ height,
49
+ width: barWidth,
50
+ backgroundColor: getColor(index)
51
+ }]} />
52
+ <Text style={styles.barLabel} numberOfLines={1}>{label}</Text>
53
+ <Text style={styles.valueText}>{formatNumber(value)}</Text>
54
+ </View>
55
+ );
56
+ })}
57
+ </View>
58
+ </ScrollView>
59
+ </View>
60
+ );
61
+ };
62
+
63
+ const styles = StyleSheet.create({
64
+ container: {
65
+ backgroundColor: '#fff',
66
+ borderRadius: 12,
67
+ padding: 16,
68
+ marginBottom: 16,
69
+ borderWidth: 1,
70
+ borderColor: '#e8e8e8',
71
+ shadowColor: '#000',
72
+ shadowOffset: { width: 0, height: 1 },
73
+ shadowOpacity: 0.05,
74
+ shadowRadius: 2,
75
+ elevation: 2,
76
+ },
77
+ title: {
78
+ fontSize: 18,
79
+ fontWeight: '700',
80
+ color: '#000',
81
+ marginBottom: 20,
82
+ textAlign: 'center',
83
+ },
84
+ chart: {
85
+ flexDirection: 'row',
86
+ alignItems: 'flex-end',
87
+ justifyContent: 'space-between',
88
+ paddingHorizontal: 8,
89
+ },
90
+ barWrapper: {
91
+ alignItems: 'center',
92
+ marginHorizontal: 4,
93
+ },
94
+ bar: {
95
+ borderRadius: 4,
96
+ },
97
+ barLabel: {
98
+ marginTop: 8,
99
+ fontSize: 11,
100
+ color: '#666',
101
+ textAlign: 'center',
102
+ width: 60,
103
+ },
104
+ valueText: {
105
+ fontSize: 10,
106
+ color: '#999',
107
+ marginTop: 4,
108
+ fontWeight: '500',
109
+ },
110
+ noDataContainer: {
111
+ padding: 40,
112
+ alignItems: 'center',
113
+ },
114
+ noDataText: {
115
+ fontSize: 16,
116
+ color: '#999',
117
+ textAlign: 'center',
118
+ },
119
+ });
120
+
121
+ export default BarChart;
@@ -0,0 +1,38 @@
1
+ export const formatCurrency = (value) => {
2
+ const num = Number(value);
3
+ if (isNaN(num)) return '0';
4
+
5
+ if (num >= 1000000) {
6
+ return `${(num / 1000000).toFixed(1)}M`;
7
+ } else if (num >= 1000) {
8
+ return `${(num / 1000).toFixed(1)}K`;
9
+ }
10
+ return num.toLocaleString();
11
+ };
12
+
13
+ export const formatNumber = (num) => {
14
+ return num.toLocaleString();
15
+ };
16
+
17
+ export const getColor = (index) => {
18
+ const colors = ['#4CAF50', '#2196F3', '#FF9800', '#E91E63', '#9C27B0', '#00BCD4'];
19
+ return colors[index % colors.length];
20
+ };
21
+
22
+ export const calculatePercentageChange = (oldValue, newValue) => {
23
+ if (!oldValue || oldValue === 0) return 0;
24
+ return ((newValue - oldValue) / Math.abs(oldValue)) * 100;
25
+ };
26
+
27
+ export const getChartDimensions = (dataLength) => {
28
+ const { width: screenWidth } = Dimensions.get('window');
29
+ const minWidth = screenWidth - 64;
30
+ const chartWidth = Math.max(minWidth, dataLength * 60);
31
+ const barWidth = Math.max(20, (chartWidth / dataLength) - 10);
32
+
33
+ return {
34
+ chartHeight: 200,
35
+ chartWidth,
36
+ barWidth
37
+ };
38
+ };
@@ -0,0 +1,142 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ ScrollView,
6
+ Dimensions,
7
+ StyleSheet,
8
+ } from 'react-native';
9
+
10
+ const LineChart = ({ data, title }) => {
11
+ const formatNumber = (num) => {
12
+ return num.toLocaleString();
13
+ };
14
+
15
+ if (!data || (!Array.isArray(data) && !data.values)) {
16
+ return (
17
+ <View style={styles.noDataContainer}>
18
+ <Text style={styles.noDataText}>No data available</Text>
19
+ </View>
20
+ );
21
+ }
22
+
23
+ // Handle different data formats
24
+ let values, labels;
25
+
26
+ if (Array.isArray(data)) {
27
+ // If data is array of objects
28
+ values = data.map(d => d.value || d.revenue || d.shipments || 0);
29
+ labels = data.map(d => d.label || d.month || d.mode || d.direction || '');
30
+ } else {
31
+ // If data has separate values and labels
32
+ values = data.values || [];
33
+ labels = data.labels || [];
34
+ }
35
+
36
+ if (values.length === 0) {
37
+ return (
38
+ <View style={styles.noDataContainer}>
39
+ <Text style={styles.noDataText}>No data available</Text>
40
+ </View>
41
+ );
42
+ }
43
+
44
+ const maxValue = Math.max(...values, 1);
45
+ const chartHeight = 200;
46
+ const chartWidth = Math.max(Dimensions.get('window').width - 64, values.length * 60);
47
+ const barWidth = Math.max(20, (chartWidth / values.length) - 10);
48
+
49
+ return (
50
+ <View style={styles.container}>
51
+ {title && <Text style={styles.title}>{title}</Text>}
52
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
53
+ <View style={[styles.chart, { height: chartHeight, width: chartWidth }]}>
54
+ {values.map((value, index) => {
55
+ const height = (value / maxValue) * chartHeight;
56
+ const label = labels[index] || `M${index + 1}`;
57
+
58
+ return (
59
+ <View key={index} style={styles.pointWrapper}>
60
+ <View style={[styles.point, {
61
+ height: Math.max(height, 2),
62
+ width: barWidth
63
+ }]} />
64
+ <View style={styles.connector} />
65
+ <Text style={styles.pointLabel} numberOfLines={1}>{label}</Text>
66
+ <Text style={styles.valueText}>{formatNumber(value)}</Text>
67
+ </View>
68
+ );
69
+ })}
70
+ </View>
71
+ </ScrollView>
72
+ </View>
73
+ );
74
+ };
75
+
76
+ const styles = StyleSheet.create({
77
+ container: {
78
+ backgroundColor: '#fff',
79
+ borderRadius: 12,
80
+ padding: 16,
81
+ marginBottom: 16,
82
+ borderWidth: 1,
83
+ borderColor: '#e8e8e8',
84
+ shadowColor: '#000',
85
+ shadowOffset: { width: 0, height: 1 },
86
+ shadowOpacity: 0.05,
87
+ shadowRadius: 2,
88
+ elevation: 2,
89
+ },
90
+ title: {
91
+ fontSize: 18,
92
+ fontWeight: '700',
93
+ color: '#000',
94
+ marginBottom: 20,
95
+ textAlign: 'center',
96
+ },
97
+ chart: {
98
+ flexDirection: 'row',
99
+ alignItems: 'flex-end',
100
+ justifyContent: 'space-between',
101
+ paddingHorizontal: 8,
102
+ },
103
+ pointWrapper: {
104
+ alignItems: 'center',
105
+ marginHorizontal: 4,
106
+ },
107
+ point: {
108
+ backgroundColor: '#2196F3',
109
+ borderRadius: 2,
110
+ },
111
+ connector: {
112
+ width: 2,
113
+ backgroundColor: '#2196F3',
114
+ opacity: 0.3,
115
+ marginVertical: 2,
116
+ flex: 1,
117
+ },
118
+ pointLabel: {
119
+ marginTop: 8,
120
+ fontSize: 11,
121
+ color: '#666',
122
+ textAlign: 'center',
123
+ width: 60,
124
+ },
125
+ valueText: {
126
+ fontSize: 10,
127
+ color: '#999',
128
+ marginTop: 4,
129
+ fontWeight: '500',
130
+ },
131
+ noDataContainer: {
132
+ padding: 40,
133
+ alignItems: 'center',
134
+ },
135
+ noDataText: {
136
+ fontSize: 16,
137
+ color: '#999',
138
+ textAlign: 'center',
139
+ },
140
+ });
141
+
142
+ export default LineChart;
@@ -0,0 +1,125 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ } from 'react-native';
7
+
8
+ const PieChart = ({ data, title }) => {
9
+ const formatNumber = (num) => {
10
+ return num.toLocaleString();
11
+ };
12
+
13
+ const getColor = (index) => {
14
+ const colors = ['#4CAF50', '#2196F3', '#FF9800', '#E91E63', '#9C27B0'];
15
+ return colors[index % colors.length];
16
+ };
17
+
18
+ if (!Array.isArray(data) || data.length === 0) {
19
+ return (
20
+ <View style={styles.noDataContainer}>
21
+ <Text style={styles.noDataText}>No data available</Text>
22
+ </View>
23
+ );
24
+ }
25
+
26
+ const total = data.reduce((sum, item) => sum + (item.shipments || item.revenue || item.value || 0), 0);
27
+
28
+ return (
29
+ <View style={styles.container}>
30
+ {title && <Text style={styles.title}>{title}</Text>}
31
+ <View style={styles.pieContainer}>
32
+ {data.map((item, index) => {
33
+ const value = item.shipments || item.revenue || item.value || 0;
34
+ const label = item.direction || item.mode || item.label || `Item ${index + 1}`;
35
+ const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0';
36
+
37
+ return (
38
+ <View key={index} style={styles.pieItem}>
39
+ <View style={[styles.pieColor, { backgroundColor: getColor(index) }]} />
40
+ <Text style={styles.pieLabel}>{label}:</Text>
41
+ <Text style={styles.pieValue}>
42
+ {formatNumber(value)} ({percentage}%)
43
+ </Text>
44
+ </View>
45
+ );
46
+ })}
47
+ </View>
48
+ {total > 0 && (
49
+ <Text style={styles.totalText}>Total: {formatNumber(total)}</Text>
50
+ )}
51
+ </View>
52
+ );
53
+ };
54
+
55
+ const styles = StyleSheet.create({
56
+ container: {
57
+ backgroundColor: '#fff',
58
+ borderRadius: 12,
59
+ padding: 16,
60
+ marginBottom: 16,
61
+ borderWidth: 1,
62
+ borderColor: '#e8e8e8',
63
+ shadowColor: '#000',
64
+ shadowOffset: { width: 0, height: 1 },
65
+ shadowOpacity: 0.05,
66
+ shadowRadius: 2,
67
+ elevation: 2,
68
+ },
69
+ title: {
70
+ fontSize: 18,
71
+ fontWeight: '700',
72
+ color: '#000',
73
+ marginBottom: 20,
74
+ textAlign: 'center',
75
+ },
76
+ pieContainer: {
77
+ padding: 10,
78
+ },
79
+ pieItem: {
80
+ flexDirection: 'row',
81
+ alignItems: 'center',
82
+ marginBottom: 12,
83
+ padding: 8,
84
+ backgroundColor: '#f9f9f9',
85
+ borderRadius: 8,
86
+ },
87
+ pieColor: {
88
+ width: 16,
89
+ height: 16,
90
+ borderRadius: 8,
91
+ marginRight: 12,
92
+ },
93
+ pieLabel: {
94
+ fontSize: 15,
95
+ fontWeight: '600',
96
+ color: '#000',
97
+ flex: 1,
98
+ },
99
+ pieValue: {
100
+ fontSize: 14,
101
+ color: '#666',
102
+ fontWeight: '500',
103
+ },
104
+ totalText: {
105
+ fontSize: 16,
106
+ fontWeight: '700',
107
+ color: '#4CAF50',
108
+ textAlign: 'center',
109
+ marginTop: 16,
110
+ paddingTop: 16,
111
+ borderTopWidth: 1,
112
+ borderTopColor: '#eee',
113
+ },
114
+ noDataContainer: {
115
+ padding: 40,
116
+ alignItems: 'center',
117
+ },
118
+ noDataText: {
119
+ fontSize: 16,
120
+ color: '#999',
121
+ textAlign: 'center',
122
+ },
123
+ });
124
+
125
+ export default PieChart;