@suflon/rnmd-reporting 0.0.5 → 0.0.6
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/modules/ManagementReport/components/AnalyticsChartView.tsx +1 -1
- package/src/modules/ManagementReport/components/DetailsListingView.tsx +1 -1
- package/src/modules/ManagementReport/components/DoctorFilterDropdown.tsx +99 -117
- package/src/modules/ManagementReport/components/KpiMetricsGrid.tsx +1 -1
- package/src/modules/ManagementReport/components/ManagementReportHeader.tsx +45 -42
- package/src/modules/ManagementReport/index.tsx +33 -2
- package/src/modules/Reporting/component/ReportingDetail.tsx +17 -3
package/package.json
CHANGED
|
@@ -109,7 +109,7 @@ const SingleChartCard: React.FC<{
|
|
|
109
109
|
loading: boolean;
|
|
110
110
|
}> = ({ detail, rows, loading }) => {
|
|
111
111
|
const isDark = useColorScheme() === 'dark';
|
|
112
|
-
const [timeframe, setTimeframe] = useState<TimeframeOption>('
|
|
112
|
+
const [timeframe, setTimeframe] = useState<TimeframeOption>('Daily');
|
|
113
113
|
const [chartType, setChartType] = useState<'bar' | 'line' | 'trend'>('bar');
|
|
114
114
|
const [fetching, setFetching] = useState(false);
|
|
115
115
|
|
|
@@ -214,7 +214,7 @@ const SingleReportTableCard: React.FC<{
|
|
|
214
214
|
loading,
|
|
215
215
|
}) => {
|
|
216
216
|
const isDark = useColorScheme() === 'dark';
|
|
217
|
-
const [timeframe, setTimeframe] = useState<TimeframeOption>('
|
|
217
|
+
const [timeframe, setTimeframe] = useState<TimeframeOption>('Daily');
|
|
218
218
|
const [layoutMode, setLayoutMode] = useState<'table' | 'card'>('table');
|
|
219
219
|
const [statusFilter, setStatusFilter] = useState<string>('ALL');
|
|
220
220
|
const [fetching, setFetching] = useState(false);
|
|
@@ -12,146 +12,128 @@ import {
|
|
|
12
12
|
import { Icon } from '@suflon/native-ui';
|
|
13
13
|
import { themeColors } from '../../../theme/colors';
|
|
14
14
|
|
|
15
|
-
interface DoctorFilterDropdownProps {
|
|
15
|
+
export interface DoctorFilterDropdownProps {
|
|
16
16
|
selectedDoctor: string;
|
|
17
17
|
onSelectDoctor: (doctor: string) => void;
|
|
18
18
|
availableDoctors: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
export interface DoctorFilterDropdownRef {
|
|
22
|
+
open: (coords?: { x: number; y: number; width: number; height: number }) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DoctorFilterDropdown = React.forwardRef<
|
|
26
|
+
DoctorFilterDropdownRef,
|
|
27
|
+
DoctorFilterDropdownProps
|
|
28
|
+
>(({ selectedDoctor, onSelectDoctor, availableDoctors }, ref) => {
|
|
26
29
|
const colorScheme = useColorScheme();
|
|
27
30
|
const isDark = colorScheme === 'dark';
|
|
28
31
|
const [open, setOpen] = useState(false);
|
|
29
32
|
const [coords, setCoords] = useState<{ x: number; y: number; width: number; height: number }>({
|
|
30
33
|
x: 0,
|
|
31
|
-
y:
|
|
34
|
+
y: 90,
|
|
32
35
|
width: 0,
|
|
33
|
-
height:
|
|
36
|
+
height: 48,
|
|
34
37
|
});
|
|
35
|
-
const buttonRef = useRef<any>(null);
|
|
36
38
|
|
|
37
|
-
const handleOpen = () => {
|
|
38
|
-
|
|
39
|
-
setCoords(
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const handleOpen = (anchorCoords?: { x: number; y: number; width: number; height: number }) => {
|
|
40
|
+
if (anchorCoords) {
|
|
41
|
+
setCoords(anchorCoords);
|
|
42
|
+
}
|
|
43
|
+
setOpen(true);
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
React.useImperativeHandle(ref, () => ({
|
|
47
|
+
open: handleOpen,
|
|
48
|
+
}));
|
|
47
49
|
|
|
48
50
|
const allOptions = ['ALL', ...availableDoctors];
|
|
49
51
|
|
|
50
52
|
return (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
borderWidth: 1,
|
|
86
|
-
borderRadius: 16,
|
|
87
|
-
paddingVertical: 6,
|
|
88
|
-
paddingHorizontal: 4,
|
|
89
|
-
minWidth: 170,
|
|
90
|
-
maxHeight: 250,
|
|
91
|
-
shadowColor: '#000',
|
|
92
|
-
shadowOffset: { width: 0, height: 6 },
|
|
93
|
-
shadowOpacity: isDark ? 0.45 : 0.18,
|
|
94
|
-
shadowRadius: 10,
|
|
95
|
-
elevation: 25,
|
|
96
|
-
zIndex: 99999,
|
|
97
|
-
}}
|
|
98
|
-
>
|
|
99
|
-
<View className="px-2.5 py-1 border-b border-slate-100 dark:border-slate-800 mb-1">
|
|
100
|
-
<Text className="text-[10px] font-extrabold uppercase tracking-wider text-slate-400">
|
|
101
|
-
Filter by Doctor
|
|
102
|
-
</Text>
|
|
103
|
-
</View>
|
|
53
|
+
<Modal
|
|
54
|
+
visible={open}
|
|
55
|
+
transparent={true}
|
|
56
|
+
animationType="none"
|
|
57
|
+
onRequestClose={() => setOpen(false)}
|
|
58
|
+
>
|
|
59
|
+
<TouchableWithoutFeedback onPress={() => setOpen(false)}>
|
|
60
|
+
<View className="flex-1 bg-transparent">
|
|
61
|
+
<View
|
|
62
|
+
style={{
|
|
63
|
+
position: 'absolute',
|
|
64
|
+
top: (coords.y || 90) + (coords.height || 48) + 6,
|
|
65
|
+
right: 16,
|
|
66
|
+
backgroundColor: isDark ? themeColors.slate[900] : themeColors.white,
|
|
67
|
+
borderColor: isDark ? themeColors.slate[700] : themeColors.slate[200],
|
|
68
|
+
borderWidth: 1,
|
|
69
|
+
borderRadius: 16,
|
|
70
|
+
paddingVertical: 6,
|
|
71
|
+
paddingHorizontal: 4,
|
|
72
|
+
minWidth: 180,
|
|
73
|
+
maxHeight: 250,
|
|
74
|
+
shadowColor: '#000',
|
|
75
|
+
shadowOffset: { width: 0, height: 6 },
|
|
76
|
+
shadowOpacity: isDark ? 0.45 : 0.18,
|
|
77
|
+
shadowRadius: 10,
|
|
78
|
+
elevation: 25,
|
|
79
|
+
zIndex: 99999,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<View className="px-2.5 py-1 border-b border-slate-100 dark:border-slate-800 mb-1">
|
|
83
|
+
<Text className="text-[10px] font-extrabold uppercase tracking-wider text-slate-400">
|
|
84
|
+
Filter by Doctor
|
|
85
|
+
</Text>
|
|
86
|
+
</View>
|
|
104
87
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
88
|
+
<ScrollView showsVerticalScrollIndicator={false}>
|
|
89
|
+
{allOptions.map((doc) => {
|
|
90
|
+
const isSelected = doc === selectedDoctor || (doc === 'ALL' && !selectedDoctor);
|
|
91
|
+
const label = doc === 'ALL' ? 'All Doctors' : doc;
|
|
109
92
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
93
|
+
return (
|
|
94
|
+
<TouchableOpacity
|
|
95
|
+
key={doc}
|
|
96
|
+
activeOpacity={0.8}
|
|
97
|
+
onPress={() => {
|
|
98
|
+
onSelectDoctor(doc);
|
|
99
|
+
setOpen(false);
|
|
100
|
+
}}
|
|
101
|
+
style={{
|
|
102
|
+
backgroundColor: isSelected
|
|
103
|
+
? (isDark ? themeColors.slate[800] : themeColors.brand[50])
|
|
104
|
+
: themeColors.transparent,
|
|
105
|
+
borderRadius: 10,
|
|
106
|
+
paddingVertical: 7,
|
|
107
|
+
paddingHorizontal: 8,
|
|
108
|
+
flexDirection: 'row',
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
gap: 6,
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<View style={{ width: 14, alignItems: 'center', justifyContent: 'center' }}>
|
|
114
|
+
{isSelected && (
|
|
115
|
+
<Icon name="check" type="Feather" size={11} color={themeColors.brand[600]} />
|
|
116
|
+
)}
|
|
117
|
+
</View>
|
|
118
|
+
<Text
|
|
118
119
|
style={{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
paddingHorizontal: 8,
|
|
125
|
-
flexDirection: 'row',
|
|
126
|
-
alignItems: 'center',
|
|
127
|
-
gap: 6,
|
|
120
|
+
fontSize: 11,
|
|
121
|
+
fontWeight: isSelected ? '700' : '500',
|
|
122
|
+
color: isSelected
|
|
123
|
+
? (isDark ? themeColors.brand[400] : themeColors.brand[600])
|
|
124
|
+
: (isDark ? themeColors.slate[200] : themeColors.slate[700]),
|
|
128
125
|
}}
|
|
126
|
+
numberOfLines={1}
|
|
129
127
|
>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
style={{
|
|
137
|
-
fontSize: 11,
|
|
138
|
-
fontWeight: isSelected ? '700' : '500',
|
|
139
|
-
color: isSelected
|
|
140
|
-
? (isDark ? themeColors.brand[400] : themeColors.brand[600])
|
|
141
|
-
: (isDark ? themeColors.slate[200] : themeColors.slate[700]),
|
|
142
|
-
}}
|
|
143
|
-
numberOfLines={1}
|
|
144
|
-
>
|
|
145
|
-
{label}
|
|
146
|
-
</Text>
|
|
147
|
-
</TouchableOpacity>
|
|
148
|
-
);
|
|
149
|
-
})}
|
|
150
|
-
</ScrollView>
|
|
151
|
-
</View>
|
|
128
|
+
{label}
|
|
129
|
+
</Text>
|
|
130
|
+
</TouchableOpacity>
|
|
131
|
+
);
|
|
132
|
+
})}
|
|
133
|
+
</ScrollView>
|
|
152
134
|
</View>
|
|
153
|
-
</
|
|
154
|
-
</
|
|
155
|
-
|
|
135
|
+
</View>
|
|
136
|
+
</TouchableWithoutFeedback>
|
|
137
|
+
</Modal>
|
|
156
138
|
);
|
|
157
|
-
};
|
|
139
|
+
});
|
|
@@ -65,7 +65,7 @@ const KpiCardItem: React.FC<{
|
|
|
65
65
|
loading: boolean;
|
|
66
66
|
}> = ({ item, index, data, loading }) => {
|
|
67
67
|
const isDark = useColorScheme() === 'dark';
|
|
68
|
-
const [timeframe, setTimeframe] = useState<TimeframeOption>('
|
|
68
|
+
const [timeframe, setTimeframe] = useState<TimeframeOption>('Daily');
|
|
69
69
|
const [fetching, setFetching] = useState(false);
|
|
70
70
|
|
|
71
71
|
const executeReportItem = useManagementReportStore((s) => s.executeReportItem);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View,
|
|
2
|
+
import { View, useColorScheme } from 'react-native';
|
|
3
3
|
import {
|
|
4
4
|
IManagementReport,
|
|
5
|
-
|
|
5
|
+
Header,
|
|
6
|
+
SearchFilter,
|
|
6
7
|
} from '@suflon/native-ui';
|
|
7
8
|
import { CategoryTabs } from './CategoryTabs';
|
|
8
9
|
import { SubModuleTabs } from './SubModuleTabs';
|
|
@@ -25,6 +26,9 @@ interface ManagementReportHeaderProps {
|
|
|
25
26
|
onSelectDoctor: (doctor: string) => void;
|
|
26
27
|
availableDoctors: string[];
|
|
27
28
|
categoryCounts?: Record<string, number>;
|
|
29
|
+
showFilter?: boolean;
|
|
30
|
+
onFilterPress?: () => void;
|
|
31
|
+
currentFilter?: string;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
export const ManagementReportHeader: React.FC<ManagementReportHeaderProps> = ({
|
|
@@ -41,54 +45,53 @@ export const ManagementReportHeader: React.FC<ManagementReportHeaderProps> = ({
|
|
|
41
45
|
onSelectDoctor,
|
|
42
46
|
availableDoctors,
|
|
43
47
|
categoryCounts,
|
|
48
|
+
showFilter = true,
|
|
49
|
+
onFilterPress,
|
|
50
|
+
currentFilter,
|
|
44
51
|
}) => {
|
|
45
52
|
const isDark = useColorScheme() === 'dark';
|
|
53
|
+
const doctorDropdownRef = React.useRef<any>(null);
|
|
54
|
+
const searchBarRef = React.useRef<any>(null);
|
|
55
|
+
|
|
56
|
+
const handleFilterClick = () => {
|
|
57
|
+
if (onFilterPress) {
|
|
58
|
+
onFilterPress();
|
|
59
|
+
} else {
|
|
60
|
+
searchBarRef.current?.measureInWindow((x: number, y: number, width: number, height: number) => {
|
|
61
|
+
doctorDropdownRef.current?.open({ x, y, width, height });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const isFilterActive = currentFilter || (selectedDoctor && selectedDoctor !== 'ALL' ? 'active' : '');
|
|
46
67
|
|
|
47
68
|
return (
|
|
48
69
|
<View className="border-b border-slate-100 dark:border-slate-800/80 bg-white dark:bg-background-lightBlack px-3.5 pt-2.5 pb-2">
|
|
49
|
-
{/* 1. Top Header Bar
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
className="w-7 h-7 rounded-full bg-slate-100 dark:bg-slate-800 items-center justify-center shrink-0"
|
|
57
|
-
>
|
|
58
|
-
<Icon name="arrow-left" type="Feather" size={14} color={isDark ? '#FFFFFF' : '#64748b'} />
|
|
59
|
-
</TouchableOpacity>
|
|
60
|
-
)}
|
|
61
|
-
|
|
62
|
-
<Text
|
|
63
|
-
style={{ color: isDark ? '#FFFFFF' : '#0F172A' }}
|
|
64
|
-
className="text-[15px] font-extrabold tracking-tight leading-tight flex-1"
|
|
65
|
-
numberOfLines={1}
|
|
66
|
-
>
|
|
67
|
-
{title}
|
|
68
|
-
</Text>
|
|
69
|
-
</View>
|
|
70
|
-
</View>
|
|
70
|
+
{/* 1. Top Header Bar from Suflon Native UI */}
|
|
71
|
+
<Header
|
|
72
|
+
title={title}
|
|
73
|
+
showBackButton={!!onBackPress}
|
|
74
|
+
onBackPress={onBackPress}
|
|
75
|
+
className="px-0 mb-2"
|
|
76
|
+
/>
|
|
71
77
|
|
|
72
|
-
{/* 2.
|
|
73
|
-
<View
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
<Icon name="x" type="Feather" size={13} color="#94a3b8" />
|
|
87
|
-
</TouchableOpacity>
|
|
88
|
-
) : null}
|
|
89
|
-
</View>
|
|
78
|
+
{/* 2. Search Filter from Suflon Native UI with Integrated Filter Modal */}
|
|
79
|
+
<View
|
|
80
|
+
ref={searchBarRef}
|
|
81
|
+
collapsable={false}
|
|
82
|
+
className="w-full mb-2"
|
|
83
|
+
>
|
|
84
|
+
<SearchFilter
|
|
85
|
+
placeholder="Search reports or creator..."
|
|
86
|
+
value={searchQuery}
|
|
87
|
+
onChangeText={onSearchChange}
|
|
88
|
+
showFilter={showFilter}
|
|
89
|
+
onFilterPress={handleFilterClick}
|
|
90
|
+
currentFilter={isFilterActive}
|
|
91
|
+
/>
|
|
90
92
|
|
|
91
93
|
<DoctorFilterDropdown
|
|
94
|
+
ref={doctorDropdownRef}
|
|
92
95
|
selectedDoctor={selectedDoctor}
|
|
93
96
|
onSelectDoctor={onSelectDoctor}
|
|
94
97
|
availableDoctors={availableDoctors}
|
|
@@ -11,11 +11,13 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
useManagementReportStore,
|
|
13
13
|
ServerErrorWrapper,
|
|
14
|
+
Icon,
|
|
14
15
|
} from '@suflon/native-ui';
|
|
15
16
|
import { ManagementReportHeader } from './components/ManagementReportHeader';
|
|
16
17
|
import { KpiMetricsGrid } from './components/KpiMetricsGrid';
|
|
17
18
|
import { AnalyticsChartView } from './components/AnalyticsChartView';
|
|
18
19
|
import { DetailsListingView } from './components/DetailsListingView';
|
|
20
|
+
import { TouchableOpacity } from 'react-native';
|
|
19
21
|
|
|
20
22
|
interface ManagementReportScreenProps {
|
|
21
23
|
onBackPress?: () => void;
|
|
@@ -69,7 +71,7 @@ const ManagementReportScreen: React.FC<ManagementReportScreenProps> = ({
|
|
|
69
71
|
const categoryReports = useMemo(() => {
|
|
70
72
|
if (!managementReports || managementReports.length === 0) return [];
|
|
71
73
|
return managementReports.filter(
|
|
72
|
-
(r) => r.mgmt_report_type
|
|
74
|
+
(r) => (r.mgmt_report_type || '').trim().toUpperCase() === (activeCategory || '').trim().toUpperCase()
|
|
73
75
|
);
|
|
74
76
|
}, [managementReports, activeCategory]);
|
|
75
77
|
|
|
@@ -77,7 +79,7 @@ const ManagementReportScreen: React.FC<ManagementReportScreenProps> = ({
|
|
|
77
79
|
const categoryCounts = useMemo(() => {
|
|
78
80
|
const counts: Record<string, number> = {};
|
|
79
81
|
(managementReports || []).forEach((r) => {
|
|
80
|
-
const type = r.mgmt_report_type
|
|
82
|
+
const type = (r.mgmt_report_type || 'OTHER').trim().toUpperCase();
|
|
81
83
|
counts[type] = (counts[type] || 0) + 1;
|
|
82
84
|
});
|
|
83
85
|
return counts;
|
|
@@ -113,6 +115,8 @@ const ManagementReportScreen: React.FC<ManagementReportScreenProps> = ({
|
|
|
113
115
|
: 'Reports Catalog';
|
|
114
116
|
const screenSubtitle = `${activeCategory} Management Module`;
|
|
115
117
|
|
|
118
|
+
const hasContent = selectedReportDetails && selectedReportDetails.length > 0;
|
|
119
|
+
|
|
116
120
|
return (
|
|
117
121
|
<SafeAreaView className="flex-1 bg-slate-50 dark:bg-background-lightBlack">
|
|
118
122
|
<ServerErrorWrapper
|
|
@@ -137,6 +141,8 @@ const ManagementReportScreen: React.FC<ManagementReportScreenProps> = ({
|
|
|
137
141
|
onSelectDoctor={setSelectedDoctor}
|
|
138
142
|
availableDoctors={availableDoctors}
|
|
139
143
|
categoryCounts={categoryCounts}
|
|
144
|
+
showFilter={true}
|
|
145
|
+
currentFilter={selectedDoctor !== 'ALL' ? 'active' : ''}
|
|
140
146
|
/>
|
|
141
147
|
|
|
142
148
|
{/* Main Content Area */}
|
|
@@ -166,6 +172,31 @@ const ManagementReportScreen: React.FC<ManagementReportScreenProps> = ({
|
|
|
166
172
|
Loading {selectedReport?.name || activeCategory} metrics & charts...
|
|
167
173
|
</Text>
|
|
168
174
|
</View>
|
|
175
|
+
) : categoryReports.length === 0 || !selectedReport || !hasContent ? (
|
|
176
|
+
/* Informative Empty State for categories without published reports or empty data */
|
|
177
|
+
<View className="py-16 px-6 items-center justify-center bg-white dark:bg-background-darkSecondaryBg rounded-2xl border border-slate-100 dark:border-slate-800 mt-2">
|
|
178
|
+
<View className="w-14 h-14 rounded-2xl bg-violet-50 dark:bg-slate-800 items-center justify-center mb-3.5">
|
|
179
|
+
<Icon name="bar-chart-2" type="Feather" size={24} color="#7c3aed" />
|
|
180
|
+
</View>
|
|
181
|
+
<Text className="text-sm font-bold text-slate-800 dark:text-slate-100 text-center">
|
|
182
|
+
{categoryReports.length === 0
|
|
183
|
+
? `No ${activeCategory} Reports Available`
|
|
184
|
+
: `No Data for ${selectedReport?.name || activeCategory}`}
|
|
185
|
+
</Text>
|
|
186
|
+
<Text className="text-xs text-slate-400 text-center mt-1 px-4 leading-4">
|
|
187
|
+
{categoryReports.length === 0
|
|
188
|
+
? `There are currently no published reports configured for the ${activeCategory.toLowerCase()} module.`
|
|
189
|
+
: 'Report structure is available, but no KPI cards or data tables were found.'}
|
|
190
|
+
</Text>
|
|
191
|
+
<TouchableOpacity
|
|
192
|
+
onPress={handleRefresh}
|
|
193
|
+
activeOpacity={0.8}
|
|
194
|
+
className="mt-4 px-4 py-2 bg-violet-600 rounded-xl flex-row items-center gap-1.5"
|
|
195
|
+
>
|
|
196
|
+
<Icon name="refresh-cw" type="Feather" size={13} color="#FFFFFF" />
|
|
197
|
+
<Text className="text-xs font-semibold text-white">Refresh Reports</Text>
|
|
198
|
+
</TouchableOpacity>
|
|
199
|
+
</View>
|
|
169
200
|
) : (
|
|
170
201
|
/* Sequential Content Flow: KPIs -> Charts -> Listing Tables */
|
|
171
202
|
<>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useEffect, useState, useMemo, useContext } from 'react';
|
|
2
|
-
import { ScrollView, View, Text, TouchableOpacity, SafeAreaView, useColorScheme } from 'react-native';
|
|
3
|
-
import { useNavigation, useRoute } from '@react-navigation/native';
|
|
1
|
+
import React, { useEffect, useState, useMemo, useContext, useCallback } from 'react';
|
|
2
|
+
import { ScrollView, View, Text, TouchableOpacity, SafeAreaView, useColorScheme, BackHandler } from 'react-native';
|
|
3
|
+
import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';
|
|
4
4
|
import { useReportStore, IReport, Header, SearchFilter, Loader, Icon } from '@suflon/native-ui';
|
|
5
5
|
import { themeColors } from '../../../theme/colors';
|
|
6
6
|
import ReportChart from './ReportChart';
|
|
@@ -33,6 +33,20 @@ const ReportingDetail = (props: any) => {
|
|
|
33
33
|
const [isLocalLoading, setIsLocalLoading] = useState(true);
|
|
34
34
|
const [activeReportId, setActiveReportId] = useState<string | number | null>(null);
|
|
35
35
|
|
|
36
|
+
useFocusEffect(
|
|
37
|
+
useCallback(() => {
|
|
38
|
+
const onBack = () => {
|
|
39
|
+
if (navigation?.canGoBack?.()) {
|
|
40
|
+
navigation.goBack();
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
const sub = BackHandler.addEventListener('hardwareBackPress', onBack);
|
|
46
|
+
return () => sub.remove();
|
|
47
|
+
}, [navigation])
|
|
48
|
+
);
|
|
49
|
+
|
|
36
50
|
const isDynamic = report?.report_type === 'SUMMARY' || report?.is_dynamic;
|
|
37
51
|
|
|
38
52
|
// Detect report_type from backend response metadata or item prop
|