@dhiraj0720/report1chart 2.5.8 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/SvgBarLineChartCompact.jsx +105 -58
- package/src/components/SvgBarLineChartCompact3A.jsx +144 -0
- package/src/components/SvgLineChartCompact3A.jsx +115 -0
- package/src/index.jsx +9 -0
- package/src/screens/Report2AScreen.jsx +156 -33
- package/src/screens/Report3AScreen.jsx +167 -0
package/package.json
CHANGED
|
@@ -6,125 +6,161 @@ import { formatNumber } from '../utils/formatNumber';
|
|
|
6
6
|
const SvgBarLineChartCompact = ({ data }) => {
|
|
7
7
|
if (!data?.series || !data.labels?.length) return null;
|
|
8
8
|
|
|
9
|
-
const height =
|
|
10
|
-
const paddingLeft =
|
|
11
|
-
const paddingRight =
|
|
9
|
+
const height = 260;
|
|
10
|
+
const paddingLeft = 70;
|
|
11
|
+
const paddingRight = 30;
|
|
12
12
|
const paddingTop = 20;
|
|
13
|
-
const paddingBottom =
|
|
13
|
+
const paddingBottom = 60;
|
|
14
14
|
|
|
15
15
|
const chartHeight = height - paddingTop - paddingBottom;
|
|
16
|
-
const graphWidth = Math.max(
|
|
16
|
+
const graphWidth = Math.max(500, data.labels.length * 100); // Wider spacing like image
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
18
|
+
// Max value slightly above the highest in image
|
|
19
|
+
const MAX = 1279958;
|
|
20
|
+
const barWidth = 20;
|
|
21
|
+
const barGap = 6; // Gap between 2024 and 2025 bar
|
|
22
|
+
const groupGap = 30; // Gap between months (matches image)
|
|
22
23
|
|
|
23
24
|
const y = (v) => paddingTop + ((MAX - v) / MAX) * chartHeight;
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
// Center X for each month group
|
|
27
|
+
const getGroupCenterX = (i) => paddingLeft + i * (barWidth * 2 + barGap + groupGap) + (barWidth + barGap / 2);
|
|
26
28
|
|
|
27
29
|
return (
|
|
28
30
|
<View style={styles.container}>
|
|
29
|
-
<Text style={styles.title}>{data.title}</Text>
|
|
31
|
+
<Text style={styles.title}>{data.title || 'AY BAZINDA KAR KARŞILAŞTIRMALARI'}</Text>
|
|
30
32
|
|
|
31
33
|
<View style={{ flexDirection: 'row' }}>
|
|
34
|
+
{/* Y Axis Labels */}
|
|
32
35
|
<Svg width={paddingLeft} height={height}>
|
|
33
|
-
{[1000000, 500000, 0].map((v) => (
|
|
36
|
+
{[1279958, 1000000, 500000, 0].map((v, i) => (
|
|
34
37
|
<SvgText
|
|
35
|
-
key={
|
|
36
|
-
x={paddingLeft -
|
|
38
|
+
key={i}
|
|
39
|
+
x={paddingLeft - 10}
|
|
37
40
|
y={y(v) + 5}
|
|
38
41
|
fontSize="11"
|
|
39
42
|
textAnchor="end"
|
|
40
43
|
fill="#444"
|
|
41
44
|
>
|
|
42
|
-
{(v / 1000000).toFixed(1)}M
|
|
45
|
+
{v >= 1000000 ? `$${ (v / 1000000).toFixed(1) }M` : formatNumber(v)}
|
|
43
46
|
</SvgText>
|
|
44
47
|
))}
|
|
45
48
|
</Svg>
|
|
46
49
|
|
|
50
|
+
{/* Main Chart - Scrollable */}
|
|
47
51
|
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
48
52
|
<Svg width={graphWidth} height={height}>
|
|
49
|
-
{/* Grid */}
|
|
50
|
-
{[1000000, 500000, 0].map((v) => (
|
|
51
|
-
<Line
|
|
53
|
+
{/* Horizontal Grid Lines */}
|
|
54
|
+
{[1279958, 1000000, 500000, 0].map((v) => (
|
|
55
|
+
<Line
|
|
56
|
+
key={v}
|
|
57
|
+
x1={0}
|
|
58
|
+
x2={graphWidth}
|
|
59
|
+
y1={y(v)}
|
|
60
|
+
y2={y(v)}
|
|
61
|
+
stroke="#eee"
|
|
62
|
+
strokeDasharray="5,5"
|
|
63
|
+
/>
|
|
52
64
|
))}
|
|
53
65
|
|
|
66
|
+
{/* Bars & Budget Line */}
|
|
54
67
|
{data.labels.map((label, i) => {
|
|
55
|
-
const
|
|
56
|
-
const bar2024 = data.series[0].data[i] || 0;
|
|
57
|
-
const bar2025 = data.series[1].data[i] || 0;
|
|
58
|
-
const budget = data.series[2].data[i] || 0;
|
|
68
|
+
const centerX = getGroupCenterX(i);
|
|
59
69
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
70
|
+
const val2024 = data.series[0]?.data[i] || 0;
|
|
71
|
+
const val2025 = data.series[1]?.data[i] || 0;
|
|
72
|
+
const budget = data.series[2]?.data[i] || 0;
|
|
73
|
+
|
|
74
|
+
const barHeight2024 = chartHeight - (y(val2024) - paddingTop);
|
|
75
|
+
const barHeight2025 = chartHeight - (y(val2025) - paddingTop);
|
|
62
76
|
|
|
63
77
|
return (
|
|
64
78
|
<React.Fragment key={i}>
|
|
65
|
-
{/* 2024 Bar */}
|
|
79
|
+
{/* 2024 Bar - Orange */}
|
|
66
80
|
<Rect
|
|
67
|
-
x={
|
|
68
|
-
y={y(
|
|
81
|
+
x={centerX - barWidth - barGap / 2}
|
|
82
|
+
y={y(val2024)}
|
|
69
83
|
width={barWidth}
|
|
70
|
-
height={
|
|
84
|
+
height={barHeight2024}
|
|
71
85
|
fill="#E07A3F"
|
|
72
86
|
/>
|
|
73
|
-
{
|
|
87
|
+
{val2024 > MAX * 0.08 && (
|
|
74
88
|
<SvgText
|
|
75
|
-
x={
|
|
76
|
-
y={y(
|
|
77
|
-
fontSize="
|
|
89
|
+
x={centerX - barWidth - barGap / 2 + barWidth / 2}
|
|
90
|
+
y={y(val2024) + barHeight2024 / 2 + 4}
|
|
91
|
+
fontSize="10"
|
|
78
92
|
textAnchor="middle"
|
|
79
93
|
fill="white"
|
|
80
94
|
fontWeight="700"
|
|
81
95
|
>
|
|
82
|
-
{formatNumber(
|
|
96
|
+
${formatNumber(val2024)}
|
|
83
97
|
</SvgText>
|
|
84
98
|
)}
|
|
85
99
|
|
|
86
|
-
{/* 2025 Bar */}
|
|
100
|
+
{/* 2025 Bar - Blue */}
|
|
87
101
|
<Rect
|
|
88
|
-
x={
|
|
89
|
-
y={y(
|
|
102
|
+
x={centerX + barGap / 2}
|
|
103
|
+
y={y(val2025)}
|
|
90
104
|
width={barWidth}
|
|
91
|
-
height={
|
|
105
|
+
height={barHeight2025}
|
|
92
106
|
fill="#4E79A7"
|
|
93
107
|
/>
|
|
94
|
-
{
|
|
108
|
+
{val2025 > MAX * 0.08 && (
|
|
95
109
|
<SvgText
|
|
96
|
-
x={
|
|
97
|
-
y={y(
|
|
98
|
-
fontSize="
|
|
110
|
+
x={centerX + barGap / 2 + barWidth / 2}
|
|
111
|
+
y={y(val2025) + barHeight2025 / 2 + 4}
|
|
112
|
+
fontSize="10"
|
|
99
113
|
textAnchor="middle"
|
|
100
114
|
fill="white"
|
|
101
115
|
fontWeight="700"
|
|
102
116
|
>
|
|
103
|
-
{formatNumber(
|
|
117
|
+
${formatNumber(val2025)}
|
|
104
118
|
</SvgText>
|
|
105
119
|
)}
|
|
106
120
|
|
|
107
|
-
{/* Budget Line
|
|
108
|
-
|
|
121
|
+
{/* Budget Dotted Line */}
|
|
122
|
+
{i > 0 && (
|
|
123
|
+
<Line
|
|
124
|
+
x1={getGroupCenterX(i - 1)}
|
|
125
|
+
y1={y(data.series[2].data[i - 1])}
|
|
126
|
+
x2={centerX}
|
|
127
|
+
y2={y(budget)}
|
|
128
|
+
stroke="#8AB6E8"
|
|
129
|
+
strokeWidth={2}
|
|
130
|
+
strokeDasharray="6,4"
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{/* Budget Dot */}
|
|
135
|
+
<Circle
|
|
136
|
+
cx={centerX}
|
|
137
|
+
cy={y(budget)}
|
|
138
|
+
r={5}
|
|
139
|
+
fill="#8AB6E8"
|
|
140
|
+
stroke="#fff"
|
|
141
|
+
strokeWidth={2}
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
{/* Budget Value Above Dot */}
|
|
109
145
|
<SvgText
|
|
110
|
-
x={
|
|
146
|
+
x={centerX}
|
|
111
147
|
y={y(budget) - 10}
|
|
112
148
|
fontSize="10"
|
|
113
149
|
textAnchor="middle"
|
|
114
150
|
fill="#333"
|
|
115
151
|
fontWeight="700"
|
|
116
152
|
>
|
|
117
|
-
{formatNumber(budget)}
|
|
153
|
+
${formatNumber(budget)}
|
|
118
154
|
</SvgText>
|
|
119
155
|
|
|
120
|
-
{/* X Label */}
|
|
156
|
+
{/* X Axis Label - Rotated */}
|
|
121
157
|
<SvgText
|
|
122
|
-
x={
|
|
123
|
-
y={height -
|
|
158
|
+
x={centerX}
|
|
159
|
+
y={height - 25}
|
|
124
160
|
fontSize="11"
|
|
125
161
|
textAnchor="middle"
|
|
126
162
|
fill="#555"
|
|
127
|
-
transform={`rotate(-45 ${
|
|
163
|
+
transform={`rotate(-45 ${centerX} ${height - 25})`}
|
|
128
164
|
>
|
|
129
165
|
{label}
|
|
130
166
|
</SvgText>
|
|
@@ -135,10 +171,11 @@ const SvgBarLineChartCompact = ({ data }) => {
|
|
|
135
171
|
</ScrollView>
|
|
136
172
|
</View>
|
|
137
173
|
|
|
174
|
+
{/* Legend */}
|
|
138
175
|
<View style={styles.legend}>
|
|
139
|
-
<Text style={{ color: '#E07A3F', marginRight:
|
|
140
|
-
<Text style={{ color: '#4E79A7', marginRight:
|
|
141
|
-
<Text style={{ color: '#8AB6E8' }}>●
|
|
176
|
+
<Text style={{ color: '#E07A3F', marginRight: 20 }}>● 2024 Kar USD</Text>
|
|
177
|
+
<Text style={{ color: '#4E79A7', marginRight: 20 }}>● 2025 Kar USD</Text>
|
|
178
|
+
<Text style={{ color: '#8AB6E8' }}>● Bütçe Kar USD</Text>
|
|
142
179
|
</View>
|
|
143
180
|
</View>
|
|
144
181
|
);
|
|
@@ -147,15 +184,25 @@ const SvgBarLineChartCompact = ({ data }) => {
|
|
|
147
184
|
export default SvgBarLineChartCompact;
|
|
148
185
|
|
|
149
186
|
const styles = StyleSheet.create({
|
|
150
|
-
container: {
|
|
187
|
+
container: {
|
|
188
|
+
marginVertical: 12,
|
|
189
|
+
backgroundColor: '#fff',
|
|
190
|
+
borderRadius: 10,
|
|
191
|
+
padding: 10,
|
|
192
|
+
borderWidth: 1,
|
|
193
|
+
borderColor: '#ddd',
|
|
194
|
+
},
|
|
151
195
|
title: {
|
|
152
196
|
fontWeight: '700',
|
|
153
|
-
|
|
154
|
-
|
|
197
|
+
fontSize: 14,
|
|
198
|
+
textAlign: 'center',
|
|
199
|
+
marginBottom: 8,
|
|
200
|
+
color: '#111',
|
|
155
201
|
},
|
|
156
202
|
legend: {
|
|
157
203
|
flexDirection: 'row',
|
|
158
|
-
|
|
204
|
+
justifyContent: 'center',
|
|
205
|
+
marginTop: 10,
|
|
159
206
|
flexWrap: 'wrap',
|
|
160
207
|
},
|
|
161
|
-
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/components/SvgBarLineChartCompact3A.jsx
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
4
|
+
import Svg, { Rect, Line, Circle, Text as SvgText } from 'react-native-svg';
|
|
5
|
+
import { formatNumber } from '../utils/formatNumber';
|
|
6
|
+
|
|
7
|
+
const SvgBarLineChartCompact3A = ({ data }) => {
|
|
8
|
+
if (!data?.series || !data.labels?.length) return null;
|
|
9
|
+
|
|
10
|
+
const height = 280;
|
|
11
|
+
const paddingLeft = 80;
|
|
12
|
+
const paddingRight = 30;
|
|
13
|
+
const paddingTop = 30;
|
|
14
|
+
const paddingBottom = 70;
|
|
15
|
+
|
|
16
|
+
const chartHeight = height - paddingTop - paddingBottom;
|
|
17
|
+
const graphWidth = Math.max(500, data.labels.length * 110);
|
|
18
|
+
|
|
19
|
+
const MAX = 600000000; // 0.6bn
|
|
20
|
+
const barWidth = 22;
|
|
21
|
+
const barGap = 8;
|
|
22
|
+
const groupGap = 35;
|
|
23
|
+
|
|
24
|
+
const y = (v) => paddingTop + ((MAX - v) / MAX) * chartHeight;
|
|
25
|
+
const getCenterX = (i) => paddingLeft + i * (barWidth * 2 + barGap + groupGap) + (barWidth + barGap / 2);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={styles.container}>
|
|
29
|
+
<Text style={styles.title}>ULAŞTIRMA HİZMETLERİ CİRO GRAFİĞİ</Text>
|
|
30
|
+
|
|
31
|
+
<View style={{ flexDirection: 'row' }}>
|
|
32
|
+
<Svg width={paddingLeft} height={height}>
|
|
33
|
+
{[500000000, 0].map((v) => (
|
|
34
|
+
<SvgText
|
|
35
|
+
key={v}
|
|
36
|
+
x={paddingLeft - 10}
|
|
37
|
+
y={y(v) + 5}
|
|
38
|
+
fontSize="11"
|
|
39
|
+
textAnchor="end"
|
|
40
|
+
fill="#444"
|
|
41
|
+
>
|
|
42
|
+
{v === 0 ? '0.0bn' : '0.5bn'}
|
|
43
|
+
</SvgText>
|
|
44
|
+
))}
|
|
45
|
+
</Svg>
|
|
46
|
+
|
|
47
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
48
|
+
<Svg width={graphWidth} height={height}>
|
|
49
|
+
{[500000000, 0].map((v) => (
|
|
50
|
+
<Line key={v} x1={0} x2={graphWidth} y1={y(v)} y2={y(v)} stroke="#eee" strokeDasharray="5,5" />
|
|
51
|
+
))}
|
|
52
|
+
|
|
53
|
+
{data.labels.map((label, i) => {
|
|
54
|
+
const centerX = getCenterX(i);
|
|
55
|
+
const val2024 = data.series[0]?.data[i] || 0;
|
|
56
|
+
const val2025 = data.series[1]?.data[i] || 0;
|
|
57
|
+
const budget = data.series[2]?.data[i] || 0;
|
|
58
|
+
|
|
59
|
+
const h2024 = chartHeight - (y(val2024) - paddingTop);
|
|
60
|
+
const h2025 = chartHeight - (y(val2025) - paddingTop);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<React.Fragment key={i}>
|
|
64
|
+
{/* 2024 Bar */}
|
|
65
|
+
<Rect x={centerX - barWidth - barGap / 2} y={y(val2024)} width={barWidth} height={h2024} fill="#E07A3F" />
|
|
66
|
+
{val2024 > MAX * 0.1 && (
|
|
67
|
+
<SvgText
|
|
68
|
+
x={centerX - barWidth - barGap / 2 + barWidth / 2}
|
|
69
|
+
y={y(val2024) + h2024 / 2 + 5}
|
|
70
|
+
fontSize="10"
|
|
71
|
+
textAnchor="middle"
|
|
72
|
+
fill="white"
|
|
73
|
+
fontWeight="700"
|
|
74
|
+
>
|
|
75
|
+
{formatNumber(val2024)}M
|
|
76
|
+
</SvgText>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{/* 2025 Bar */}
|
|
80
|
+
<Rect x={centerX + barGap / 2} y={y(val2025)} width={barWidth} height={h2025} fill="#4E79A7" />
|
|
81
|
+
{val2025 > MAX * 0.1 && (
|
|
82
|
+
<SvgText
|
|
83
|
+
x={centerX + barGap / 2 + barWidth / 2}
|
|
84
|
+
y={y(val2025) + h2025 / 2 + 5}
|
|
85
|
+
fontSize="10"
|
|
86
|
+
textAnchor="middle"
|
|
87
|
+
fill="white"
|
|
88
|
+
fontWeight="700"
|
|
89
|
+
>
|
|
90
|
+
{formatNumber(val2025)}M
|
|
91
|
+
</SvgText>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{/* Budget Dotted Line & Dot */}
|
|
95
|
+
{i > 0 && (
|
|
96
|
+
<Line
|
|
97
|
+
x1={getCenterX(i - 1)}
|
|
98
|
+
y1={y(data.series[2].data[i - 1])}
|
|
99
|
+
x2={centerX}
|
|
100
|
+
y2={y(budget)}
|
|
101
|
+
stroke="#8AB6E8"
|
|
102
|
+
strokeWidth={2}
|
|
103
|
+
strokeDasharray="6,4"
|
|
104
|
+
/>
|
|
105
|
+
)}
|
|
106
|
+
<Circle cx={centerX} cy={y(budget)} r={6} fill="#8AB6E8" stroke="#fff" strokeWidth={2} />
|
|
107
|
+
<SvgText x={centerX} y={y(budget) - 12} fontSize="11" textAnchor="middle" fill="#333" fontWeight="700">
|
|
108
|
+
{formatNumber(budget)}M
|
|
109
|
+
</SvgText>
|
|
110
|
+
|
|
111
|
+
{/* X Label */}
|
|
112
|
+
<SvgText
|
|
113
|
+
x={centerX}
|
|
114
|
+
y={height - 30}
|
|
115
|
+
fontSize="11"
|
|
116
|
+
textAnchor="middle"
|
|
117
|
+
fill="#555"
|
|
118
|
+
transform={`rotate(-45 ${centerX} ${height - 30})`}
|
|
119
|
+
>
|
|
120
|
+
{label}
|
|
121
|
+
</SvgText>
|
|
122
|
+
</React.Fragment>
|
|
123
|
+
);
|
|
124
|
+
})}
|
|
125
|
+
</Svg>
|
|
126
|
+
</ScrollView>
|
|
127
|
+
</View>
|
|
128
|
+
|
|
129
|
+
<View style={styles.legend}>
|
|
130
|
+
<Text style={{ color: '#E07A3F', marginRight: 20 }}>● 2024 Gelir Tutar (TL)</Text>
|
|
131
|
+
<Text style={{ color: '#4E79A7', marginRight: 20 }}>● 2025 Gelir Tutar (TL)</Text>
|
|
132
|
+
<Text style={{ color: '#8AB6E8' }}>● 2025 Bütçe Gelir (TL)</Text>
|
|
133
|
+
</View>
|
|
134
|
+
</View>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const styles = StyleSheet.create({
|
|
139
|
+
container: { padding: 10 },
|
|
140
|
+
title: { fontWeight: '700', fontSize: 14, textAlign: 'center', marginBottom: 10 },
|
|
141
|
+
legend: { flexDirection: 'row', justifyContent: 'center', marginTop: 12, flexWrap: 'wrap' },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
export default SvgBarLineChartCompact3A;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/components/SvgLineChartCompact3A.jsx
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
4
|
+
import Svg, { Line, Circle, Text as SvgText } from 'react-native-svg';
|
|
5
|
+
import { formatNumber } from '../utils/formatNumber';
|
|
6
|
+
|
|
7
|
+
const SvgLineChartCompact3A = ({ data }) => {
|
|
8
|
+
if (!data?.series || !data.labels?.length) return null;
|
|
9
|
+
|
|
10
|
+
const height = 260;
|
|
11
|
+
const paddingLeft = 70;
|
|
12
|
+
const paddingRight = 30;
|
|
13
|
+
const paddingTop = 30;
|
|
14
|
+
const paddingBottom = 60;
|
|
15
|
+
|
|
16
|
+
const chartHeight = height - paddingTop - paddingBottom;
|
|
17
|
+
const graphWidth = Math.max(500, data.labels.length * 100);
|
|
18
|
+
|
|
19
|
+
const MIN = 35000;
|
|
20
|
+
const MAX = 55000;
|
|
21
|
+
|
|
22
|
+
const y = (v) => paddingTop + ((MAX - v) / (MAX - MIN)) * chartHeight;
|
|
23
|
+
|
|
24
|
+
const totalPoints = data.labels.length;
|
|
25
|
+
const xStep = totalPoints === 1 ? graphWidth / 2 : (graphWidth - paddingLeft - paddingRight) / (totalPoints - 1);
|
|
26
|
+
|
|
27
|
+
const colors = ['#E07A3F', '#4E79A7']; // Orange 2024, Blue 2025
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<Text style={styles.title}>ULAŞTIRMA HİZMETLERİ YÜK ADET GRAFİĞİ</Text>
|
|
32
|
+
|
|
33
|
+
<View style={{ flexDirection: 'row' }}>
|
|
34
|
+
<Svg width={paddingLeft} height={height}>
|
|
35
|
+
{[50000, 40000].map((v) => (
|
|
36
|
+
<SvgText
|
|
37
|
+
key={v}
|
|
38
|
+
x={paddingLeft - 10}
|
|
39
|
+
y={y(v) + 5}
|
|
40
|
+
fontSize="11"
|
|
41
|
+
textAnchor="end"
|
|
42
|
+
fill="#444"
|
|
43
|
+
>
|
|
44
|
+
{v / 1000}K
|
|
45
|
+
</SvgText>
|
|
46
|
+
))}
|
|
47
|
+
</Svg>
|
|
48
|
+
|
|
49
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
50
|
+
<Svg width={graphWidth} height={height}>
|
|
51
|
+
{[50000, 40000].map((v) => (
|
|
52
|
+
<Line key={v} x1={0} x2={graphWidth} y1={y(v)} y2={y(v)} stroke="#eee" strokeDasharray="5,5" />
|
|
53
|
+
))}
|
|
54
|
+
|
|
55
|
+
{data.series.map((series, si) =>
|
|
56
|
+
series.data.map((val, i) => {
|
|
57
|
+
const x = paddingLeft + i * (totalPoints === 1 ? 0 : xStep) + (totalPoints === 1 ? graphWidth / 2 - paddingLeft : 0);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<React.Fragment key={`${si}-${i}`}>
|
|
61
|
+
{i > 0 && (
|
|
62
|
+
<Line
|
|
63
|
+
x1={paddingLeft + (i - 1) * xStep + (totalPoints === 1 ? graphWidth / 2 - paddingLeft : 0)}
|
|
64
|
+
y1={y(series.data[i - 1])}
|
|
65
|
+
x2={x}
|
|
66
|
+
y2={y(val)}
|
|
67
|
+
stroke={colors[si]}
|
|
68
|
+
strokeWidth={3}
|
|
69
|
+
strokeDasharray={si === 0 ? "6,4" : "0"}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
<Circle cx={x} cy={y(val)} r={6} fill={colors[si]} stroke="#fff" strokeWidth={2} />
|
|
73
|
+
<SvgText x={x} y={y(val) - 12} fontSize="11" textAnchor="middle" fill="#333" fontWeight="700">
|
|
74
|
+
{formatNumber(val)}
|
|
75
|
+
</SvgText>
|
|
76
|
+
</React.Fragment>
|
|
77
|
+
);
|
|
78
|
+
})
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{data.labels.map((label, i) => {
|
|
82
|
+
const x = paddingLeft + i * xStep + (totalPoints === 1 ? graphWidth / 2 - paddingLeft : 0);
|
|
83
|
+
return (
|
|
84
|
+
<SvgText
|
|
85
|
+
key={i}
|
|
86
|
+
x={x}
|
|
87
|
+
y={height - 25}
|
|
88
|
+
fontSize="11"
|
|
89
|
+
textAnchor="middle"
|
|
90
|
+
fill="#555"
|
|
91
|
+
transform={`rotate(-45 ${x} ${height - 25})`}
|
|
92
|
+
>
|
|
93
|
+
{label}
|
|
94
|
+
</SvgText>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
</Svg>
|
|
98
|
+
</ScrollView>
|
|
99
|
+
</View>
|
|
100
|
+
|
|
101
|
+
<View style={styles.legend}>
|
|
102
|
+
<Text style={{ color: '#E07A3F', marginRight: 20 }}>● 2024 Yük Adet</Text>
|
|
103
|
+
<Text style={{ color: '#4E79A7' }}>● 2025 Yük Adet</Text>
|
|
104
|
+
</View>
|
|
105
|
+
</View>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const styles = StyleSheet.create({
|
|
110
|
+
container: { padding: 10 },
|
|
111
|
+
title: { fontWeight: '700', fontSize: 14, textAlign: 'center', marginBottom: 10 },
|
|
112
|
+
legend: { flexDirection: 'row', justifyContent: 'center', marginTop: 12 },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export default SvgLineChartCompact3A;
|
package/src/index.jsx
CHANGED
|
@@ -7,6 +7,7 @@ import Report2Screen from './screens/Report2Screen';
|
|
|
7
7
|
import Report3Screen from './screens/Report3Screen';
|
|
8
8
|
import Report1AScreen from './screens/Report1AScreen';
|
|
9
9
|
import Report2AScreen from './screens/Report2AScreen';
|
|
10
|
+
import Report3AScreen from './screens/Report3AScreen';
|
|
10
11
|
|
|
11
12
|
const AnalyticsReports = ({ config, onExit }) => {
|
|
12
13
|
const [active, setActive] = useState(null);
|
|
@@ -110,6 +111,14 @@ if (active === '2A') {
|
|
|
110
111
|
);
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
if (active === '3A') {
|
|
115
|
+
return (
|
|
116
|
+
<SafeScreen>
|
|
117
|
+
<Report3AScreen api={config.report3} token={config.token} onBack={() => setActive(null)} />
|
|
118
|
+
</SafeScreen>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
113
122
|
return null;
|
|
114
123
|
};
|
|
115
124
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
// src/screens/Report2AScreen.jsx
|
|
1
2
|
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ScrollView,
|
|
5
|
+
Text,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
View,
|
|
8
|
+
ActivityIndicator,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
} from 'react-native';
|
|
3
11
|
import { filterChartByMonths } from '../utils/filterChartByMonths';
|
|
4
12
|
import { getDivisions, getTable, getLine, getBar } from '../api/report2Fetcher';
|
|
5
13
|
import MonthFilterModal from '../components/MonthFilterModal';
|
|
@@ -20,33 +28,92 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
20
28
|
const [table, setTable] = useState(null);
|
|
21
29
|
const [line, setLine] = useState(null);
|
|
22
30
|
const [bar, setBar] = useState(null);
|
|
31
|
+
const [loading, setLoading] = useState(true);
|
|
32
|
+
const [error, setError] = useState(null);
|
|
23
33
|
|
|
34
|
+
// Fetch divisions
|
|
24
35
|
useEffect(() => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
if (!api?.divisions || !token) return;
|
|
37
|
+
|
|
38
|
+
getDivisions(api.divisions, token)
|
|
39
|
+
.then((d) => {
|
|
40
|
+
setDivisions(d);
|
|
41
|
+
if (d.length > 0) {
|
|
42
|
+
setDivision(d[0].code);
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.catch((err) => {
|
|
46
|
+
console.error('Failed to load divisions:', err);
|
|
47
|
+
setError('Failed to load divisions');
|
|
48
|
+
setLoading(false);
|
|
49
|
+
});
|
|
50
|
+
}, [api?.divisions, token]);
|
|
51
|
+
|
|
52
|
+
// Fetch data when division changes
|
|
31
53
|
useEffect(() => {
|
|
32
|
-
if (!division) return;
|
|
54
|
+
if (!division || !api || !token) return;
|
|
55
|
+
|
|
56
|
+
setLoading(true);
|
|
57
|
+
setError(null);
|
|
33
58
|
|
|
34
59
|
Promise.all([
|
|
35
|
-
getTable(api.table, division, token),
|
|
36
|
-
getLine(api.line, division, token),
|
|
37
|
-
getBar(api.bar, division, token),
|
|
38
|
-
])
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
getTable(api.table, division, token).catch(() => null),
|
|
61
|
+
getLine(api.line, division, token).catch(() => null),
|
|
62
|
+
getBar(api.bar, division, token).catch(() => null),
|
|
63
|
+
])
|
|
64
|
+
.then(([t, l, b]) => {
|
|
65
|
+
if (!t || !l || !b) {
|
|
66
|
+
setError('Failed to load report data');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setTable(t);
|
|
71
|
+
setLine(l);
|
|
72
|
+
setBar(b);
|
|
73
|
+
setMonths(l.labels || []);
|
|
74
|
+
setSelectedMonths(l.labels || []);
|
|
75
|
+
})
|
|
76
|
+
.catch((err) => {
|
|
77
|
+
console.error('Data fetch error:', err);
|
|
78
|
+
setError('Network error or data unavailable');
|
|
79
|
+
})
|
|
80
|
+
.finally(() => {
|
|
81
|
+
setLoading(false);
|
|
82
|
+
});
|
|
45
83
|
}, [division, api, token]);
|
|
46
84
|
|
|
47
|
-
|
|
85
|
+
// Loading State
|
|
86
|
+
if (loading) {
|
|
87
|
+
return (
|
|
88
|
+
<View style={styles.center}>
|
|
89
|
+
<ActivityIndicator size="large" color="#1e88e5" />
|
|
90
|
+
<Text style={styles.loadingText}>Loading Report 2A...</Text>
|
|
91
|
+
</View>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Error State
|
|
96
|
+
if (error) {
|
|
97
|
+
return (
|
|
98
|
+
<View style={styles.center}>
|
|
99
|
+
<Text style={styles.errorText}>{error}</Text>
|
|
100
|
+
<TouchableOpacity onPress={onBack}>
|
|
101
|
+
<Text style={styles.backLink}>← Go Back</Text>
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
</View>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Safety check
|
|
108
|
+
if (!table || !line || !bar || !division) {
|
|
109
|
+
return (
|
|
110
|
+
<View style={styles.center}>
|
|
111
|
+
<Text>No data available</Text>
|
|
112
|
+
</View>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
48
115
|
|
|
49
|
-
const filteredRows = table.rows.filter(r =>
|
|
116
|
+
const filteredRows = table.rows.filter((r) =>
|
|
50
117
|
selectedMonths.includes(r.monthLabel)
|
|
51
118
|
);
|
|
52
119
|
|
|
@@ -54,36 +121,42 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
54
121
|
const filteredBar = filterChartByMonths(bar, selectedMonths);
|
|
55
122
|
|
|
56
123
|
return (
|
|
57
|
-
<ScrollView style={
|
|
58
|
-
|
|
124
|
+
<ScrollView style={styles.container}>
|
|
125
|
+
{/* Header */}
|
|
126
|
+
<Text onPress={onBack} style={styles.backButton}>
|
|
59
127
|
‹ Back
|
|
60
128
|
</Text>
|
|
61
129
|
|
|
62
|
-
{/*
|
|
63
|
-
<View style={
|
|
130
|
+
{/* Filters */}
|
|
131
|
+
<View style={styles.filterRow}>
|
|
64
132
|
<TouchableOpacity onPress={() => setMonthsModal(true)}>
|
|
65
|
-
<Text style={
|
|
133
|
+
<Text style={styles.filterText}>Months ⛃</Text>
|
|
66
134
|
</TouchableOpacity>
|
|
67
135
|
|
|
68
|
-
<TouchableOpacity
|
|
69
|
-
|
|
136
|
+
<TouchableOpacity
|
|
137
|
+
onPress={() => setDivisionModal(true)}
|
|
138
|
+
style={{ marginLeft: 20 }}
|
|
139
|
+
>
|
|
140
|
+
<Text style={styles.filterText}>
|
|
141
|
+
Division: {divisions.find((d) => d.code === division)?.displayName || division}
|
|
142
|
+
</Text>
|
|
70
143
|
</TouchableOpacity>
|
|
71
144
|
</View>
|
|
72
145
|
|
|
73
|
-
{/*
|
|
146
|
+
{/* Table */}
|
|
74
147
|
<FrozenTableReport2A rows={filteredRows} />
|
|
75
148
|
|
|
76
|
-
{/*
|
|
77
|
-
<View style={
|
|
149
|
+
{/* Line Chart */}
|
|
150
|
+
<View style={styles.chartContainer}>
|
|
78
151
|
<SvgLineChartCompact data={filteredLine} />
|
|
79
152
|
</View>
|
|
80
153
|
|
|
81
|
-
{/*
|
|
82
|
-
<View style={
|
|
154
|
+
{/* Bar + Budget Chart */}
|
|
155
|
+
<View style={styles.chartContainer}>
|
|
83
156
|
<SvgBarLineChartCompact data={filteredBar} />
|
|
84
157
|
</View>
|
|
85
158
|
|
|
86
|
-
{/*
|
|
159
|
+
{/* Modals */}
|
|
87
160
|
<MonthFilterModal
|
|
88
161
|
visible={monthsModal}
|
|
89
162
|
months={months}
|
|
@@ -103,4 +176,54 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
103
176
|
);
|
|
104
177
|
};
|
|
105
178
|
|
|
179
|
+
const styles = StyleSheet.create({
|
|
180
|
+
container: { padding: 12, backgroundColor: '#f8f9fa' },
|
|
181
|
+
center: {
|
|
182
|
+
flex: 1,
|
|
183
|
+
justifyContent: 'center',
|
|
184
|
+
alignItems: 'center',
|
|
185
|
+
padding: 20,
|
|
186
|
+
},
|
|
187
|
+
loadingText: {
|
|
188
|
+
marginTop: 12,
|
|
189
|
+
fontSize: 16,
|
|
190
|
+
color: '#555',
|
|
191
|
+
},
|
|
192
|
+
errorText: {
|
|
193
|
+
fontSize: 16,
|
|
194
|
+
color: '#d32f2f',
|
|
195
|
+
textAlign: 'center',
|
|
196
|
+
marginBottom: 20,
|
|
197
|
+
},
|
|
198
|
+
backLink: {
|
|
199
|
+
fontSize: 18,
|
|
200
|
+
color: '#1e88e5',
|
|
201
|
+
fontWeight: '600',
|
|
202
|
+
},
|
|
203
|
+
backButton: {
|
|
204
|
+
fontSize: 20,
|
|
205
|
+
color: '#1e88e5',
|
|
206
|
+
marginBottom: 16,
|
|
207
|
+
fontWeight: '600',
|
|
208
|
+
},
|
|
209
|
+
filterRow: {
|
|
210
|
+
flexDirection: 'row',
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
marginVertical: 12,
|
|
213
|
+
},
|
|
214
|
+
filterText: {
|
|
215
|
+
fontSize: 16,
|
|
216
|
+
color: '#1e88e5',
|
|
217
|
+
fontWeight: '600',
|
|
218
|
+
},
|
|
219
|
+
chartContainer: {
|
|
220
|
+
borderWidth: 1,
|
|
221
|
+
borderColor: '#ddd',
|
|
222
|
+
borderRadius: 10,
|
|
223
|
+
overflow: 'hidden',
|
|
224
|
+
marginVertical: 12,
|
|
225
|
+
backgroundColor: '#fff',
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
106
229
|
export default Report2AScreen;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ScrollView,
|
|
4
|
+
Text,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
View,
|
|
7
|
+
ActivityIndicator,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
fetchReport3Table,
|
|
12
|
+
fetchReport3Line,
|
|
13
|
+
fetchReport3Bar,
|
|
14
|
+
} from '../api/report3Fetcher';
|
|
15
|
+
import MonthFilterModal from '../components/MonthFilterModal';
|
|
16
|
+
import FrozenTableReport3A from '../components/FrozenTableReport3A';
|
|
17
|
+
import SvgLineChartCompact3A from '../components/SvgLineChartCompact3A';
|
|
18
|
+
import SvgBarLineChartCompact3A from '../components/SvgBarLineChartCompact3A';
|
|
19
|
+
|
|
20
|
+
const Report3AScreen = ({ api, token, onBack }) => {
|
|
21
|
+
const [monthsModal, setMonthsModal] = useState(false);
|
|
22
|
+
const [months, setMonths] = useState([]);
|
|
23
|
+
const [selectedMonths, setSelectedMonths] = useState([]);
|
|
24
|
+
const [table, setTable] = useState(null);
|
|
25
|
+
const [line, setLine] = useState(null);
|
|
26
|
+
const [bar, setBar] = useState(null);
|
|
27
|
+
const [loading, setLoading] = useState(true);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!api || !token) return;
|
|
32
|
+
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
|
|
36
|
+
Promise.all([
|
|
37
|
+
fetchReport3Table(api.table, token).catch(() => null),
|
|
38
|
+
fetchReport3Line(api.line, token).catch(() => null),
|
|
39
|
+
fetchReport3Bar(api.bar, token).catch(() => null),
|
|
40
|
+
])
|
|
41
|
+
.then(([t, l, b]) => {
|
|
42
|
+
if (!t || !l || !b) throw new Error('Failed to load data');
|
|
43
|
+
|
|
44
|
+
setTable(t);
|
|
45
|
+
setLine(l);
|
|
46
|
+
setBar(b);
|
|
47
|
+
|
|
48
|
+
const monthLabels = t.rows
|
|
49
|
+
.filter(r => r.month !== 99)
|
|
50
|
+
.map(r => r.monthLabel);
|
|
51
|
+
|
|
52
|
+
setMonths(monthLabels);
|
|
53
|
+
setSelectedMonths(monthLabels); // default: all selected
|
|
54
|
+
})
|
|
55
|
+
.catch((err) => {
|
|
56
|
+
console.error(err);
|
|
57
|
+
setError('Failed to load Report 3A data');
|
|
58
|
+
})
|
|
59
|
+
.finally(() => setLoading(false));
|
|
60
|
+
}, [api, token]);
|
|
61
|
+
|
|
62
|
+
if (loading) {
|
|
63
|
+
return (
|
|
64
|
+
<View style={styles.center}>
|
|
65
|
+
<ActivityIndicator size="large" color="#1e88e5" />
|
|
66
|
+
<Text style={styles.loadingText}>Loading Report 3A...</Text>
|
|
67
|
+
</View>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (error) {
|
|
72
|
+
return (
|
|
73
|
+
<View style={styles.center}>
|
|
74
|
+
<Text style={styles.errorText}>{error}</Text>
|
|
75
|
+
<TouchableOpacity onPress={onBack}>
|
|
76
|
+
<Text style={styles.backLink}>← Go Back</Text>
|
|
77
|
+
</TouchableOpacity>
|
|
78
|
+
</View>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const filteredRows = table.rows.filter(r =>
|
|
83
|
+
selectedMonths.includes(r.monthLabel)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const filterChart = (chart) => {
|
|
87
|
+
if (selectedMonths.length === months.length) return chart;
|
|
88
|
+
|
|
89
|
+
const indices = chart.labels
|
|
90
|
+
.map((label, i) => selectedMonths.includes(label) ? i : -1)
|
|
91
|
+
.filter(i => i !== -1);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
...chart,
|
|
95
|
+
labels: indices.map(i => chart.labels[i]),
|
|
96
|
+
series: chart.series.map(s => ({
|
|
97
|
+
...s,
|
|
98
|
+
data: indices.map(i => s.data[i]),
|
|
99
|
+
})),
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const filteredLine = filterChart(line);
|
|
104
|
+
const filteredBar = filterChart(bar);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<ScrollView style={styles.container}>
|
|
108
|
+
<Text onPress={onBack} style={styles.backButton}>‹ Back</Text>
|
|
109
|
+
|
|
110
|
+
<Text style={styles.screenTitle}>Ulaştırma Hizmetleri Analizi (A)</Text>
|
|
111
|
+
|
|
112
|
+
{/* Month Filter */}
|
|
113
|
+
<TouchableOpacity
|
|
114
|
+
onPress={() => setMonthsModal(true)}
|
|
115
|
+
style={styles.filterButton}
|
|
116
|
+
>
|
|
117
|
+
<Text style={styles.filterText}>
|
|
118
|
+
Months ({selectedMonths.length}/{months.length}) ⛃
|
|
119
|
+
</Text>
|
|
120
|
+
</TouchableOpacity>
|
|
121
|
+
|
|
122
|
+
{/* Table */}
|
|
123
|
+
<FrozenTableReport3A rows={filteredRows} />
|
|
124
|
+
|
|
125
|
+
{/* Line Chart - Yük Adet */}
|
|
126
|
+
<View style={styles.chartWrapper}>
|
|
127
|
+
<SvgLineChartCompact3A data={filteredLine} />
|
|
128
|
+
</View>
|
|
129
|
+
|
|
130
|
+
{/* Bar + Budget Chart - Ciro */}
|
|
131
|
+
<View style={styles.chartWrapper}>
|
|
132
|
+
<SvgBarLineChartCompact3A data={filteredBar} />
|
|
133
|
+
</View>
|
|
134
|
+
|
|
135
|
+
{/* Modal */}
|
|
136
|
+
<MonthFilterModal
|
|
137
|
+
visible={monthsModal}
|
|
138
|
+
months={months}
|
|
139
|
+
selected={selectedMonths}
|
|
140
|
+
onApply={setSelectedMonths}
|
|
141
|
+
onClose={() => setMonthsModal(false)}
|
|
142
|
+
/>
|
|
143
|
+
</ScrollView>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const styles = StyleSheet.create({
|
|
148
|
+
container: { padding: 12, backgroundColor: '#f8f9fa' },
|
|
149
|
+
center: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
|
|
150
|
+
loadingText: { marginTop: 12, fontSize: 16, color: '#555' },
|
|
151
|
+
errorText: { fontSize: 16, color: '#d32f2f', textAlign: 'center', marginBottom: 20 },
|
|
152
|
+
backLink: { fontSize: 18, color: '#1e88e5', fontWeight: '600' },
|
|
153
|
+
backButton: { fontSize: 20, color: '#1e88e5', marginBottom: 16, fontWeight: '600' },
|
|
154
|
+
screenTitle: { fontSize: 18, fontWeight: '700', textAlign: 'center', marginVertical: 12 },
|
|
155
|
+
filterButton: { alignSelf: 'flex-start', marginVertical: 12 },
|
|
156
|
+
filterText: { fontSize: 16, color: '#1e88e5', fontWeight: '600' },
|
|
157
|
+
chartWrapper: {
|
|
158
|
+
borderWidth: 1,
|
|
159
|
+
borderColor: '#ddd',
|
|
160
|
+
borderRadius: 10,
|
|
161
|
+
overflow: 'hidden',
|
|
162
|
+
marginVertical: 12,
|
|
163
|
+
backgroundColor: '#fff',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export default Report3AScreen;
|