@dhiraj0720/report1chart 2.2.0 → 2.2.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 +1 -1
- package/src/api/ApiService.jsx +63 -81
- package/src/components/charts/BarChart.jsx +7 -12
- package/src/components/charts/LineChart.jsx +7 -15
- package/src/components/charts/PieChart.jsx +3 -8
- package/src/components/common/ReportCard.jsx +0 -14
- package/src/components/tables/CompactTable.jsx +18 -93
- package/src/components/tables/DefaultTable.jsx +9 -13
- package/src/components/tables/FreezeTable.jsx +4 -18
- package/src/screens/ReportDashboard.jsx +6 -17
- package/src/screens/ReportDetail.jsx +3 -46
- package/src/api/ApiService.test.jsx +0 -27
- package/src/components/charts/ChartUtils.jsx +0 -25
- package/src/components/tables/TableUtils.jsx +0 -47
package/package.json
CHANGED
package/src/api/ApiService.jsx
CHANGED
|
@@ -13,34 +13,30 @@ class ApiService {
|
|
|
13
13
|
|
|
14
14
|
async fetchReportData(apiConfig) {
|
|
15
15
|
try {
|
|
16
|
-
console.log('Fetching from npm package:', apiConfig.url);
|
|
17
|
-
|
|
18
16
|
const config = {
|
|
19
17
|
url: apiConfig.url,
|
|
20
18
|
method: apiConfig.method || 'GET',
|
|
21
19
|
timeout: apiConfig.timeout || 15000,
|
|
22
20
|
headers: {
|
|
23
21
|
'Content-Type': 'application/json',
|
|
24
|
-
'Accept': 'application/json'
|
|
25
|
-
...(apiConfig.token && {
|
|
26
|
-
'Authorization': `Bearer ${apiConfig.token}`
|
|
27
|
-
})
|
|
22
|
+
'Accept': 'application/json'
|
|
28
23
|
}
|
|
29
24
|
};
|
|
30
25
|
|
|
26
|
+
if (apiConfig.token) {
|
|
27
|
+
config.headers.Authorization = `Bearer ${apiConfig.token}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
31
30
|
const response = await this.api(config);
|
|
32
|
-
|
|
33
|
-
// Transform data based on endpoint
|
|
34
31
|
return this.transformData(response.data, apiConfig.url);
|
|
35
32
|
|
|
36
33
|
} catch (error) {
|
|
37
|
-
console.error('
|
|
38
|
-
|
|
34
|
+
console.error('API Error:', error.message);
|
|
35
|
+
return this.getFallbackData(apiConfig.url);
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
transformData(data, url) {
|
|
43
|
-
// Transform data based on endpoint patterns
|
|
44
40
|
if (url.includes('cumulative-operating-profit')) {
|
|
45
41
|
return this.transformPerformanceData(data);
|
|
46
42
|
} else if (url.includes('revenue-by-mode')) {
|
|
@@ -51,55 +47,44 @@ class ApiService {
|
|
|
51
47
|
return this.transformRevenueTrend(data);
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
// Return as-is for unknown formats
|
|
55
50
|
return data;
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
transformPerformanceData(data) {
|
|
59
|
-
|
|
60
|
-
if (!data || !data.data) return [];
|
|
54
|
+
if (!data || !data.data) return this.getPerformanceFallbackData();
|
|
61
55
|
|
|
62
56
|
return data.data.map(item => ({
|
|
63
|
-
name: item.name ||
|
|
64
|
-
actual2024: item.actual2024 ||
|
|
65
|
-
actual2025: item.actual2025 ||
|
|
66
|
-
budget2025: item.budget2025 ||
|
|
67
|
-
yoYChangePercent: this.calculatePercentageChange(
|
|
68
|
-
|
|
69
|
-
|
|
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')
|
|
57
|
+
name: item.name || 'Unknown',
|
|
58
|
+
actual2024: item.actual2024 || 0,
|
|
59
|
+
actual2025: item.actual2025 || 0,
|
|
60
|
+
budget2025: item.budget2025 || 0,
|
|
61
|
+
yoYChangePercent: this.calculatePercentageChange(item.actual2024 || 0, item.actual2025 || 0),
|
|
62
|
+
budgetVariancePercent: this.calculatePercentageChange(item.budget2025 || 0, item.actual2025 || 0),
|
|
63
|
+
grossMarginPercent: item.grossMarginPercent || 0,
|
|
64
|
+
isTotal: item.isTotal || false
|
|
77
65
|
}));
|
|
78
66
|
}
|
|
79
67
|
|
|
80
68
|
transformRevenueByMode(data) {
|
|
81
|
-
// Transform revenue by mode data
|
|
82
69
|
if (Array.isArray(data)) {
|
|
83
70
|
return data.map(item => ({
|
|
84
|
-
mode: item.mode ||
|
|
85
|
-
revenue: item.revenue ||
|
|
71
|
+
mode: item.mode || 'Unknown',
|
|
72
|
+
revenue: item.revenue || 0
|
|
86
73
|
}));
|
|
87
74
|
}
|
|
88
75
|
|
|
89
|
-
// Handle different response formats
|
|
90
76
|
if (data.data && Array.isArray(data.data)) {
|
|
91
77
|
return data.data;
|
|
92
78
|
}
|
|
93
79
|
|
|
94
|
-
return
|
|
80
|
+
return this.getRevenueByModeFallbackData();
|
|
95
81
|
}
|
|
96
82
|
|
|
97
83
|
transformShipmentByDirection(data) {
|
|
98
|
-
// Transform shipment by direction data
|
|
99
84
|
if (Array.isArray(data)) {
|
|
100
85
|
return data.map(item => ({
|
|
101
|
-
direction: item.direction ||
|
|
102
|
-
shipments: item.shipments ||
|
|
86
|
+
direction: item.direction || 'Unknown',
|
|
87
|
+
shipments: item.shipments || 0
|
|
103
88
|
}));
|
|
104
89
|
}
|
|
105
90
|
|
|
@@ -107,11 +92,10 @@ class ApiService {
|
|
|
107
92
|
return data.data;
|
|
108
93
|
}
|
|
109
94
|
|
|
110
|
-
return
|
|
95
|
+
return this.getShipmentFallbackData();
|
|
111
96
|
}
|
|
112
97
|
|
|
113
98
|
transformRevenueTrend(data) {
|
|
114
|
-
// Transform revenue trend data
|
|
115
99
|
if (data.labels && data.values) {
|
|
116
100
|
return {
|
|
117
101
|
labels: data.labels,
|
|
@@ -119,62 +103,60 @@ class ApiService {
|
|
|
119
103
|
};
|
|
120
104
|
}
|
|
121
105
|
|
|
122
|
-
return
|
|
106
|
+
return this.getRevenueTrendFallbackData();
|
|
123
107
|
}
|
|
124
108
|
|
|
125
109
|
calculatePercentageChange(oldValue, newValue) {
|
|
126
110
|
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
|
-
};
|
|
111
|
+
return Number(((newValue - oldValue) / Math.abs(oldValue)) * 100).toFixed(1);
|
|
150
112
|
}
|
|
151
113
|
|
|
152
114
|
getFallbackData(url) {
|
|
153
|
-
// Return mock data for development
|
|
154
115
|
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
|
-
];
|
|
116
|
+
return this.getPerformanceFallbackData();
|
|
163
117
|
} 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
|
-
];
|
|
118
|
+
return this.getRevenueByModeFallbackData();
|
|
169
119
|
} else if (url.includes('shipment-by-direction')) {
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
];
|
|
120
|
+
return this.getShipmentFallbackData();
|
|
121
|
+
} else if (url.includes('revenue-trend')) {
|
|
122
|
+
return this.getRevenueTrendFallbackData();
|
|
174
123
|
}
|
|
175
124
|
|
|
176
125
|
return [];
|
|
177
126
|
}
|
|
127
|
+
|
|
128
|
+
getPerformanceFallbackData() {
|
|
129
|
+
return [
|
|
130
|
+
{ name: "Ocean Transportation", actual2024: 45000000, actual2025: 52000000, budget2025: 50000000, grossMarginPercent: 25, isTotal: false },
|
|
131
|
+
{ name: "Air Freight", actual2024: 18000000, actual2025: 21000000, budget2025: 20000000, grossMarginPercent: 22, isTotal: false },
|
|
132
|
+
{ name: "Road Logistics", actual2024: 32000000, actual2025: 38000000, budget2025: 35000000, grossMarginPercent: 28, isTotal: false },
|
|
133
|
+
{ name: "Warehousing", actual2024: 12000000, actual2025: 15000000, budget2025: 14000000, grossMarginPercent: 35, isTotal: false },
|
|
134
|
+
{ name: "Customs Clearance", actual2024: 8000000, actual2025: 9500000, budget2025: 9000000, grossMarginPercent: 40, isTotal: false },
|
|
135
|
+
{ name: "TOTAL", actual2024: 115000000, actual2025: 135500000, budget2025: 128000000, grossMarginPercent: 28, isTotal: true }
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
getRevenueByModeFallbackData() {
|
|
140
|
+
return [
|
|
141
|
+
{ mode: "Ocean", revenue: 101951456 },
|
|
142
|
+
{ mode: "Road", revenue: 89158437 },
|
|
143
|
+
{ mode: "Air", revenue: 40171032 }
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getShipmentFallbackData() {
|
|
148
|
+
return [
|
|
149
|
+
{ direction: "Export", shipments: 568 },
|
|
150
|
+
{ direction: "Import", shipments: 546 }
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getRevenueTrendFallbackData() {
|
|
155
|
+
return {
|
|
156
|
+
labels: ["01-2025", "02-2025", "03-2025", "04-2025", "05-2025", "06-2025"],
|
|
157
|
+
values: [16011213, 13368072, 17004674, 19065790, 19978378, 20766951]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
178
160
|
}
|
|
179
161
|
|
|
180
|
-
export default new ApiService();
|
|
162
|
+
export default new ApiService();
|
|
@@ -24,12 +24,11 @@ const BarChart = ({ data, title }) => {
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const values = data.map(d => d.revenue || d.value ||
|
|
28
|
-
const labels = data.map(d => d.mode || d.label ||
|
|
27
|
+
const values = data.map(d => d.revenue || d.value || 0);
|
|
28
|
+
const labels = data.map(d => d.mode || d.label || '');
|
|
29
29
|
|
|
30
30
|
const maxValue = Math.max(...values, 1);
|
|
31
31
|
const chartHeight = 200;
|
|
32
|
-
const barWidth = 40; // Fixed width, no Dimensions needed
|
|
33
32
|
|
|
34
33
|
return (
|
|
35
34
|
<View style={styles.container}>
|
|
@@ -37,7 +36,6 @@ const BarChart = ({ data, title }) => {
|
|
|
37
36
|
<ScrollView
|
|
38
37
|
horizontal
|
|
39
38
|
showsHorizontalScrollIndicator={true}
|
|
40
|
-
contentContainerStyle={styles.scrollContent}
|
|
41
39
|
>
|
|
42
40
|
<View style={[styles.chart, { height: chartHeight }]}>
|
|
43
41
|
{values.map((value, index) => {
|
|
@@ -48,7 +46,6 @@ const BarChart = ({ data, title }) => {
|
|
|
48
46
|
<View key={index} style={styles.barWrapper}>
|
|
49
47
|
<View style={[styles.bar, {
|
|
50
48
|
height,
|
|
51
|
-
width: barWidth,
|
|
52
49
|
backgroundColor: getColor(index)
|
|
53
50
|
}]} />
|
|
54
51
|
<Text style={styles.barLabel} numberOfLines={1}>{label}</Text>
|
|
@@ -78,31 +75,29 @@ const styles = StyleSheet.create({
|
|
|
78
75
|
marginBottom: 20,
|
|
79
76
|
textAlign: 'center',
|
|
80
77
|
},
|
|
81
|
-
scrollContent: {
|
|
82
|
-
paddingHorizontal: 8,
|
|
83
|
-
},
|
|
84
78
|
chart: {
|
|
85
79
|
flexDirection: 'row',
|
|
86
80
|
alignItems: 'flex-end',
|
|
87
|
-
|
|
81
|
+
paddingHorizontal: 8,
|
|
88
82
|
minWidth: 400,
|
|
89
83
|
},
|
|
90
84
|
barWrapper: {
|
|
91
85
|
alignItems: 'center',
|
|
92
|
-
marginHorizontal:
|
|
86
|
+
marginHorizontal: 12,
|
|
93
87
|
},
|
|
94
88
|
bar: {
|
|
89
|
+
width: 40,
|
|
95
90
|
borderRadius: 4,
|
|
96
91
|
},
|
|
97
92
|
barLabel: {
|
|
98
93
|
marginTop: 8,
|
|
99
|
-
fontSize:
|
|
94
|
+
fontSize: 12,
|
|
100
95
|
color: '#666',
|
|
101
96
|
textAlign: 'center',
|
|
102
97
|
width: 60,
|
|
103
98
|
},
|
|
104
99
|
valueText: {
|
|
105
|
-
fontSize:
|
|
100
|
+
fontSize: 11,
|
|
106
101
|
color: '#999',
|
|
107
102
|
marginTop: 4,
|
|
108
103
|
fontWeight: '500',
|
|
@@ -19,15 +19,12 @@ const LineChart = ({ data, title }) => {
|
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// Handle different data formats
|
|
23
22
|
let values, labels;
|
|
24
23
|
|
|
25
24
|
if (Array.isArray(data)) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
labels = data.map(d => d.label || d.month || d.mode || d.direction || '');
|
|
25
|
+
values = data.map(d => d.value || 0);
|
|
26
|
+
labels = data.map(d => d.label || '');
|
|
29
27
|
} else {
|
|
30
|
-
// If data has separate values and labels
|
|
31
28
|
values = data.values || [];
|
|
32
29
|
labels = data.labels || [];
|
|
33
30
|
}
|
|
@@ -42,7 +39,6 @@ const LineChart = ({ data, title }) => {
|
|
|
42
39
|
|
|
43
40
|
const maxValue = Math.max(...values, 1);
|
|
44
41
|
const chartHeight = 200;
|
|
45
|
-
const barWidth = 30; // Fixed width
|
|
46
42
|
|
|
47
43
|
return (
|
|
48
44
|
<View style={styles.container}>
|
|
@@ -50,7 +46,6 @@ const LineChart = ({ data, title }) => {
|
|
|
50
46
|
<ScrollView
|
|
51
47
|
horizontal
|
|
52
48
|
showsHorizontalScrollIndicator={true}
|
|
53
|
-
contentContainerStyle={styles.scrollContent}
|
|
54
49
|
>
|
|
55
50
|
<View style={[styles.chart, { height: chartHeight }]}>
|
|
56
51
|
{values.map((value, index) => {
|
|
@@ -61,7 +56,6 @@ const LineChart = ({ data, title }) => {
|
|
|
61
56
|
<View key={index} style={styles.pointWrapper}>
|
|
62
57
|
<View style={[styles.point, {
|
|
63
58
|
height: Math.max(height, 2),
|
|
64
|
-
width: barWidth
|
|
65
59
|
}]} />
|
|
66
60
|
<View style={styles.connector} />
|
|
67
61
|
<Text style={styles.pointLabel} numberOfLines={1}>{label}</Text>
|
|
@@ -91,20 +85,18 @@ const styles = StyleSheet.create({
|
|
|
91
85
|
marginBottom: 20,
|
|
92
86
|
textAlign: 'center',
|
|
93
87
|
},
|
|
94
|
-
scrollContent: {
|
|
95
|
-
paddingHorizontal: 8,
|
|
96
|
-
},
|
|
97
88
|
chart: {
|
|
98
89
|
flexDirection: 'row',
|
|
99
90
|
alignItems: 'flex-end',
|
|
100
|
-
|
|
91
|
+
paddingHorizontal: 8,
|
|
101
92
|
minWidth: 400,
|
|
102
93
|
},
|
|
103
94
|
pointWrapper: {
|
|
104
95
|
alignItems: 'center',
|
|
105
|
-
marginHorizontal:
|
|
96
|
+
marginHorizontal: 12,
|
|
106
97
|
},
|
|
107
98
|
point: {
|
|
99
|
+
width: 30,
|
|
108
100
|
backgroundColor: '#2196F3',
|
|
109
101
|
borderRadius: 2,
|
|
110
102
|
},
|
|
@@ -117,13 +109,13 @@ const styles = StyleSheet.create({
|
|
|
117
109
|
},
|
|
118
110
|
pointLabel: {
|
|
119
111
|
marginTop: 8,
|
|
120
|
-
fontSize:
|
|
112
|
+
fontSize: 12,
|
|
121
113
|
color: '#666',
|
|
122
114
|
textAlign: 'center',
|
|
123
115
|
width: 60,
|
|
124
116
|
},
|
|
125
117
|
valueText: {
|
|
126
|
-
fontSize:
|
|
118
|
+
fontSize: 11,
|
|
127
119
|
color: '#999',
|
|
128
120
|
marginTop: 4,
|
|
129
121
|
fontWeight: '500',
|
|
@@ -23,15 +23,15 @@ const PieChart = ({ data, title }) => {
|
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const total = data.reduce((sum, item) => sum + (item.shipments || item.
|
|
26
|
+
const total = data.reduce((sum, item) => sum + (item.shipments || item.value || 0), 0);
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<View style={styles.container}>
|
|
30
30
|
{title && <Text style={styles.title}>{title}</Text>}
|
|
31
31
|
<View style={styles.pieContainer}>
|
|
32
32
|
{data.map((item, index) => {
|
|
33
|
-
const value = item.shipments || item.
|
|
34
|
-
const label = item.direction || item.
|
|
33
|
+
const value = item.shipments || item.value || 0;
|
|
34
|
+
const label = item.direction || item.label || `Item ${index + 1}`;
|
|
35
35
|
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0';
|
|
36
36
|
|
|
37
37
|
return (
|
|
@@ -60,11 +60,6 @@ const styles = StyleSheet.create({
|
|
|
60
60
|
marginBottom: 16,
|
|
61
61
|
borderWidth: 1,
|
|
62
62
|
borderColor: '#e8e8e8',
|
|
63
|
-
shadowColor: '#000',
|
|
64
|
-
shadowOffset: { width: 0, height: 1 },
|
|
65
|
-
shadowOpacity: 0.05,
|
|
66
|
-
shadowRadius: 2,
|
|
67
|
-
elevation: 2,
|
|
68
63
|
},
|
|
69
64
|
title: {
|
|
70
65
|
fontSize: 18,
|
|
@@ -24,9 +24,6 @@ const ReportCard = ({ report, onPress }) => {
|
|
|
24
24
|
<Text style={styles.name}>{report.name}</Text>
|
|
25
25
|
<Text style={styles.title}>{report.title}</Text>
|
|
26
26
|
<Text style={styles.desc}>{report.description}</Text>
|
|
27
|
-
<Text style={styles.apiInfo}>
|
|
28
|
-
API: {report.apiConfig?.url ? 'Configured' : 'No API'}
|
|
29
|
-
</Text>
|
|
30
27
|
</View>
|
|
31
28
|
<Text style={styles.arrow}>›</Text>
|
|
32
29
|
</TouchableOpacity>
|
|
@@ -43,11 +40,6 @@ const styles = StyleSheet.create({
|
|
|
43
40
|
marginBottom: 12,
|
|
44
41
|
borderWidth: 1,
|
|
45
42
|
borderColor: '#e8e8e8',
|
|
46
|
-
shadowColor: '#000',
|
|
47
|
-
shadowOffset: { width: 0, height: 1 },
|
|
48
|
-
shadowOpacity: 0.05,
|
|
49
|
-
shadowRadius: 2,
|
|
50
|
-
elevation: 2,
|
|
51
43
|
},
|
|
52
44
|
icon: {
|
|
53
45
|
width: 44,
|
|
@@ -81,12 +73,6 @@ const styles = StyleSheet.create({
|
|
|
81
73
|
fontSize: 13,
|
|
82
74
|
color: '#666',
|
|
83
75
|
lineHeight: 18,
|
|
84
|
-
marginBottom: 4,
|
|
85
|
-
},
|
|
86
|
-
apiInfo: {
|
|
87
|
-
fontSize: 12,
|
|
88
|
-
color: '#888',
|
|
89
|
-
fontStyle: 'italic',
|
|
90
76
|
},
|
|
91
77
|
arrow: {
|
|
92
78
|
fontSize: 24,
|
|
@@ -35,10 +35,8 @@ const CompactTable = ({ data }) => {
|
|
|
35
35
|
style={[
|
|
36
36
|
styles.card,
|
|
37
37
|
item.isTotal && styles.totalCard,
|
|
38
|
-
index % 2 === 0 ? styles.evenCard : styles.oddCard
|
|
39
38
|
]}
|
|
40
39
|
>
|
|
41
|
-
{/* Card Header */}
|
|
42
40
|
<View style={styles.header}>
|
|
43
41
|
<Text style={[
|
|
44
42
|
styles.title,
|
|
@@ -48,79 +46,53 @@ const CompactTable = ({ data }) => {
|
|
|
48
46
|
</Text>
|
|
49
47
|
<Text style={[
|
|
50
48
|
styles.grossMargin,
|
|
51
|
-
styles.grossMarginText
|
|
52
49
|
]}>
|
|
53
|
-
|
|
50
|
+
Gross Margin: {item.grossMarginPercent}%
|
|
54
51
|
</Text>
|
|
55
52
|
</View>
|
|
56
53
|
|
|
57
|
-
{/* Card Body - Grid Layout */}
|
|
58
54
|
<View style={styles.grid}>
|
|
59
|
-
{/* Column 1 */}
|
|
60
55
|
<View style={styles.column}>
|
|
61
|
-
<Text style={styles.label}>2024
|
|
56
|
+
<Text style={styles.label}>2024 Actual</Text>
|
|
62
57
|
<Text style={styles.value}>
|
|
63
58
|
{formatCurrency(item.actual2024)}
|
|
64
59
|
</Text>
|
|
65
60
|
</View>
|
|
66
61
|
|
|
67
|
-
{/* Column 2 */}
|
|
68
62
|
<View style={styles.column}>
|
|
69
|
-
<Text style={styles.label}>2025
|
|
63
|
+
<Text style={styles.label}>2025 Actual</Text>
|
|
70
64
|
<Text style={styles.value}>
|
|
71
65
|
{formatCurrency(item.actual2025)}
|
|
72
66
|
</Text>
|
|
73
67
|
</View>
|
|
74
68
|
|
|
75
|
-
{/* Column 3 */}
|
|
76
69
|
<View style={styles.column}>
|
|
77
|
-
<Text style={styles.label}>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
</Text>
|
|
85
|
-
</View>
|
|
70
|
+
<Text style={styles.label}>YoY Change</Text>
|
|
71
|
+
<Text style={[
|
|
72
|
+
styles.percentText,
|
|
73
|
+
item.yoYChangePercent >= 0 ? styles.positive : styles.negative
|
|
74
|
+
]}>
|
|
75
|
+
{item.yoYChangePercent >= 0 ? '↑' : '↓'} {Math.abs(item.yoYChangePercent)}%
|
|
76
|
+
</Text>
|
|
86
77
|
</View>
|
|
87
78
|
|
|
88
|
-
{/* Column 4 */}
|
|
89
79
|
<View style={styles.column}>
|
|
90
|
-
<Text style={styles.label}>2025
|
|
80
|
+
<Text style={styles.label}>2025 Budget</Text>
|
|
91
81
|
<Text style={styles.value}>
|
|
92
82
|
{formatCurrency(item.budget2025)}
|
|
93
83
|
</Text>
|
|
94
84
|
</View>
|
|
95
85
|
|
|
96
|
-
{/* Column 5 */}
|
|
97
86
|
<View style={styles.column}>
|
|
98
|
-
<Text style={styles.label}>
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
</Text>
|
|
106
|
-
</View>
|
|
87
|
+
<Text style={styles.label}>Variance</Text>
|
|
88
|
+
<Text style={[
|
|
89
|
+
styles.percentText,
|
|
90
|
+
item.budgetVariancePercent >= 0 ? styles.positive : styles.negative
|
|
91
|
+
]}>
|
|
92
|
+
{item.budgetVariancePercent >= 0 ? '↑' : '↓'} {Math.abs(item.budgetVariancePercent)}%
|
|
93
|
+
</Text>
|
|
107
94
|
</View>
|
|
108
95
|
</View>
|
|
109
|
-
|
|
110
|
-
{/* Status Indicator */}
|
|
111
|
-
<View style={styles.statusIndicator}>
|
|
112
|
-
<View style={[
|
|
113
|
-
styles.statusDot,
|
|
114
|
-
(item.yoYChangePercent >= 0 && item.budgetVariancePercent >= 0)
|
|
115
|
-
? styles.statusGood
|
|
116
|
-
: styles.statusWarning
|
|
117
|
-
]} />
|
|
118
|
-
<Text style={styles.statusText}>
|
|
119
|
-
{item.yoYChangePercent >= 0 && item.budgetVariancePercent >= 0
|
|
120
|
-
? 'Performans İyi'
|
|
121
|
-
: 'İnceleme Gerekli'}
|
|
122
|
-
</Text>
|
|
123
|
-
</View>
|
|
124
96
|
</View>
|
|
125
97
|
))}
|
|
126
98
|
</ScrollView>
|
|
@@ -139,23 +111,12 @@ const styles = StyleSheet.create({
|
|
|
139
111
|
padding: 16,
|
|
140
112
|
borderWidth: 1,
|
|
141
113
|
borderColor: '#e8e8e8',
|
|
142
|
-
shadowColor: '#000',
|
|
143
|
-
shadowOffset: { width: 0, height: 2 },
|
|
144
|
-
shadowOpacity: 0.1,
|
|
145
|
-
shadowRadius: 3,
|
|
146
|
-
elevation: 2,
|
|
147
114
|
},
|
|
148
115
|
totalCard: {
|
|
149
116
|
backgroundColor: '#e8f5e9',
|
|
150
117
|
borderColor: '#4CAF50',
|
|
151
118
|
borderWidth: 2,
|
|
152
119
|
},
|
|
153
|
-
evenCard: {
|
|
154
|
-
backgroundColor: '#fff',
|
|
155
|
-
},
|
|
156
|
-
oddCard: {
|
|
157
|
-
backgroundColor: '#f9f9f9',
|
|
158
|
-
},
|
|
159
120
|
header: {
|
|
160
121
|
flexDirection: 'row',
|
|
161
122
|
justifyContent: 'space-between',
|
|
@@ -183,8 +144,6 @@ const styles = StyleSheet.create({
|
|
|
183
144
|
paddingVertical: 4,
|
|
184
145
|
borderRadius: 6,
|
|
185
146
|
backgroundColor: '#f0f0f0',
|
|
186
|
-
},
|
|
187
|
-
grossMarginText: {
|
|
188
147
|
color: '#2c3e50',
|
|
189
148
|
},
|
|
190
149
|
grid: {
|
|
@@ -210,20 +169,10 @@ const styles = StyleSheet.create({
|
|
|
210
169
|
fontSize: 13,
|
|
211
170
|
fontWeight: '600',
|
|
212
171
|
color: '#2c3e50',
|
|
213
|
-
fontFamily: 'monospace',
|
|
214
|
-
},
|
|
215
|
-
percentBadge: {
|
|
216
|
-
backgroundColor: '#fff',
|
|
217
|
-
paddingHorizontal: 10,
|
|
218
|
-
paddingVertical: 4,
|
|
219
|
-
borderRadius: 12,
|
|
220
|
-
borderWidth: 1,
|
|
221
|
-
borderColor: '#e0e0e0',
|
|
222
172
|
},
|
|
223
173
|
percentText: {
|
|
224
174
|
fontSize: 12,
|
|
225
175
|
fontWeight: '600',
|
|
226
|
-
fontFamily: 'monospace',
|
|
227
176
|
},
|
|
228
177
|
positive: {
|
|
229
178
|
color: '#27ae60',
|
|
@@ -231,30 +180,6 @@ const styles = StyleSheet.create({
|
|
|
231
180
|
negative: {
|
|
232
181
|
color: '#e74c3c',
|
|
233
182
|
},
|
|
234
|
-
statusIndicator: {
|
|
235
|
-
flexDirection: 'row',
|
|
236
|
-
alignItems: 'center',
|
|
237
|
-
marginTop: 12,
|
|
238
|
-
paddingTop: 12,
|
|
239
|
-
borderTopWidth: 1,
|
|
240
|
-
borderTopColor: '#eee',
|
|
241
|
-
},
|
|
242
|
-
statusDot: {
|
|
243
|
-
width: 8,
|
|
244
|
-
height: 8,
|
|
245
|
-
borderRadius: 4,
|
|
246
|
-
marginRight: 8,
|
|
247
|
-
},
|
|
248
|
-
statusGood: {
|
|
249
|
-
backgroundColor: '#4CAF50',
|
|
250
|
-
},
|
|
251
|
-
statusWarning: {
|
|
252
|
-
backgroundColor: '#FF9800',
|
|
253
|
-
},
|
|
254
|
-
statusText: {
|
|
255
|
-
fontSize: 12,
|
|
256
|
-
color: '#666',
|
|
257
|
-
},
|
|
258
183
|
noDataContainer: {
|
|
259
184
|
padding: 40,
|
|
260
185
|
alignItems: 'center',
|
|
@@ -30,32 +30,30 @@ const DefaultTable = ({ data }) => {
|
|
|
30
30
|
return (
|
|
31
31
|
<ScrollView horizontal showsHorizontalScrollIndicator={true}>
|
|
32
32
|
<View style={styles.table}>
|
|
33
|
-
{/* Table Header */}
|
|
34
33
|
<View style={styles.tableHeader}>
|
|
35
34
|
<View style={[styles.headerCell, styles.firstColumn]}>
|
|
36
|
-
<Text style={styles.headerText}>
|
|
35
|
+
<Text style={styles.headerText}>ACTIVITY PROFIT/LOSS</Text>
|
|
37
36
|
</View>
|
|
38
37
|
<View style={styles.headerCell}>
|
|
39
|
-
<Text style={styles.headerText}>2024
|
|
38
|
+
<Text style={styles.headerText}>2024 Actual</Text>
|
|
40
39
|
</View>
|
|
41
40
|
<View style={styles.headerCell}>
|
|
42
|
-
<Text style={styles.headerText}>2025
|
|
41
|
+
<Text style={styles.headerText}>2025 Actual</Text>
|
|
43
42
|
</View>
|
|
44
43
|
<View style={styles.headerCell}>
|
|
45
|
-
<Text style={styles.headerText}>
|
|
44
|
+
<Text style={styles.headerText}>YoY Change %</Text>
|
|
46
45
|
</View>
|
|
47
46
|
<View style={styles.headerCell}>
|
|
48
|
-
<Text style={styles.headerText}>2025
|
|
47
|
+
<Text style={styles.headerText}>2025 Budget</Text>
|
|
49
48
|
</View>
|
|
50
49
|
<View style={styles.headerCell}>
|
|
51
|
-
<Text style={styles.headerText}>
|
|
50
|
+
<Text style={styles.headerText}>Variance %</Text>
|
|
52
51
|
</View>
|
|
53
52
|
<View style={styles.headerCell}>
|
|
54
|
-
<Text style={styles.headerText}>
|
|
53
|
+
<Text style={styles.headerText}>GROSS MARGIN %</Text>
|
|
55
54
|
</View>
|
|
56
55
|
</View>
|
|
57
56
|
|
|
58
|
-
{/* Table Rows */}
|
|
59
57
|
{data.map((item, index) => (
|
|
60
58
|
<View
|
|
61
59
|
key={index}
|
|
@@ -87,7 +85,7 @@ const DefaultTable = ({ data }) => {
|
|
|
87
85
|
styles.percentText,
|
|
88
86
|
item.yoYChangePercent >= 0 ? styles.positive : styles.negative
|
|
89
87
|
]}>
|
|
90
|
-
{item.yoYChangePercent >= 0 ? '↑' : '↓'} {Math.abs(item.yoYChangePercent)
|
|
88
|
+
{item.yoYChangePercent >= 0 ? '↑' : '↓'} {Math.abs(item.yoYChangePercent)}%
|
|
91
89
|
</Text>
|
|
92
90
|
</View>
|
|
93
91
|
|
|
@@ -102,7 +100,7 @@ const DefaultTable = ({ data }) => {
|
|
|
102
100
|
styles.percentText,
|
|
103
101
|
item.budgetVariancePercent >= 0 ? styles.positive : styles.negative
|
|
104
102
|
]}>
|
|
105
|
-
{item.budgetVariancePercent >= 0 ? '↑' : '↓'} {Math.abs(item.budgetVariancePercent)
|
|
103
|
+
{item.budgetVariancePercent >= 0 ? '↑' : '↓'} {Math.abs(item.budgetVariancePercent)}%
|
|
106
104
|
</Text>
|
|
107
105
|
</View>
|
|
108
106
|
|
|
@@ -193,13 +191,11 @@ const styles = StyleSheet.create({
|
|
|
193
191
|
numberText: {
|
|
194
192
|
fontSize: 11,
|
|
195
193
|
fontWeight: '500',
|
|
196
|
-
fontFamily: 'monospace',
|
|
197
194
|
color: '#333',
|
|
198
195
|
},
|
|
199
196
|
percentText: {
|
|
200
197
|
fontSize: 12,
|
|
201
198
|
fontWeight: '600',
|
|
202
|
-
fontFamily: 'monospace',
|
|
203
199
|
},
|
|
204
200
|
positive: {
|
|
205
201
|
color: '#27ae60',
|
|
@@ -29,10 +29,9 @@ const FreezeTable = ({ data }) => {
|
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
31
|
<View style={styles.container}>
|
|
32
|
-
{/* Fixed First Column */}
|
|
33
32
|
<View style={styles.fixedColumn}>
|
|
34
33
|
<View style={[styles.fixedHeader, styles.fixedFirstCell]}>
|
|
35
|
-
<Text style={styles.fixedHeaderText}>
|
|
34
|
+
<Text style={styles.fixedHeaderText}>ACTIVITY</Text>
|
|
36
35
|
</View>
|
|
37
36
|
{data.map((item, index) => (
|
|
38
37
|
<View
|
|
@@ -40,7 +39,6 @@ const FreezeTable = ({ data }) => {
|
|
|
40
39
|
style={[
|
|
41
40
|
styles.fixedRow,
|
|
42
41
|
item.isTotal && styles.fixedTotalRow,
|
|
43
|
-
index % 2 === 0 ? styles.evenRow : styles.oddRow
|
|
44
42
|
]}
|
|
45
43
|
>
|
|
46
44
|
<Text style={[
|
|
@@ -53,26 +51,22 @@ const FreezeTable = ({ data }) => {
|
|
|
53
51
|
))}
|
|
54
52
|
</View>
|
|
55
53
|
|
|
56
|
-
{/* Scrollable Data Columns */}
|
|
57
54
|
<ScrollView horizontal showsHorizontalScrollIndicator={true}>
|
|
58
55
|
<View style={styles.scrollableColumns}>
|
|
59
|
-
{/* Headers for scrollable columns */}
|
|
60
56
|
<View style={styles.scrollableHeaderRow}>
|
|
61
|
-
{['2024
|
|
57
|
+
{['2024 Actual', '2025 Actual', 'YoY Change %', '2025 Budget', 'Variance %', 'GROSS MARGIN %'].map((header, idx) => (
|
|
62
58
|
<View key={idx} style={styles.scrollableHeader}>
|
|
63
59
|
<Text style={styles.scrollableHeaderText}>{header}</Text>
|
|
64
60
|
</View>
|
|
65
61
|
))}
|
|
66
62
|
</View>
|
|
67
63
|
|
|
68
|
-
{/* Data rows for scrollable columns */}
|
|
69
64
|
{data.map((item, rowIndex) => (
|
|
70
65
|
<View
|
|
71
66
|
key={rowIndex}
|
|
72
67
|
style={[
|
|
73
68
|
styles.scrollableDataRow,
|
|
74
69
|
item.isTotal && styles.scrollableTotalDataRow,
|
|
75
|
-
rowIndex % 2 === 0 ? styles.evenRow : styles.oddRow
|
|
76
70
|
]}
|
|
77
71
|
>
|
|
78
72
|
<View style={styles.scrollableDataCell}>
|
|
@@ -92,7 +86,7 @@ const FreezeTable = ({ data }) => {
|
|
|
92
86
|
styles.scrollablePercent,
|
|
93
87
|
item.yoYChangePercent >= 0 ? styles.positive : styles.negative
|
|
94
88
|
]}>
|
|
95
|
-
{item.yoYChangePercent >= 0 ? '↑' : '↓'} {Math.abs(item.yoYChangePercent)
|
|
89
|
+
{item.yoYChangePercent >= 0 ? '↑' : '↓'} {Math.abs(item.yoYChangePercent)}%
|
|
96
90
|
</Text>
|
|
97
91
|
</View>
|
|
98
92
|
|
|
@@ -107,7 +101,7 @@ const FreezeTable = ({ data }) => {
|
|
|
107
101
|
styles.scrollablePercent,
|
|
108
102
|
item.budgetVariancePercent >= 0 ? styles.positive : styles.negative
|
|
109
103
|
]}>
|
|
110
|
-
{item.budgetVariancePercent >= 0 ? '↑' : '↓'} {Math.abs(item.budgetVariancePercent)
|
|
104
|
+
{item.budgetVariancePercent >= 0 ? '↑' : '↓'} {Math.abs(item.budgetVariancePercent)}%
|
|
111
105
|
</Text>
|
|
112
106
|
</View>
|
|
113
107
|
|
|
@@ -226,13 +220,11 @@ const styles = StyleSheet.create({
|
|
|
226
220
|
scrollableDataText: {
|
|
227
221
|
fontSize: 12,
|
|
228
222
|
fontWeight: '500',
|
|
229
|
-
fontFamily: 'monospace',
|
|
230
223
|
color: '#333',
|
|
231
224
|
},
|
|
232
225
|
scrollablePercent: {
|
|
233
226
|
fontSize: 12,
|
|
234
227
|
fontWeight: '600',
|
|
235
|
-
fontFamily: 'monospace',
|
|
236
228
|
},
|
|
237
229
|
positive: {
|
|
238
230
|
color: '#27ae60',
|
|
@@ -244,12 +236,6 @@ const styles = StyleSheet.create({
|
|
|
244
236
|
color: '#2c3e50',
|
|
245
237
|
fontWeight: '700',
|
|
246
238
|
},
|
|
247
|
-
evenRow: {
|
|
248
|
-
backgroundColor: '#fff',
|
|
249
|
-
},
|
|
250
|
-
oddRow: {
|
|
251
|
-
backgroundColor: '#f9f9f9',
|
|
252
|
-
},
|
|
253
239
|
noDataContainer: {
|
|
254
240
|
padding: 40,
|
|
255
241
|
alignItems: 'center',
|
|
@@ -20,30 +20,19 @@ const ReportDashboard = ({ reports, onReportSelect, onClose }) => {
|
|
|
20
20
|
setError(null);
|
|
21
21
|
|
|
22
22
|
try {
|
|
23
|
-
// Fetch data from API using npm package service
|
|
24
23
|
const data = await ApiService.fetchReportData(report.apiConfig);
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
setSelectedReport({
|
|
31
|
-
...report,
|
|
32
|
-
data: data.fallbackData
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
setSelectedReport({
|
|
37
|
-
...report,
|
|
38
|
-
data: data
|
|
39
|
-
});
|
|
40
|
-
}
|
|
25
|
+
setSelectedReport({
|
|
26
|
+
...report,
|
|
27
|
+
data: data
|
|
28
|
+
});
|
|
41
29
|
|
|
42
30
|
if (onReportSelect) {
|
|
43
31
|
onReportSelect(report.name);
|
|
44
32
|
}
|
|
45
33
|
} catch (err) {
|
|
46
|
-
setError(
|
|
34
|
+
setError('Failed to fetch data');
|
|
35
|
+
console.error(err);
|
|
47
36
|
} finally {
|
|
48
37
|
setLoading(false);
|
|
49
38
|
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
StyleSheet,
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
ScrollView,
|
|
8
|
-
ActivityIndicator,
|
|
9
8
|
} from 'react-native';
|
|
10
9
|
import DefaultTable from '../components/tables/DefaultTable';
|
|
11
10
|
import CompactTable from '../components/tables/CompactTable';
|
|
@@ -36,7 +35,6 @@ const ReportDetail = ({ report, loading, error, onBack, onClose }) => {
|
|
|
36
35
|
);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
// Render based on report type
|
|
40
38
|
switch (report.type) {
|
|
41
39
|
case 'table':
|
|
42
40
|
return renderTable();
|
|
@@ -57,7 +55,6 @@ const ReportDetail = ({ report, loading, error, onBack, onClose }) => {
|
|
|
57
55
|
return <CompactTable data={report.data} />;
|
|
58
56
|
case 'freeze':
|
|
59
57
|
return <FreezeTable data={report.data} />;
|
|
60
|
-
case 'default':
|
|
61
58
|
default:
|
|
62
59
|
return <DefaultTable data={report.data} />;
|
|
63
60
|
}
|
|
@@ -93,7 +90,7 @@ const ReportDetail = ({ report, loading, error, onBack, onClose }) => {
|
|
|
93
90
|
<Text style={[
|
|
94
91
|
styles.viewTypeText,
|
|
95
92
|
viewType === 'default' && styles.activeViewTypeText
|
|
96
|
-
]}>
|
|
93
|
+
]}>Default</Text>
|
|
97
94
|
</TouchableOpacity>
|
|
98
95
|
|
|
99
96
|
<TouchableOpacity
|
|
@@ -106,7 +103,7 @@ const ReportDetail = ({ report, loading, error, onBack, onClose }) => {
|
|
|
106
103
|
<Text style={[
|
|
107
104
|
styles.viewTypeText,
|
|
108
105
|
viewType === 'compact' && styles.activeViewTypeText
|
|
109
|
-
]}>
|
|
106
|
+
]}>Compact</Text>
|
|
110
107
|
</TouchableOpacity>
|
|
111
108
|
|
|
112
109
|
<TouchableOpacity
|
|
@@ -119,25 +116,13 @@ const ReportDetail = ({ report, loading, error, onBack, onClose }) => {
|
|
|
119
116
|
<Text style={[
|
|
120
117
|
styles.viewTypeText,
|
|
121
118
|
viewType === 'freeze' && styles.activeViewTypeText
|
|
122
|
-
]}>
|
|
119
|
+
]}>Freeze</Text>
|
|
123
120
|
</TouchableOpacity>
|
|
124
121
|
</View>
|
|
125
122
|
)}
|
|
126
123
|
|
|
127
124
|
<ScrollView style={styles.content}>
|
|
128
125
|
{renderContent()}
|
|
129
|
-
|
|
130
|
-
{report.data && (
|
|
131
|
-
<View style={styles.dataInfo}>
|
|
132
|
-
<Text style={styles.infoTitle}>Report Information</Text>
|
|
133
|
-
<Text style={styles.infoText}>
|
|
134
|
-
{report.description || 'Detailed analysis report'}
|
|
135
|
-
</Text>
|
|
136
|
-
<Text style={styles.dataSource}>
|
|
137
|
-
Data points: {Array.isArray(report.data) ? report.data.length : 'N/A'}
|
|
138
|
-
</Text>
|
|
139
|
-
</View>
|
|
140
|
-
)}
|
|
141
126
|
</ScrollView>
|
|
142
127
|
</View>
|
|
143
128
|
);
|
|
@@ -213,11 +198,6 @@ const styles = StyleSheet.create({
|
|
|
213
198
|
},
|
|
214
199
|
activeViewType: {
|
|
215
200
|
backgroundColor: '#fff',
|
|
216
|
-
shadowColor: '#000',
|
|
217
|
-
shadowOffset: { width: 0, height: 1 },
|
|
218
|
-
shadowOpacity: 0.1,
|
|
219
|
-
shadowRadius: 2,
|
|
220
|
-
elevation: 2,
|
|
221
201
|
},
|
|
222
202
|
viewTypeText: {
|
|
223
203
|
fontSize: 13,
|
|
@@ -228,29 +208,6 @@ const styles = StyleSheet.create({
|
|
|
228
208
|
color: '#4CAF50',
|
|
229
209
|
fontWeight: '600',
|
|
230
210
|
},
|
|
231
|
-
dataInfo: {
|
|
232
|
-
backgroundColor: '#e8f5e9',
|
|
233
|
-
borderRadius: 12,
|
|
234
|
-
padding: 16,
|
|
235
|
-
marginTop: 16,
|
|
236
|
-
},
|
|
237
|
-
infoTitle: {
|
|
238
|
-
fontSize: 16,
|
|
239
|
-
fontWeight: '700',
|
|
240
|
-
color: '#2e7d32',
|
|
241
|
-
marginBottom: 8,
|
|
242
|
-
},
|
|
243
|
-
infoText: {
|
|
244
|
-
fontSize: 14,
|
|
245
|
-
color: '#555',
|
|
246
|
-
lineHeight: 20,
|
|
247
|
-
marginBottom: 8,
|
|
248
|
-
},
|
|
249
|
-
dataSource: {
|
|
250
|
-
fontSize: 13,
|
|
251
|
-
color: '#777',
|
|
252
|
-
fontStyle: 'italic',
|
|
253
|
-
},
|
|
254
211
|
noDataContainer: {
|
|
255
212
|
padding: 40,
|
|
256
213
|
alignItems: 'center',
|
|
@@ -1,27 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Dimensions } from 'react-native';
|
|
2
|
-
|
|
3
|
-
export const formatCurrency = (value) => {
|
|
4
|
-
const num = Number(value);
|
|
5
|
-
if (isNaN(num)) return '0';
|
|
6
|
-
|
|
7
|
-
if (num >= 1000000) {
|
|
8
|
-
return `${(num / 1000000).toFixed(1)}M`;
|
|
9
|
-
} else if (num >= 1000) {
|
|
10
|
-
return `${(num / 1000).toFixed(1)}K`;
|
|
11
|
-
}
|
|
12
|
-
return num.toLocaleString();
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const formatNumber = (num) => {
|
|
16
|
-
return num.toLocaleString();
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const calculatePercentageChange = (oldValue, newValue) => {
|
|
20
|
-
if (!oldValue || oldValue === 0) return 0;
|
|
21
|
-
return ((newValue - oldValue) / Math.abs(oldValue)) * 100;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const getTableWidth = (columns) => {
|
|
25
|
-
const { width: screenWidth } = Dimensions.get('window');
|
|
26
|
-
return Math.max(screenWidth, columns * 120);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const getStatusColor = (yoYChange, budgetVariance) => {
|
|
30
|
-
if (yoYChange >= 0 && budgetVariance >= 0) {
|
|
31
|
-
return '#4CAF50'; // Good
|
|
32
|
-
} else if (yoYChange >= 0 || budgetVariance >= 0) {
|
|
33
|
-
return '#FF9800'; // Warning
|
|
34
|
-
} else {
|
|
35
|
-
return '#F44336'; // Critical
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const getStatusText = (yoYChange, budgetVariance) => {
|
|
40
|
-
if (yoYChange >= 0 && budgetVariance >= 0) {
|
|
41
|
-
return 'Performans İyi';
|
|
42
|
-
} else if (yoYChange >= 0 || budgetVariance >= 0) {
|
|
43
|
-
return 'Kısmen İyi';
|
|
44
|
-
} else {
|
|
45
|
-
return 'İnceleme Gerekli';
|
|
46
|
-
}
|
|
47
|
-
};
|