@dhiraj0720/report1chart 2.6.6 → 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 +96 -164
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": {
|
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
Modal,
|
|
9
9
|
Dimensions,
|
|
10
10
|
ActivityIndicator,
|
|
11
|
+
Alert,
|
|
11
12
|
} from 'react-native';
|
|
12
|
-
import {
|
|
13
|
-
import Svg from 'react-native-svg'; // if not imported globally
|
|
13
|
+
import Svg, { Path } from 'react-native-svg';
|
|
14
14
|
import { filterChartByMonths } from '../utils/filterChartByMonths';
|
|
15
15
|
import { getDivisions, getTable, getLine, getBar } from '../api/report2Fetcher';
|
|
16
16
|
import MonthFilterModal from '../components/MonthFilterModal';
|
|
@@ -18,57 +18,35 @@ import DivisionFilterModal from '../components/DivisionFilterModal';
|
|
|
18
18
|
import FrozenTableReport2A from '../components/FrozenTableReport2A';
|
|
19
19
|
import SvgLineChartCompact from '../components/SvgLineChartCompact';
|
|
20
20
|
import SvgBarLineChartCompact from '../components/SvgBarLineChartCompact';
|
|
21
|
-
import { FilterIcon, FullScreenIcon } from './icons'; // or inline
|
|
22
|
-
|
|
23
|
-
// Proper SVG Funnel Icon Component (clean black filter icon)
|
|
24
|
-
const FunnelIcon = () => (
|
|
25
|
-
<View style={styles.funnelIcon}>
|
|
26
|
-
<View style={styles.funnelTop} />
|
|
27
|
-
<View style={styles.funnelBody} />
|
|
28
|
-
<View style={styles.funnelLines}>
|
|
29
|
-
<View style={styles.line1} />
|
|
30
|
-
<View style={styles.line2} />
|
|
31
|
-
<View style={styles.line3} />
|
|
32
|
-
</View>
|
|
33
|
-
</View>
|
|
34
|
-
);
|
|
35
21
|
|
|
36
22
|
const Report2AScreen = ({ api, token, onBack }) => {
|
|
37
23
|
const [monthsModal, setMonthsModal] = useState(false);
|
|
38
24
|
const [divisionModal, setDivisionModal] = useState(false);
|
|
25
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
26
|
+
const [zoomScale, setZoomScale] = useState(1);
|
|
39
27
|
|
|
40
28
|
const [months, setMonths] = useState([]);
|
|
41
29
|
const [selectedMonths, setSelectedMonths] = useState([]);
|
|
42
30
|
const [divisions, setDivisions] = useState([]);
|
|
43
31
|
const [division, setDivision] = useState(null);
|
|
44
|
-
const [fullscreen, setFullscreen] = useState(false);
|
|
45
|
-
const [zoomScale, setZoomScale] = useState(1);
|
|
46
32
|
|
|
47
33
|
const [table, setTable] = useState(null);
|
|
48
34
|
const [line, setLine] = useState(null);
|
|
49
35
|
const [bar, setBar] = useState(null);
|
|
50
36
|
const [loading, setLoading] = useState(true);
|
|
51
|
-
const [error, setError] = useState(null);
|
|
52
37
|
|
|
53
|
-
|
|
54
38
|
useEffect(() => {
|
|
55
|
-
if (!api?.divisions || !token) return;
|
|
56
|
-
|
|
57
39
|
getDivisions(api.divisions, token)
|
|
58
40
|
.then((d) => {
|
|
59
41
|
setDivisions(d);
|
|
60
42
|
if (d.length > 0) setDivision(d[0].code);
|
|
61
43
|
})
|
|
62
|
-
.catch(() =>
|
|
63
|
-
|
|
64
|
-
}, [api?.divisions, token]);
|
|
44
|
+
.catch(() => Alert.alert('Error', 'Failed to load divisions'));
|
|
45
|
+
}, [api.divisions, token]);
|
|
65
46
|
|
|
66
47
|
useEffect(() => {
|
|
67
|
-
if (!division
|
|
68
|
-
|
|
48
|
+
if (!division) return;
|
|
69
49
|
setLoading(true);
|
|
70
|
-
setError(null);
|
|
71
|
-
|
|
72
50
|
Promise.all([
|
|
73
51
|
getTable(api.table, division, token),
|
|
74
52
|
getLine(api.line, division, token),
|
|
@@ -81,61 +59,78 @@ const Report2AScreen = ({ api, token, onBack }) => {
|
|
|
81
59
|
setMonths(l.labels || []);
|
|
82
60
|
setSelectedMonths(l.labels || []);
|
|
83
61
|
})
|
|
84
|
-
.catch(() => setError('Failed to load data'))
|
|
85
62
|
.finally(() => setLoading(false));
|
|
86
|
-
}, [division
|
|
63
|
+
}, [division]);
|
|
87
64
|
|
|
88
65
|
if (loading) {
|
|
89
66
|
return (
|
|
90
67
|
<View style={styles.center}>
|
|
91
68
|
<ActivityIndicator size="large" color="#000" />
|
|
92
|
-
<Text style={styles.loadingText}>Loading...</Text>
|
|
93
|
-
</View>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (error) {
|
|
98
|
-
return (
|
|
99
|
-
<View style={styles.center}>
|
|
100
|
-
<Text style={styles.errorText}>{error}</Text>
|
|
101
|
-
<TouchableOpacity onPress={onBack}>
|
|
102
|
-
<Text style={styles.backLink}>‹ Back to Reports</Text>
|
|
103
|
-
</TouchableOpacity>
|
|
104
69
|
</View>
|
|
105
70
|
);
|
|
106
71
|
}
|
|
107
72
|
|
|
108
|
-
|
|
109
|
-
return <View style={styles.center}><Text>No data available</Text></View>;
|
|
110
|
-
}
|
|
73
|
+
const currentDivisionName = divisions.find(d => d.code === division)?.displayName || 'Division';
|
|
111
74
|
|
|
112
|
-
const currentDivisionName = divisions.find(d => d.code === division)?.displayName || 'Division';
|
|
113
75
|
const filteredRows = table.rows.filter(r => selectedMonths.includes(r.monthLabel));
|
|
114
76
|
const filteredLine = filterChartByMonths(line, selectedMonths);
|
|
115
77
|
const filteredBar = filterChartByMonths(bar, selectedMonths);
|
|
116
78
|
|
|
79
|
+
// Detect landscape
|
|
80
|
+
const { width, height } = Dimensions.get('window');
|
|
81
|
+
const isLandscape = width > height;
|
|
82
|
+
|
|
117
83
|
return (
|
|
118
84
|
<View style={styles.container}>
|
|
119
|
-
{/* HEADER
|
|
85
|
+
{/* HEADER */}
|
|
120
86
|
<View style={styles.header}>
|
|
121
|
-
<TouchableOpacity onPress={onBack}
|
|
122
|
-
<Text style={styles.backIcon}
|
|
87
|
+
<TouchableOpacity onPress={onBack}>
|
|
88
|
+
<Text style={styles.backIcon}>←</Text>
|
|
123
89
|
</TouchableOpacity>
|
|
124
|
-
|
|
125
|
-
<Text style={styles.headerTitle}>
|
|
126
|
-
{currentDivisionName} Transportation
|
|
127
|
-
</Text>
|
|
90
|
+
<Text style={styles.headerTitle}>{currentDivisionName} Transportation</Text>
|
|
128
91
|
</View>
|
|
129
92
|
|
|
130
|
-
{/* FILTERS
|
|
93
|
+
{/* FILTERS */}
|
|
131
94
|
<View style={styles.filterBar}>
|
|
132
95
|
<TouchableOpacity onPress={() => setMonthsModal(true)} style={styles.filterItem}>
|
|
133
|
-
|
|
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>
|
|
134
113
|
<Text style={styles.filterText}>Months ({selectedMonths.length}/{months.length})</Text>
|
|
135
114
|
</TouchableOpacity>
|
|
136
115
|
|
|
137
116
|
<TouchableOpacity onPress={() => setDivisionModal(true)} style={styles.filterItem}>
|
|
138
|
-
|
|
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>
|
|
139
134
|
<Text style={styles.filterText}>{currentDivisionName}</Text>
|
|
140
135
|
</TouchableOpacity>
|
|
141
136
|
</View>
|
|
@@ -143,11 +138,24 @@ const currentDivisionName = divisions.find(d => d.code === division)?.displayNam
|
|
|
143
138
|
<ScrollView style={styles.content}>
|
|
144
139
|
{/* FULL SCREEN ICON ABOVE TABLE */}
|
|
145
140
|
<TouchableOpacity
|
|
146
|
-
onPress={() =>
|
|
141
|
+
onPress={() => {
|
|
142
|
+
setFullscreen(true);
|
|
143
|
+
setZoomScale(1);
|
|
144
|
+
Alert.alert('Tip', 'Rotate your device to landscape for full view');
|
|
145
|
+
}}
|
|
147
146
|
style={styles.fullScreenBtn}
|
|
148
147
|
>
|
|
149
|
-
|
|
150
|
-
<
|
|
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>
|
|
151
159
|
</TouchableOpacity>
|
|
152
160
|
|
|
153
161
|
<FrozenTableReport2A rows={filteredRows} />
|
|
@@ -161,60 +169,39 @@ const currentDivisionName = divisions.find(d => d.code === division)?.displayNam
|
|
|
161
169
|
</View>
|
|
162
170
|
</ScrollView>
|
|
163
171
|
|
|
164
|
-
{/*
|
|
165
|
-
|
|
166
|
-
{/* FULL SCREEN MODAL */}
|
|
172
|
+
{/* FULL SCREEN MODAL - SIMPLE ZOOM */}
|
|
167
173
|
<Modal visible={fullscreen} supportedOrientations={['portrait', 'landscape']}>
|
|
168
174
|
<View style={styles.fullModal}>
|
|
169
175
|
<View style={styles.fullHeader}>
|
|
170
176
|
<TouchableOpacity onPress={() => setFullscreen(false)}>
|
|
171
177
|
<Text style={styles.closeFull}>✕</Text>
|
|
172
178
|
</TouchableOpacity>
|
|
173
|
-
<Text style={styles.
|
|
179
|
+
<Text style={styles.fullHint}>Rotate to landscape • Pinch to zoom</Text>
|
|
174
180
|
</View>
|
|
175
181
|
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
}}
|
|
182
|
+
<ScrollView
|
|
183
|
+
maximumZoomScale={3}
|
|
184
|
+
minimumZoomScale={0.8}
|
|
185
|
+
centerContent
|
|
186
|
+
contentContainerStyle={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}
|
|
183
187
|
>
|
|
184
|
-
<View style={
|
|
185
|
-
<
|
|
186
|
-
<FrozenTableReport2A rows={table.rows} isFullscreen /> {/* all rows */}
|
|
187
|
-
</View>
|
|
188
|
+
<View style={{ transform: [{ scale: isLandscape ? 1.4 : 1 }] }}>
|
|
189
|
+
<FrozenTableReport2A rows={table.rows} isFullscreen />
|
|
188
190
|
</View>
|
|
189
|
-
</
|
|
191
|
+
</ScrollView>
|
|
190
192
|
</View>
|
|
191
193
|
</Modal>
|
|
192
194
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
selected={selectedMonths}
|
|
197
|
-
onApply={setSelectedMonths}
|
|
198
|
-
onClose={() => setMonthsModal(false)}
|
|
199
|
-
/>
|
|
200
|
-
|
|
201
|
-
<DivisionFilterModal
|
|
202
|
-
visible={divisionModal}
|
|
203
|
-
divisions={divisions}
|
|
204
|
-
selected={division}
|
|
205
|
-
onSelect={setDivision}
|
|
206
|
-
onClose={() => setDivisionModal(false)}
|
|
207
|
-
/>
|
|
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)} />
|
|
208
198
|
</View>
|
|
209
199
|
);
|
|
210
200
|
};
|
|
211
201
|
|
|
212
202
|
const styles = StyleSheet.create({
|
|
213
203
|
container: { flex: 1, backgroundColor: '#f8f9fa' },
|
|
214
|
-
center: { flex: 1, justifyContent: 'center', alignItems: 'center'
|
|
215
|
-
loadingText: { marginTop: 12, fontSize: 16, color: '#000' },
|
|
216
|
-
errorText: { fontSize: 16, color: '#d32f2f', textAlign: 'center', marginBottom: 20 },
|
|
217
|
-
backLink: { fontSize: 18, color: '#000', fontWeight: '600' },
|
|
204
|
+
center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
218
205
|
|
|
219
206
|
header: {
|
|
220
207
|
flexDirection: 'row',
|
|
@@ -225,15 +212,8 @@ const styles = StyleSheet.create({
|
|
|
225
212
|
borderBottomWidth: 1,
|
|
226
213
|
borderColor: '#eee',
|
|
227
214
|
},
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
headerTitle: {
|
|
231
|
-
fontSize: 19,
|
|
232
|
-
fontWeight: '700',
|
|
233
|
-
color: '#000',
|
|
234
|
-
flex: 1,
|
|
235
|
-
textAlign: 'center',
|
|
236
|
-
},
|
|
215
|
+
backIcon: { fontSize: 28, color: '#000' },
|
|
216
|
+
headerTitle: { fontSize: 19, fontWeight: '700', color: '#000', flex: 1, textAlign: 'center' },
|
|
237
217
|
|
|
238
218
|
filterBar: {
|
|
239
219
|
flexDirection: 'row',
|
|
@@ -243,75 +223,27 @@ const styles = StyleSheet.create({
|
|
|
243
223
|
borderBottomWidth: 1,
|
|
244
224
|
borderColor: '#eee',
|
|
245
225
|
},
|
|
246
|
-
filterItem: {
|
|
247
|
-
|
|
248
|
-
alignItems: 'center',
|
|
249
|
-
paddingHorizontal: 16,
|
|
250
|
-
paddingVertical: 8,
|
|
251
|
-
},
|
|
252
|
-
filterText: {
|
|
253
|
-
fontSize: 16,
|
|
254
|
-
color: '#000',
|
|
255
|
-
fontWeight: '600',
|
|
256
|
-
marginLeft: 8,
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
// Proper Funnel Icon (SVG-style with Views)
|
|
260
|
-
funnelIcon: {
|
|
261
|
-
width: 20,
|
|
262
|
-
height: 20,
|
|
263
|
-
justifyContent: 'center',
|
|
264
|
-
alignItems: 'center',
|
|
265
|
-
},
|
|
266
|
-
funnelTop: {
|
|
267
|
-
width: 16,
|
|
268
|
-
height: 4,
|
|
269
|
-
backgroundColor: '#000',
|
|
270
|
-
borderRadius: 2,
|
|
271
|
-
},
|
|
272
|
-
funnelBody: {
|
|
273
|
-
width: 10,
|
|
274
|
-
height: 10,
|
|
275
|
-
borderLeftWidth: 4,
|
|
276
|
-
borderRightWidth: 4,
|
|
277
|
-
borderBottomWidth: 8,
|
|
278
|
-
borderColor: 'transparent',
|
|
279
|
-
borderBottomColor: '#000',
|
|
280
|
-
marginTop: -2,
|
|
281
|
-
},
|
|
282
|
-
funnelLines: {
|
|
283
|
-
position: 'absolute',
|
|
284
|
-
top: 6,
|
|
285
|
-
left: 4,
|
|
286
|
-
right: 4,
|
|
287
|
-
},
|
|
288
|
-
line1: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
289
|
-
line2: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
290
|
-
line3: { height: 1, backgroundColor: '#000', marginVertical: 2 },
|
|
226
|
+
filterItem: { flexDirection: 'row', alignItems: 'center' },
|
|
227
|
+
filterText: { fontSize: 16, color: '#000', fontWeight: '600', marginLeft: 8 },
|
|
291
228
|
|
|
292
|
-
content: { flex: 1, padding: 12 },
|
|
293
|
-
chartContainer: {
|
|
294
|
-
borderWidth: 1,
|
|
295
|
-
borderColor: '#ddd',
|
|
296
|
-
borderRadius: 10,
|
|
297
|
-
overflow: 'hidden',
|
|
298
|
-
marginVertical: 12,
|
|
299
|
-
backgroundColor: '#fff',
|
|
300
|
-
},
|
|
301
229
|
fullScreenBtn: {
|
|
302
230
|
flexDirection: 'row',
|
|
303
231
|
alignItems: 'center',
|
|
304
232
|
justifyContent: 'center',
|
|
305
|
-
padding:
|
|
306
|
-
|
|
233
|
+
padding: 14,
|
|
234
|
+
backgroundColor: '#f0f0f0',
|
|
235
|
+
borderRadius: 8,
|
|
236
|
+
marginVertical: 12,
|
|
307
237
|
},
|
|
308
|
-
fullScreenText: { marginLeft:
|
|
238
|
+
fullScreenText: { marginLeft: 10, fontSize: 16, color: '#000', fontWeight: '600' },
|
|
239
|
+
|
|
240
|
+
content: { flex: 1, padding: 12 },
|
|
241
|
+
chartContainer: { borderWidth: 1, borderColor: '#ddd', borderRadius: 10, overflow: 'hidden', marginVertical: 12, backgroundColor: '#fff' },
|
|
309
242
|
|
|
310
243
|
fullModal: { flex: 1, backgroundColor: '#fff' },
|
|
311
|
-
fullHeader: { flexDirection: 'row',
|
|
244
|
+
fullHeader: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', padding: 16, backgroundColor: '#f8f8f8', position: 'relative' },
|
|
312
245
|
closeFull: { position: 'absolute', left: 16, fontSize: 28, color: '#000' },
|
|
313
|
-
|
|
314
|
-
zoomView: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
246
|
+
fullHint: { fontSize: 15, color: '#000' },
|
|
315
247
|
});
|
|
316
248
|
|
|
317
249
|
export default Report2AScreen;
|