@dhiraj0720/report1chart 2.6.3 → 2.6.5
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,33 +1,42 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View, Text, ScrollView, StyleSheet
|
|
3
|
-
|
|
4
|
-
const { width, height } = Dimensions.get('window');
|
|
2
|
+
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
5
3
|
|
|
6
4
|
const Arrow = ({ value }) => (
|
|
7
|
-
<Text
|
|
5
|
+
<Text
|
|
6
|
+
style={[
|
|
7
|
+
styles.arrow,
|
|
8
|
+
value >= 0 ? styles.up : styles.down,
|
|
9
|
+
]}
|
|
10
|
+
>
|
|
8
11
|
{value >= 0 ? '↑' : '↓'} {Math.abs(value)}%
|
|
9
12
|
</Text>
|
|
10
13
|
);
|
|
11
14
|
|
|
12
15
|
const Cell = ({ children, bold, frozen }) => (
|
|
13
|
-
<View
|
|
14
|
-
|
|
16
|
+
<View
|
|
17
|
+
style={[
|
|
18
|
+
styles.cell,
|
|
19
|
+
frozen && styles.frozenCell,
|
|
20
|
+
]}
|
|
21
|
+
>
|
|
22
|
+
<Text
|
|
23
|
+
numberOfLines={1}
|
|
24
|
+
ellipsizeMode="tail"
|
|
25
|
+
style={[
|
|
26
|
+
styles.text,
|
|
27
|
+
bold && styles.bold,
|
|
28
|
+
]}
|
|
29
|
+
>
|
|
15
30
|
{children}
|
|
16
31
|
</Text>
|
|
17
32
|
</View>
|
|
18
33
|
);
|
|
19
34
|
|
|
20
|
-
const FrozenTableReport1A = ({ rows
|
|
21
|
-
const isLandscape = width > height;
|
|
22
|
-
|
|
23
|
-
// Auto-adjust cell width in fullscreen landscape
|
|
24
|
-
const cellWidth = isFullscreen && isLandscape ? 160 : 110;
|
|
25
|
-
const frozenWidth = isFullscreen && isLandscape ? 200 : 150;
|
|
26
|
-
const fontSize = isFullscreen ? 14 : 12;
|
|
27
|
-
|
|
35
|
+
const FrozenTableReport1A = ({ rows }) => {
|
|
28
36
|
return (
|
|
29
37
|
<View style={styles.container}>
|
|
30
|
-
|
|
38
|
+
{/* LEFT FROZEN COLUMN */}
|
|
39
|
+
<View style={styles.frozen}>
|
|
31
40
|
<Cell bold frozen>FAALİYET</Cell>
|
|
32
41
|
{rows.map((r, i) => (
|
|
33
42
|
<Cell key={i} frozen bold>
|
|
@@ -36,6 +45,7 @@ const FrozenTableReport1A = ({ rows, isFullscreen = false }) => {
|
|
|
36
45
|
))}
|
|
37
46
|
</View>
|
|
38
47
|
|
|
48
|
+
{/* SCROLLABLE COLUMNS */}
|
|
39
49
|
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
40
50
|
<View>
|
|
41
51
|
<View style={styles.headerRow}>
|
|
@@ -48,10 +58,10 @@ const FrozenTableReport1A = ({ rows, isFullscreen = false }) => {
|
|
|
48
58
|
|
|
49
59
|
{rows.map((r, i) => (
|
|
50
60
|
<View key={i} style={styles.row}>
|
|
51
|
-
<Cell>{r.actual2024
|
|
52
|
-
<Cell>{r.actual2025
|
|
61
|
+
<Cell>{r.actual2024}</Cell>
|
|
62
|
+
<Cell>{r.actual2025}</Cell>
|
|
53
63
|
<Cell><Arrow value={r.actualChangePercent} /></Cell>
|
|
54
|
-
<Cell>{r.budget2025
|
|
64
|
+
<Cell>{r.budget2025}</Cell>
|
|
55
65
|
<Cell><Arrow value={r.budgetVariancePercent} /></Cell>
|
|
56
66
|
</View>
|
|
57
67
|
))}
|
|
@@ -61,6 +71,11 @@ const FrozenTableReport1A = ({ rows, isFullscreen = false }) => {
|
|
|
61
71
|
);
|
|
62
72
|
};
|
|
63
73
|
|
|
74
|
+
export default FrozenTableReport1A;
|
|
75
|
+
|
|
76
|
+
/* ======================
|
|
77
|
+
STYLES (COMPACT)
|
|
78
|
+
====================== */
|
|
64
79
|
const styles = StyleSheet.create({
|
|
65
80
|
container: {
|
|
66
81
|
flexDirection: 'row',
|
|
@@ -70,24 +85,55 @@ const styles = StyleSheet.create({
|
|
|
70
85
|
overflow: 'hidden',
|
|
71
86
|
backgroundColor: '#fff',
|
|
72
87
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
|
|
89
|
+
frozen: {
|
|
90
|
+
width: 150, // 👈 fixed width
|
|
91
|
+
backgroundColor: '#f5f7fa',
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
frozenCell: {
|
|
95
|
+
alignItems: 'flex-start',
|
|
96
|
+
paddingHorizontal: 8,
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
headerRow: {
|
|
100
|
+
flexDirection: 'row',
|
|
101
|
+
backgroundColor: '#f0f0f0',
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
row: {
|
|
105
|
+
flexDirection: 'row',
|
|
106
|
+
},
|
|
107
|
+
|
|
77
108
|
cell: {
|
|
78
|
-
width: 110,
|
|
79
|
-
paddingVertical:
|
|
109
|
+
width: 110, // 👈 compact width
|
|
110
|
+
paddingVertical: 6, // 👈 reduced height
|
|
80
111
|
paddingHorizontal: 6,
|
|
81
112
|
borderBottomWidth: 1,
|
|
82
113
|
borderColor: '#eee',
|
|
83
114
|
justifyContent: 'center',
|
|
84
115
|
alignItems: 'center',
|
|
85
116
|
},
|
|
86
|
-
text: { fontSize: 12, color: '#222' },
|
|
87
|
-
bold: { fontWeight: '700' },
|
|
88
|
-
arrow: { fontSize: 12, fontWeight: '700' },
|
|
89
|
-
up: { color: '#2e7d32' },
|
|
90
|
-
down: { color: '#d32f2f' },
|
|
91
|
-
});
|
|
92
117
|
|
|
93
|
-
|
|
118
|
+
text: {
|
|
119
|
+
fontSize: 12, // 👈 smaller text
|
|
120
|
+
color: '#222',
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
bold: {
|
|
124
|
+
fontWeight: '700',
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
arrow: {
|
|
128
|
+
fontSize: 12,
|
|
129
|
+
fontWeight: '700',
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
up: {
|
|
133
|
+
color: '#2e7d32',
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
down: {
|
|
137
|
+
color: '#d32f2f',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
@@ -1,52 +1,45 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ScrollView,
|
|
4
|
-
Text,
|
|
5
|
-
TouchableOpacity,
|
|
6
|
-
Modal,
|
|
7
|
-
View,
|
|
8
|
-
StyleSheet,
|
|
9
|
-
Dimensions,
|
|
10
|
-
Alert,
|
|
11
|
-
} from 'react-native';
|
|
12
|
-
import { PinchGestureHandler, State } from 'react-native-gesture-handler';
|
|
2
|
+
import { ScrollView, Text, TouchableOpacity } from 'react-native';
|
|
13
3
|
import fetchReport1 from '../api/report1Fetcher';
|
|
14
4
|
import FrozenTableReport1A from '../components/FrozenTableReport1A';
|
|
15
5
|
import Report1Card from '../components/Report1Card';
|
|
6
|
+
import FullScreenTableModal from '../components/FullScreenTableModal';
|
|
16
7
|
|
|
17
8
|
const Report1AScreen = ({ endpoint, token, onBack }) => {
|
|
18
9
|
const [rows, setRows] = useState([]);
|
|
19
10
|
const [fullscreen, setFullscreen] = useState(false);
|
|
20
|
-
const [scale, setScale] = useState(1);
|
|
21
11
|
|
|
22
12
|
useEffect(() => {
|
|
23
13
|
fetchReport1(endpoint, token).then(setRows);
|
|
24
|
-
}, [
|
|
25
|
-
|
|
26
|
-
const openFullscreen = () => {
|
|
27
|
-
setFullscreen(true);
|
|
28
|
-
setScale(1);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const closeFullscreen = () => {
|
|
32
|
-
setFullscreen(false);
|
|
33
|
-
setScale(1);
|
|
34
|
-
};
|
|
14
|
+
}, []);
|
|
35
15
|
|
|
36
16
|
return (
|
|
37
17
|
<>
|
|
38
|
-
<ScrollView style={{ padding: 16
|
|
39
|
-
<Text onPress={onBack} style={
|
|
40
|
-
|
|
41
|
-
<Text style={
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
18
|
+
<ScrollView style={{ padding: 16 }}>
|
|
19
|
+
<Text onPress={onBack} style={{ marginBottom: 12 }}>‹ Back</Text>
|
|
20
|
+
|
|
21
|
+
<Text style={{ fontSize: 18, fontWeight: '700', marginBottom: 12 }}>
|
|
22
|
+
PERFORMANS RAPORU (USD)
|
|
23
|
+
</Text>
|
|
24
|
+
|
|
25
|
+
{/* FULLSCREEN BUTTON */}
|
|
26
|
+
<TouchableOpacity
|
|
27
|
+
onPress={() => setFullscreen(true)}
|
|
28
|
+
style={{
|
|
29
|
+
alignSelf: 'flex-end',
|
|
30
|
+
marginBottom: 8,
|
|
31
|
+
padding: 6,
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<Text style={{ fontWeight: '700' }}>⤢ Full Screen</Text>
|
|
45
35
|
</TouchableOpacity>
|
|
46
36
|
|
|
47
37
|
<FrozenTableReport1A rows={rows} />
|
|
48
38
|
|
|
49
|
-
|
|
39
|
+
{/* BELOW TABLE → CARDS */}
|
|
40
|
+
<Text style={{ marginVertical: 12, fontWeight: '700' }}>
|
|
41
|
+
Individual Reports
|
|
42
|
+
</Text>
|
|
50
43
|
|
|
51
44
|
{rows.map((r, i) => (
|
|
52
45
|
<Report1Card key={i} item={r} />
|
|
@@ -54,85 +47,13 @@ const Report1AScreen = ({ endpoint, token, onBack }) => {
|
|
|
54
47
|
</ScrollView>
|
|
55
48
|
|
|
56
49
|
{/* FULL SCREEN MODAL */}
|
|
57
|
-
<
|
|
50
|
+
<FullScreenTableModal
|
|
58
51
|
visible={fullscreen}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<View style={styles.modalContainer}>
|
|
63
|
-
{/* Header */}
|
|
64
|
-
<View style={styles.modalHeader}>
|
|
65
|
-
<TouchableOpacity
|
|
66
|
-
onPress={() =>
|
|
67
|
-
Alert.alert(
|
|
68
|
-
'Rotate Device',
|
|
69
|
-
'For best view, please rotate your device to landscape mode ↻',
|
|
70
|
-
[{ text: 'OK' }]
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
>
|
|
74
|
-
<Text style={styles.rotateHint}>↻ Rotate Device</Text>
|
|
75
|
-
</TouchableOpacity>
|
|
76
|
-
|
|
77
|
-
<TouchableOpacity onPress={closeFullscreen}>
|
|
78
|
-
<Text style={styles.closeBtn}>✕ Close</Text>
|
|
79
|
-
</TouchableOpacity>
|
|
80
|
-
</View>
|
|
81
|
-
|
|
82
|
-
{/* Zoomable Table */}
|
|
83
|
-
<PinchGestureHandler
|
|
84
|
-
onGestureEvent={(e) => setScale(e.nativeEvent.scale)}
|
|
85
|
-
onHandlerStateChange={(e) => {
|
|
86
|
-
if (e.nativeEvent.state === State.END) {
|
|
87
|
-
let newScale = e.nativeEvent.scale;
|
|
88
|
-
newScale = Math.max(0.8, Math.min(newScale, 3));
|
|
89
|
-
setScale(newScale);
|
|
90
|
-
}
|
|
91
|
-
}}
|
|
92
|
-
>
|
|
93
|
-
<View style={styles.zoomContainer}>
|
|
94
|
-
<View style={{ transform: [{ scale }] }}>
|
|
95
|
-
<FrozenTableReport1A rows={rows} isFullscreen />
|
|
96
|
-
</View>
|
|
97
|
-
</View>
|
|
98
|
-
</PinchGestureHandler>
|
|
99
|
-
</View>
|
|
100
|
-
</Modal>
|
|
52
|
+
rows={rows}
|
|
53
|
+
onClose={() => setFullscreen(false)}
|
|
54
|
+
/>
|
|
101
55
|
</>
|
|
102
56
|
);
|
|
103
57
|
};
|
|
104
58
|
|
|
105
|
-
|
|
106
|
-
backButton: { fontSize: 20, color: '#1e88e5', marginBottom: 16, fontWeight: '600' },
|
|
107
|
-
title: { fontSize: 18, fontWeight: '700', textAlign: 'center', marginVertical: 16 },
|
|
108
|
-
fullscreenBtn: {
|
|
109
|
-
alignSelf: 'flex-end',
|
|
110
|
-
backgroundColor: '#1e88e5',
|
|
111
|
-
paddingHorizontal: 16,
|
|
112
|
-
paddingVertical: 10,
|
|
113
|
-
borderRadius: 8,
|
|
114
|
-
marginBottom: 16,
|
|
115
|
-
},
|
|
116
|
-
fullscreenText: { color: '#fff', fontWeight: '700' },
|
|
117
|
-
sectionTitle: { fontSize: 16, fontWeight: '700', marginVertical: 16 },
|
|
118
|
-
|
|
119
|
-
modalContainer: { flex: 1, backgroundColor: '#000' },
|
|
120
|
-
modalHeader: {
|
|
121
|
-
flexDirection: 'row',
|
|
122
|
-
justifyContent: 'space-between',
|
|
123
|
-
alignItems: 'center',
|
|
124
|
-
padding: 16,
|
|
125
|
-
backgroundColor: '#111',
|
|
126
|
-
borderBottomWidth: 1,
|
|
127
|
-
borderColor: '#333',
|
|
128
|
-
},
|
|
129
|
-
rotateHint: { color: '#fff', fontSize: 16, fontWeight: '700' },
|
|
130
|
-
closeBtn: { color: '#fff', fontSize: 28, fontWeight: '300' },
|
|
131
|
-
zoomContainer: {
|
|
132
|
-
flex: 1,
|
|
133
|
-
justifyContent: 'center',
|
|
134
|
-
alignItems: 'center',
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
export default Report1AScreen;
|
|
59
|
+
export default Report1AScreen;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
// src/screens/Report2AScreen.jsx
|
|
2
1
|
import React, { useEffect, useState } from 'react';
|
|
3
2
|
import {
|
|
4
3
|
ScrollView,
|
|
5
4
|
Text,
|
|
6
5
|
TouchableOpacity,
|
|
7
6
|
View,
|
|
8
|
-
ActivityIndicator,
|
|
9
7
|
StyleSheet,
|
|
8
|
+
ActivityIndicator,
|
|
10
9
|
} from 'react-native';
|
|
11
10
|
import { filterChartByMonths } from '../utils/filterChartByMonths';
|
|
12
11
|
import { getDivisions, getTable, getLine, getBar } from '../api/report2Fetcher';
|
|
@@ -16,6 +15,19 @@ import FrozenTableReport2A from '../components/FrozenTableReport2A';
|
|
|
16
15
|
import SvgLineChartCompact from '../components/SvgLineChartCompact';
|
|
17
16
|
import SvgBarLineChartCompact from '../components/SvgBarLineChartCompact';
|
|
18
17
|
|
|
18
|
+
// Proper SVG Funnel Icon Component (clean black filter icon)
|
|
19
|
+
const FunnelIcon = () => (
|
|
20
|
+
<View style={styles.funnelIcon}>
|
|
21
|
+
<View style={styles.funnelTop} />
|
|
22
|
+
<View style={styles.funnelBody} />
|
|
23
|
+
<View style={styles.funnelLines}>
|
|
24
|
+
<View style={styles.line1} />
|
|
25
|
+
<View style={styles.line2} />
|
|
26
|
+
<View style={styles.line3} />
|
|
27
|
+
</View>
|
|
28
|
+
</View>
|
|
29
|
+
);
|
|
30
|
+
|
|
19
31
|
const Report2AScreen = ({ api, token, onBack }) => {
|
|
20
32
|
const [monthsModal, setMonthsModal] = useState(false);
|
|
21
33
|
const [divisionModal, setDivisionModal] = useState(false);
|
|
@@ -31,25 +43,18 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
31
43
|
const [loading, setLoading] = useState(true);
|
|
32
44
|
const [error, setError] = useState(null);
|
|
33
45
|
|
|
34
|
-
// Fetch divisions
|
|
35
46
|
useEffect(() => {
|
|
36
47
|
if (!api?.divisions || !token) return;
|
|
37
48
|
|
|
38
49
|
getDivisions(api.divisions, token)
|
|
39
50
|
.then((d) => {
|
|
40
51
|
setDivisions(d);
|
|
41
|
-
if (d.length > 0)
|
|
42
|
-
setDivision(d[0].code);
|
|
43
|
-
}
|
|
52
|
+
if (d.length > 0) setDivision(d[0].code);
|
|
44
53
|
})
|
|
45
|
-
.catch((
|
|
46
|
-
|
|
47
|
-
setError('Failed to load divisions');
|
|
48
|
-
setLoading(false);
|
|
49
|
-
});
|
|
54
|
+
.catch(() => setError('Failed to load divisions'))
|
|
55
|
+
.finally(() => setLoading(false));
|
|
50
56
|
}, [api?.divisions, token]);
|
|
51
57
|
|
|
52
|
-
// Fetch data when division changes
|
|
53
58
|
useEffect(() => {
|
|
54
59
|
if (!division || !api || !token) return;
|
|
55
60
|
|
|
@@ -57,106 +62,100 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
57
62
|
setError(null);
|
|
58
63
|
|
|
59
64
|
Promise.all([
|
|
60
|
-
getTable(api.table, division, token)
|
|
61
|
-
getLine(api.line, division, token)
|
|
62
|
-
getBar(api.bar, division, token)
|
|
65
|
+
getTable(api.table, division, token),
|
|
66
|
+
getLine(api.line, division, token),
|
|
67
|
+
getBar(api.bar, division, token),
|
|
63
68
|
])
|
|
64
69
|
.then(([t, l, b]) => {
|
|
65
|
-
if (!t || !l || !b) {
|
|
66
|
-
setError('Failed to load report data');
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
70
|
setTable(t);
|
|
71
71
|
setLine(l);
|
|
72
72
|
setBar(b);
|
|
73
73
|
setMonths(l.labels || []);
|
|
74
74
|
setSelectedMonths(l.labels || []);
|
|
75
75
|
})
|
|
76
|
-
.catch((
|
|
77
|
-
|
|
78
|
-
setError('Network error or data unavailable');
|
|
79
|
-
})
|
|
80
|
-
.finally(() => {
|
|
81
|
-
setLoading(false);
|
|
82
|
-
});
|
|
76
|
+
.catch(() => setError('Failed to load data'))
|
|
77
|
+
.finally(() => setLoading(false));
|
|
83
78
|
}, [division, api, token]);
|
|
84
79
|
|
|
85
|
-
// Loading State
|
|
86
80
|
if (loading) {
|
|
87
81
|
return (
|
|
88
82
|
<View style={styles.center}>
|
|
89
|
-
<ActivityIndicator size="large" color="#
|
|
90
|
-
<Text style={styles.loadingText}>Loading
|
|
83
|
+
<ActivityIndicator size="large" color="#000" />
|
|
84
|
+
<Text style={styles.loadingText}>Loading...</Text>
|
|
91
85
|
</View>
|
|
92
86
|
);
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
// Error State
|
|
96
89
|
if (error) {
|
|
97
90
|
return (
|
|
98
91
|
<View style={styles.center}>
|
|
99
92
|
<Text style={styles.errorText}>{error}</Text>
|
|
100
93
|
<TouchableOpacity onPress={onBack}>
|
|
101
|
-
<Text style={styles.backLink}>←
|
|
94
|
+
<Text style={styles.backLink}>← Back to Reports</Text>
|
|
102
95
|
</TouchableOpacity>
|
|
103
96
|
</View>
|
|
104
97
|
);
|
|
105
98
|
}
|
|
106
99
|
|
|
107
|
-
// Safety check
|
|
108
100
|
if (!table || !line || !bar || !division) {
|
|
109
|
-
return
|
|
110
|
-
<View style={styles.center}>
|
|
111
|
-
<Text>No data available</Text>
|
|
112
|
-
</View>
|
|
113
|
-
);
|
|
101
|
+
return <View style={styles.center}><Text>No data available</Text></View>;
|
|
114
102
|
}
|
|
115
103
|
|
|
116
|
-
const
|
|
117
|
-
selectedMonths.includes(r.monthLabel)
|
|
118
|
-
);
|
|
104
|
+
const currentDivisionName = divisions.find(d => d.code === division)?.displayName || 'Division';
|
|
119
105
|
|
|
106
|
+
const filteredRows = table.rows.filter(r => selectedMonths.includes(r.monthLabel));
|
|
120
107
|
const filteredLine = filterChartByMonths(line, selectedMonths);
|
|
121
108
|
const filteredBar = filterChartByMonths(bar, selectedMonths);
|
|
122
109
|
|
|
123
110
|
return (
|
|
124
|
-
<
|
|
125
|
-
{/*
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
111
|
+
<View style={styles.container}>
|
|
112
|
+
{/* HEADER WITH BACK ICON + HEADING */}
|
|
113
|
+
<View style={styles.header}>
|
|
114
|
+
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
|
115
|
+
<Text style={styles.backIcon}>←</Text>
|
|
116
|
+
</TouchableOpacity>
|
|
117
|
+
|
|
118
|
+
<Text style={styles.headerTitle}>
|
|
119
|
+
{currentDivisionName} Transportation
|
|
120
|
+
</Text>
|
|
121
|
+
</View>
|
|
122
|
+
|
|
123
|
+
{/* FILTERS - BLACK TEXT + PROPER FUNNEL ICON */}
|
|
124
|
+
<View style={styles.filterBar}>
|
|
125
|
+
<TouchableOpacity
|
|
126
|
+
onPress={() => setMonthsModal(true)}
|
|
127
|
+
style={styles.filterItem}
|
|
128
|
+
>
|
|
129
|
+
<FunnelIcon />
|
|
130
|
+
<Text style={styles.filterText}>
|
|
131
|
+
Months ({selectedMonths.length}/{months.length})
|
|
132
|
+
</Text>
|
|
134
133
|
</TouchableOpacity>
|
|
135
134
|
|
|
136
135
|
<TouchableOpacity
|
|
137
136
|
onPress={() => setDivisionModal(true)}
|
|
138
|
-
style={
|
|
137
|
+
style={styles.filterItem}
|
|
139
138
|
>
|
|
139
|
+
<FunnelIcon />
|
|
140
140
|
<Text style={styles.filterText}>
|
|
141
|
-
|
|
141
|
+
{currentDivisionName}
|
|
142
142
|
</Text>
|
|
143
143
|
</TouchableOpacity>
|
|
144
144
|
</View>
|
|
145
145
|
|
|
146
|
-
{
|
|
147
|
-
|
|
146
|
+
<ScrollView style={styles.content}>
|
|
147
|
+
<FrozenTableReport2A rows={filteredRows} />
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
</View>
|
|
149
|
+
<View style={styles.chartContainer}>
|
|
150
|
+
<SvgLineChartCompact data={filteredLine} />
|
|
151
|
+
</View>
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
</
|
|
153
|
+
<View style={styles.chartContainer}>
|
|
154
|
+
<SvgBarLineChartCompact data={filteredBar} />
|
|
155
|
+
</View>
|
|
156
|
+
</ScrollView>
|
|
158
157
|
|
|
159
|
-
{/*
|
|
158
|
+
{/* MODALS */}
|
|
160
159
|
<MonthFilterModal
|
|
161
160
|
visible={monthsModal}
|
|
162
161
|
months={months}
|
|
@@ -172,50 +171,91 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
172
171
|
onSelect={setDivision}
|
|
173
172
|
onClose={() => setDivisionModal(false)}
|
|
174
173
|
/>
|
|
175
|
-
</
|
|
174
|
+
</View>
|
|
176
175
|
);
|
|
177
176
|
};
|
|
178
177
|
|
|
179
178
|
const styles = StyleSheet.create({
|
|
180
|
-
container: {
|
|
181
|
-
center: {
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
container: { flex: 1, backgroundColor: '#f8f9fa' },
|
|
180
|
+
center: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
|
|
181
|
+
loadingText: { marginTop: 12, fontSize: 16, color: '#000' },
|
|
182
|
+
errorText: { fontSize: 16, color: '#d32f2f', textAlign: 'center', marginBottom: 20 },
|
|
183
|
+
backLink: { fontSize: 18, color: '#000', fontWeight: '600' },
|
|
184
|
+
|
|
185
|
+
header: {
|
|
186
|
+
flexDirection: 'row',
|
|
184
187
|
alignItems: 'center',
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
color: '#555',
|
|
188
|
+
paddingHorizontal: 16,
|
|
189
|
+
paddingVertical: 18,
|
|
190
|
+
backgroundColor: '#fff',
|
|
191
|
+
borderBottomWidth: 1,
|
|
192
|
+
borderColor: '#eee',
|
|
191
193
|
},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
backButton: { paddingRight: 12 },
|
|
195
|
+
backIcon: { fontSize: 28, color: '#000', fontWeight: '300' },
|
|
196
|
+
headerTitle: {
|
|
197
|
+
fontSize: 19,
|
|
198
|
+
fontWeight: '700',
|
|
199
|
+
color: '#000',
|
|
200
|
+
flex: 1,
|
|
195
201
|
textAlign: 'center',
|
|
196
|
-
marginBottom: 20,
|
|
197
|
-
},
|
|
198
|
-
backLink: {
|
|
199
|
-
fontSize: 18,
|
|
200
|
-
color: '#1e88e5',
|
|
201
|
-
fontWeight: '600',
|
|
202
202
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
|
|
204
|
+
filterBar: {
|
|
205
|
+
flexDirection: 'row',
|
|
206
|
+
justifyContent: 'space-around',
|
|
207
|
+
paddingVertical: 14,
|
|
208
|
+
backgroundColor: '#fff',
|
|
209
|
+
borderBottomWidth: 1,
|
|
210
|
+
borderColor: '#eee',
|
|
208
211
|
},
|
|
209
|
-
|
|
212
|
+
filterItem: {
|
|
210
213
|
flexDirection: 'row',
|
|
211
214
|
alignItems: 'center',
|
|
212
|
-
|
|
215
|
+
paddingHorizontal: 16,
|
|
216
|
+
paddingVertical: 8,
|
|
213
217
|
},
|
|
214
218
|
filterText: {
|
|
215
219
|
fontSize: 16,
|
|
216
|
-
color: '#
|
|
220
|
+
color: '#000',
|
|
217
221
|
fontWeight: '600',
|
|
222
|
+
marginLeft: 8,
|
|
218
223
|
},
|
|
224
|
+
|
|
225
|
+
// Proper Funnel Icon (SVG-style with Views)
|
|
226
|
+
funnelIcon: {
|
|
227
|
+
width: 20,
|
|
228
|
+
height: 20,
|
|
229
|
+
justifyContent: 'center',
|
|
230
|
+
alignItems: 'center',
|
|
231
|
+
},
|
|
232
|
+
funnelTop: {
|
|
233
|
+
width: 16,
|
|
234
|
+
height: 4,
|
|
235
|
+
backgroundColor: '#000',
|
|
236
|
+
borderRadius: 2,
|
|
237
|
+
},
|
|
238
|
+
funnelBody: {
|
|
239
|
+
width: 10,
|
|
240
|
+
height: 10,
|
|
241
|
+
borderLeftWidth: 4,
|
|
242
|
+
borderRightWidth: 4,
|
|
243
|
+
borderBottomWidth: 8,
|
|
244
|
+
borderColor: 'transparent',
|
|
245
|
+
borderBottomColor: '#000',
|
|
246
|
+
marginTop: -2,
|
|
247
|
+
},
|
|
248
|
+
funnelLines: {
|
|
249
|
+
position: 'absolute',
|
|
250
|
+
top: 6,
|
|
251
|
+
left: 4,
|
|
252
|
+
right: 4,
|
|
253
|
+
},
|
|
254
|
+
line1: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
255
|
+
line2: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
256
|
+
line3: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
257
|
+
|
|
258
|
+
content: { flex: 1, padding: 12 },
|
|
219
259
|
chartContainer: {
|
|
220
260
|
borderWidth: 1,
|
|
221
261
|
borderColor: '#ddd',
|