@dhiraj0720/report1chart 2.6.5 → 2.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/screens/Report2AScreen.jsx +121 -141
- package/src/screens/icons.js +34 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhiraj0720/report1chart",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.7",
|
|
4
4
|
"main": "src/index.jsx",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo 'No tests'"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"axios": "^1.13.2",
|
|
10
10
|
"react-native-gesture-handler": "^2.30.0",
|
|
11
|
+
"react-native-reanimated": "^4.2.1",
|
|
11
12
|
"react-native-svg": "^15.15.1"
|
|
12
13
|
},
|
|
13
14
|
"peerDependencies": {
|
|
@@ -5,8 +5,12 @@ import {
|
|
|
5
5
|
TouchableOpacity,
|
|
6
6
|
View,
|
|
7
7
|
StyleSheet,
|
|
8
|
+
Modal,
|
|
9
|
+
Dimensions,
|
|
8
10
|
ActivityIndicator,
|
|
11
|
+
Alert,
|
|
9
12
|
} from 'react-native';
|
|
13
|
+
import Svg, { Path } from 'react-native-svg';
|
|
10
14
|
import { filterChartByMonths } from '../utils/filterChartByMonths';
|
|
11
15
|
import { getDivisions, getTable, getLine, getBar } from '../api/report2Fetcher';
|
|
12
16
|
import MonthFilterModal from '../components/MonthFilterModal';
|
|
@@ -15,22 +19,11 @@ import FrozenTableReport2A from '../components/FrozenTableReport2A';
|
|
|
15
19
|
import SvgLineChartCompact from '../components/SvgLineChartCompact';
|
|
16
20
|
import SvgBarLineChartCompact from '../components/SvgBarLineChartCompact';
|
|
17
21
|
|
|
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
|
-
|
|
31
22
|
const Report2AScreen = ({ api, token, onBack }) => {
|
|
32
23
|
const [monthsModal, setMonthsModal] = useState(false);
|
|
33
24
|
const [divisionModal, setDivisionModal] = useState(false);
|
|
25
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
26
|
+
const [zoomScale, setZoomScale] = useState(1);
|
|
34
27
|
|
|
35
28
|
const [months, setMonths] = useState([]);
|
|
36
29
|
const [selectedMonths, setSelectedMonths] = useState([]);
|
|
@@ -41,26 +34,19 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
41
34
|
const [line, setLine] = useState(null);
|
|
42
35
|
const [bar, setBar] = useState(null);
|
|
43
36
|
const [loading, setLoading] = useState(true);
|
|
44
|
-
const [error, setError] = useState(null);
|
|
45
37
|
|
|
46
38
|
useEffect(() => {
|
|
47
|
-
if (!api?.divisions || !token) return;
|
|
48
|
-
|
|
49
39
|
getDivisions(api.divisions, token)
|
|
50
40
|
.then((d) => {
|
|
51
41
|
setDivisions(d);
|
|
52
42
|
if (d.length > 0) setDivision(d[0].code);
|
|
53
43
|
})
|
|
54
|
-
.catch(() =>
|
|
55
|
-
|
|
56
|
-
}, [api?.divisions, token]);
|
|
44
|
+
.catch(() => Alert.alert('Error', 'Failed to load divisions'));
|
|
45
|
+
}, [api.divisions, token]);
|
|
57
46
|
|
|
58
47
|
useEffect(() => {
|
|
59
|
-
if (!division
|
|
60
|
-
|
|
48
|
+
if (!division) return;
|
|
61
49
|
setLoading(true);
|
|
62
|
-
setError(null);
|
|
63
|
-
|
|
64
50
|
Promise.all([
|
|
65
51
|
getTable(api.table, division, token),
|
|
66
52
|
getLine(api.line, division, token),
|
|
@@ -73,77 +59,105 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
73
59
|
setMonths(l.labels || []);
|
|
74
60
|
setSelectedMonths(l.labels || []);
|
|
75
61
|
})
|
|
76
|
-
.catch(() => setError('Failed to load data'))
|
|
77
62
|
.finally(() => setLoading(false));
|
|
78
|
-
}, [division
|
|
63
|
+
}, [division]);
|
|
79
64
|
|
|
80
65
|
if (loading) {
|
|
81
66
|
return (
|
|
82
67
|
<View style={styles.center}>
|
|
83
68
|
<ActivityIndicator size="large" color="#000" />
|
|
84
|
-
<Text style={styles.loadingText}>Loading...</Text>
|
|
85
|
-
</View>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (error) {
|
|
90
|
-
return (
|
|
91
|
-
<View style={styles.center}>
|
|
92
|
-
<Text style={styles.errorText}>{error}</Text>
|
|
93
|
-
<TouchableOpacity onPress={onBack}>
|
|
94
|
-
<Text style={styles.backLink}>← Back to Reports</Text>
|
|
95
|
-
</TouchableOpacity>
|
|
96
69
|
</View>
|
|
97
70
|
);
|
|
98
71
|
}
|
|
99
72
|
|
|
100
|
-
if (!table || !line || !bar || !division) {
|
|
101
|
-
return <View style={styles.center}><Text>No data available</Text></View>;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
73
|
const currentDivisionName = divisions.find(d => d.code === division)?.displayName || 'Division';
|
|
105
74
|
|
|
106
75
|
const filteredRows = table.rows.filter(r => selectedMonths.includes(r.monthLabel));
|
|
107
76
|
const filteredLine = filterChartByMonths(line, selectedMonths);
|
|
108
77
|
const filteredBar = filterChartByMonths(bar, selectedMonths);
|
|
109
78
|
|
|
79
|
+
// Detect landscape
|
|
80
|
+
const { width, height } = Dimensions.get('window');
|
|
81
|
+
const isLandscape = width > height;
|
|
82
|
+
|
|
110
83
|
return (
|
|
111
84
|
<View style={styles.container}>
|
|
112
|
-
{/* HEADER
|
|
85
|
+
{/* HEADER */}
|
|
113
86
|
<View style={styles.header}>
|
|
114
|
-
<TouchableOpacity onPress={onBack}
|
|
87
|
+
<TouchableOpacity onPress={onBack}>
|
|
115
88
|
<Text style={styles.backIcon}>←</Text>
|
|
116
89
|
</TouchableOpacity>
|
|
117
|
-
|
|
118
|
-
<Text style={styles.headerTitle}>
|
|
119
|
-
{currentDivisionName} Transportation
|
|
120
|
-
</Text>
|
|
90
|
+
<Text style={styles.headerTitle}>{currentDivisionName} Transportation</Text>
|
|
121
91
|
</View>
|
|
122
92
|
|
|
123
|
-
{/* FILTERS
|
|
93
|
+
{/* FILTERS */}
|
|
124
94
|
<View style={styles.filterBar}>
|
|
125
|
-
<TouchableOpacity
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
95
|
+
<TouchableOpacity onPress={() => setMonthsModal(true)} style={styles.filterItem}>
|
|
96
|
+
{/* FILTER FUNNEL ICON */}
|
|
97
|
+
<Svg width={22} height={22} viewBox="0 0 24 24" fill="none">
|
|
98
|
+
<Path
|
|
99
|
+
d="M20 4H4V6.5L12 14.5L20 6.5V4Z"
|
|
100
|
+
stroke="#000"
|
|
101
|
+
strokeWidth={2}
|
|
102
|
+
strokeLinecap="round"
|
|
103
|
+
strokeLinejoin="round"
|
|
104
|
+
/>
|
|
105
|
+
<Path
|
|
106
|
+
d="M8 11V19L16 19V11"
|
|
107
|
+
stroke="#000"
|
|
108
|
+
strokeWidth={2}
|
|
109
|
+
strokeLinecap="round"
|
|
110
|
+
strokeLinejoin="round"
|
|
111
|
+
/>
|
|
112
|
+
</Svg>
|
|
113
|
+
<Text style={styles.filterText}>Months ({selectedMonths.length}/{months.length})</Text>
|
|
133
114
|
</TouchableOpacity>
|
|
134
115
|
|
|
135
|
-
<TouchableOpacity
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
116
|
+
<TouchableOpacity onPress={() => setDivisionModal(true)} style={styles.filterItem}>
|
|
117
|
+
{/* SAME FILTER ICON */}
|
|
118
|
+
<Svg width={22} height={22} viewBox="0 0 24 24" fill="none">
|
|
119
|
+
<Path
|
|
120
|
+
d="M20 4H4V6.5L12 14.5L20 6.5V4Z"
|
|
121
|
+
stroke="#000"
|
|
122
|
+
strokeWidth={2}
|
|
123
|
+
strokeLinecap="round"
|
|
124
|
+
strokeLinejoin="round"
|
|
125
|
+
/>
|
|
126
|
+
<Path
|
|
127
|
+
d="M8 11V19L16 19V11"
|
|
128
|
+
stroke="#000"
|
|
129
|
+
strokeWidth={2}
|
|
130
|
+
strokeLinecap="round"
|
|
131
|
+
strokeLinejoin="round"
|
|
132
|
+
/>
|
|
133
|
+
</Svg>
|
|
134
|
+
<Text style={styles.filterText}>{currentDivisionName}</Text>
|
|
143
135
|
</TouchableOpacity>
|
|
144
136
|
</View>
|
|
145
137
|
|
|
146
138
|
<ScrollView style={styles.content}>
|
|
139
|
+
{/* FULL SCREEN ICON ABOVE TABLE */}
|
|
140
|
+
<TouchableOpacity
|
|
141
|
+
onPress={() => {
|
|
142
|
+
setFullscreen(true);
|
|
143
|
+
setZoomScale(1);
|
|
144
|
+
Alert.alert('Tip', 'Rotate your device to landscape for full view');
|
|
145
|
+
}}
|
|
146
|
+
style={styles.fullScreenBtn}
|
|
147
|
+
>
|
|
148
|
+
{/* FULL SCREEN EXPAND ICON */}
|
|
149
|
+
<Svg width={28} height={28} viewBox="0 0 24 24" fill="none">
|
|
150
|
+
<Path
|
|
151
|
+
d="M8 3H5C4.44772 3 4 3.44772 4 4V8M20 8V4C20 3.44772 19.5523 3 19 3H16M16 21H19C19.5523 21 20 20.5523 20 20V16M4 16V20C4 20.5523 4.44772 21 5 21H8"
|
|
152
|
+
stroke="#000"
|
|
153
|
+
strokeWidth={2}
|
|
154
|
+
strokeLinecap="round"
|
|
155
|
+
strokeLinejoin="round"
|
|
156
|
+
/>
|
|
157
|
+
</Svg>
|
|
158
|
+
<Text style={styles.fullScreenText}>Full Screen Table</Text>
|
|
159
|
+
</TouchableOpacity>
|
|
160
|
+
|
|
147
161
|
<FrozenTableReport2A rows={filteredRows} />
|
|
148
162
|
|
|
149
163
|
<View style={styles.chartContainer}>
|
|
@@ -155,32 +169,39 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
155
169
|
</View>
|
|
156
170
|
</ScrollView>
|
|
157
171
|
|
|
158
|
-
{/*
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
172
|
+
{/* FULL SCREEN MODAL - SIMPLE ZOOM */}
|
|
173
|
+
<Modal visible={fullscreen} supportedOrientations={['portrait', 'landscape']}>
|
|
174
|
+
<View style={styles.fullModal}>
|
|
175
|
+
<View style={styles.fullHeader}>
|
|
176
|
+
<TouchableOpacity onPress={() => setFullscreen(false)}>
|
|
177
|
+
<Text style={styles.closeFull}>✕</Text>
|
|
178
|
+
</TouchableOpacity>
|
|
179
|
+
<Text style={styles.fullHint}>Rotate to landscape • Pinch to zoom</Text>
|
|
180
|
+
</View>
|
|
181
|
+
|
|
182
|
+
<ScrollView
|
|
183
|
+
maximumZoomScale={3}
|
|
184
|
+
minimumZoomScale={0.8}
|
|
185
|
+
centerContent
|
|
186
|
+
contentContainerStyle={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}
|
|
187
|
+
>
|
|
188
|
+
<View style={{ transform: [{ scale: isLandscape ? 1.4 : 1 }] }}>
|
|
189
|
+
<FrozenTableReport2A rows={table.rows} isFullscreen />
|
|
190
|
+
</View>
|
|
191
|
+
</ScrollView>
|
|
192
|
+
</View>
|
|
193
|
+
</Modal>
|
|
166
194
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
selected={division}
|
|
171
|
-
onSelect={setDivision}
|
|
172
|
-
onClose={() => setDivisionModal(false)}
|
|
173
|
-
/>
|
|
195
|
+
{/* Existing modals */}
|
|
196
|
+
<MonthFilterModal visible={monthsModal} months={months} selected={selectedMonths} onApply={setSelectedMonths} onClose={() => setMonthsModal(false)} />
|
|
197
|
+
<DivisionFilterModal visible={divisionModal} divisions={divisions} selected={division} onSelect={setDivision} onClose={() => setDivisionModal(false)} />
|
|
174
198
|
</View>
|
|
175
199
|
);
|
|
176
200
|
};
|
|
177
201
|
|
|
178
202
|
const styles = StyleSheet.create({
|
|
179
203
|
container: { flex: 1, backgroundColor: '#f8f9fa' },
|
|
180
|
-
center: { flex: 1, justifyContent: 'center', alignItems: 'center'
|
|
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' },
|
|
204
|
+
center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
184
205
|
|
|
185
206
|
header: {
|
|
186
207
|
flexDirection: 'row',
|
|
@@ -191,15 +212,8 @@ const styles = StyleSheet.create({
|
|
|
191
212
|
borderBottomWidth: 1,
|
|
192
213
|
borderColor: '#eee',
|
|
193
214
|
},
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
headerTitle: {
|
|
197
|
-
fontSize: 19,
|
|
198
|
-
fontWeight: '700',
|
|
199
|
-
color: '#000',
|
|
200
|
-
flex: 1,
|
|
201
|
-
textAlign: 'center',
|
|
202
|
-
},
|
|
215
|
+
backIcon: { fontSize: 28, color: '#000' },
|
|
216
|
+
headerTitle: { fontSize: 19, fontWeight: '700', color: '#000', flex: 1, textAlign: 'center' },
|
|
203
217
|
|
|
204
218
|
filterBar: {
|
|
205
219
|
flexDirection: 'row',
|
|
@@ -209,61 +223,27 @@ const styles = StyleSheet.create({
|
|
|
209
223
|
borderBottomWidth: 1,
|
|
210
224
|
borderColor: '#eee',
|
|
211
225
|
},
|
|
212
|
-
filterItem: {
|
|
226
|
+
filterItem: { flexDirection: 'row', alignItems: 'center' },
|
|
227
|
+
filterText: { fontSize: 16, color: '#000', fontWeight: '600', marginLeft: 8 },
|
|
228
|
+
|
|
229
|
+
fullScreenBtn: {
|
|
213
230
|
flexDirection: 'row',
|
|
214
231
|
alignItems: 'center',
|
|
215
|
-
paddingHorizontal: 16,
|
|
216
|
-
paddingVertical: 8,
|
|
217
|
-
},
|
|
218
|
-
filterText: {
|
|
219
|
-
fontSize: 16,
|
|
220
|
-
color: '#000',
|
|
221
|
-
fontWeight: '600',
|
|
222
|
-
marginLeft: 8,
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
// Proper Funnel Icon (SVG-style with Views)
|
|
226
|
-
funnelIcon: {
|
|
227
|
-
width: 20,
|
|
228
|
-
height: 20,
|
|
229
232
|
justifyContent: 'center',
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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,
|
|
233
|
+
padding: 14,
|
|
234
|
+
backgroundColor: '#f0f0f0',
|
|
235
|
+
borderRadius: 8,
|
|
236
|
+
marginVertical: 12,
|
|
253
237
|
},
|
|
254
|
-
|
|
255
|
-
line2: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
256
|
-
line3: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
238
|
+
fullScreenText: { marginLeft: 10, fontSize: 16, color: '#000', fontWeight: '600' },
|
|
257
239
|
|
|
258
240
|
content: { flex: 1, padding: 12 },
|
|
259
|
-
chartContainer: {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
backgroundColor: '#fff',
|
|
266
|
-
},
|
|
241
|
+
chartContainer: { borderWidth: 1, borderColor: '#ddd', borderRadius: 10, overflow: 'hidden', marginVertical: 12, backgroundColor: '#fff' },
|
|
242
|
+
|
|
243
|
+
fullModal: { flex: 1, backgroundColor: '#fff' },
|
|
244
|
+
fullHeader: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', padding: 16, backgroundColor: '#f8f8f8', position: 'relative' },
|
|
245
|
+
closeFull: { position: 'absolute', left: 16, fontSize: 28, color: '#000' },
|
|
246
|
+
fullHint: { fontSize: 15, color: '#000' },
|
|
267
247
|
});
|
|
268
248
|
|
|
269
249
|
export default Report2AScreen;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Svg, { Path } from 'react-native-svg';
|
|
2
|
+
|
|
3
|
+
// Filter Funnel Icon (exact match to your image)
|
|
4
|
+
const FilterIcon = ({ size = 20, color = '#000' }) => (
|
|
5
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
6
|
+
<Path
|
|
7
|
+
d="M20 4H4V6.5L12 14.5L20 6.5V4Z"
|
|
8
|
+
stroke={color}
|
|
9
|
+
strokeWidth={2}
|
|
10
|
+
strokeLinecap="round"
|
|
11
|
+
strokeLinejoin="round"
|
|
12
|
+
/>
|
|
13
|
+
<Path
|
|
14
|
+
d="M8 11V19L16 19V11"
|
|
15
|
+
stroke={color}
|
|
16
|
+
strokeWidth={2}
|
|
17
|
+
strokeLinecap="round"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
|
+
/>
|
|
20
|
+
</Svg>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Full Screen Expand Icon
|
|
24
|
+
const FullScreenIcon = ({ size = 24, color = '#000' }) => (
|
|
25
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
26
|
+
<Path
|
|
27
|
+
d="M8 3H5C4.44772 3 4 3.44772 4 4V8M20 8V4C20 3.44772 19.5523 3 19 3H16M16 21H19C19.5523 21 20 20.5523 20 20V16M4 16V20C4 20.5523 4.44772 21 5 21H8"
|
|
28
|
+
stroke={color}
|
|
29
|
+
strokeWidth={2}
|
|
30
|
+
strokeLinecap="round"
|
|
31
|
+
strokeLinejoin="round"
|
|
32
|
+
/>
|
|
33
|
+
</Svg>
|
|
34
|
+
);
|