@suflon/rnmd-reporting 0.0.1
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/App.tsx +69 -0
- package/README.md +97 -0
- package/app.json +4 -0
- package/babel.config.js +18 -0
- package/global.css +143 -0
- package/index.js +9 -0
- package/metro.config.js +209 -0
- package/nativewind-env.d.ts +1 -0
- package/package.json +106 -0
- package/patches/@react-navigation+stack+7.6.16.patch +11 -0
- package/patches/@suflon+native-ui+0.0.18.patch +26020 -0
- package/patches/react-native+0.83.1.patch +52 -0
- package/react-native.config.js +27 -0
- package/scripts/fix-suflon-native-ui.js +25 -0
- package/scripts/link-react-native-pnpm.js +42 -0
- package/src/config/index.ts +10 -0
- package/src/context/ConnectionI18nContext.tsx +61 -0
- package/src/modules/Reporting/component/README.md +3 -0
- package/src/modules/Reporting/component/ReportChart.tsx +882 -0
- package/src/modules/Reporting/component/ReportFilterModal.tsx +403 -0
- package/src/modules/Reporting/component/ReportingDetail.tsx +481 -0
- package/src/modules/Reporting/index.tsx +239 -0
- package/src/modules/Reporting/utils.tsx +14 -0
- package/src/navigation/index.tsx +31 -0
- package/src/screens/ConnectionListScreen.tsx +471 -0
- package/src/screens/DevToolsCorner.tsx +810 -0
- package/src/screens/NewConnectionModal.tsx +282 -0
- package/src/services/ApiService.ts +22 -0
- package/src/services/ConnectionService.ts +81 -0
- package/src/services/api.ts +83 -0
- package/src/stores/connection.store.ts +3 -0
- package/src/stores/language.store.ts +54 -0
- package/src/theme/colors.ts +56 -0
- package/src/types/connection.ts +69 -0
- package/src/utils/AsyncStorageUtils.ts +56 -0
- package/src/utils/connectionStrings.ts +158 -0
- package/src/utils/errorMessage.ts +11 -0
- package/tailwind.config.js +196 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
|
2
|
+
import { ScrollView, TouchableOpacity, View, Text, SafeAreaView, useColorScheme } from 'react-native';
|
|
3
|
+
import { useNavigation } from '@react-navigation/native';
|
|
4
|
+
import { useReportStore, IReport, Header, SearchFilter, Loader, Icon } from '@suflon/native-ui';
|
|
5
|
+
import { themeColors } from '@/theme/colors';
|
|
6
|
+
import { SUB_CLASS_ICON_MAP, SUB_CLASS_LABEL_MAP } from './utils';
|
|
7
|
+
|
|
8
|
+
const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
|
|
9
|
+
const navigation = useNavigation<any>();
|
|
10
|
+
const colorScheme = useColorScheme();
|
|
11
|
+
const isDark = colorScheme === 'dark';
|
|
12
|
+
|
|
13
|
+
const { reports, loading, getReports } = useReportStore();
|
|
14
|
+
|
|
15
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
16
|
+
const [activeTabId, setActiveTabId] = useState<string>('all');
|
|
17
|
+
|
|
18
|
+
// Fetch all reports once on mount and when search changes
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
getReports({
|
|
21
|
+
search_query: searchQuery || '',
|
|
22
|
+
});
|
|
23
|
+
}, [searchQuery, getReports]);
|
|
24
|
+
|
|
25
|
+
// Build dynamic category tabs from the response's sub_report_class
|
|
26
|
+
const categoryTabs = useMemo(() => {
|
|
27
|
+
const tabs: { id: string; label: string; icon: string }[] = [
|
|
28
|
+
{ id: 'all', label: 'All Reports', icon: 'grid' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
if (!reports || reports.length === 0) return tabs;
|
|
32
|
+
|
|
33
|
+
// Extract unique sub_report_class values
|
|
34
|
+
const uniqueSubClasses = new Set<string>();
|
|
35
|
+
reports.forEach((r: IReport) => {
|
|
36
|
+
if (r.sub_report_class) {
|
|
37
|
+
uniqueSubClasses.add(r.sub_report_class);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Sort and build tabs
|
|
42
|
+
Array.from(uniqueSubClasses).sort().forEach((subClass) => {
|
|
43
|
+
tabs.push({
|
|
44
|
+
id: subClass,
|
|
45
|
+
label: SUB_CLASS_LABEL_MAP[subClass] || subClass.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
|
|
46
|
+
icon: SUB_CLASS_ICON_MAP[subClass] || 'folder',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return tabs;
|
|
51
|
+
}, [reports]);
|
|
52
|
+
|
|
53
|
+
// Filter reports client-side based on selected tab
|
|
54
|
+
const filteredReports = useMemo(() => {
|
|
55
|
+
if (!reports || reports.length === 0) return [];
|
|
56
|
+
if (activeTabId === 'all') return reports;
|
|
57
|
+
return reports.filter((r: IReport) => r.sub_report_class === activeTabId);
|
|
58
|
+
}, [reports, activeTabId]);
|
|
59
|
+
|
|
60
|
+
// Reset to 'all' if the active tab no longer exists
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (activeTabId !== 'all' && categoryTabs.length > 0) {
|
|
63
|
+
const tabExists = categoryTabs.some(t => t.id === activeTabId);
|
|
64
|
+
if (!tabExists) setActiveTabId('all');
|
|
65
|
+
}
|
|
66
|
+
}, [categoryTabs, activeTabId]);
|
|
67
|
+
|
|
68
|
+
const handleReportPress = useCallback((report: IReport) => {
|
|
69
|
+
navigation.navigate('ReportingDetail', { report });
|
|
70
|
+
}, [navigation]);
|
|
71
|
+
|
|
72
|
+
const featuredPicks = useMemo(() => {
|
|
73
|
+
if (!filteredReports || filteredReports.length === 0) return [];
|
|
74
|
+
// Show featured reports first, fallback to first 4
|
|
75
|
+
const featured = filteredReports.filter((r: IReport) => r.featured);
|
|
76
|
+
return featured.length > 0 ? featured.slice(0, 4) : filteredReports.slice(0, 4);
|
|
77
|
+
}, [filteredReports]);
|
|
78
|
+
|
|
79
|
+
const iconBrandColor = isDark ? '#a78bfa' : themeColors.brand[600];
|
|
80
|
+
const iconMutedColor = isDark ? '#9CA3AF' : themeColors.slate[400];
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<SafeAreaView className="flex-1 bg-slate-50 dark:bg-background-lightBlack">
|
|
84
|
+
{/* Header Component */}
|
|
85
|
+
<Header
|
|
86
|
+
title="Reports Catalog"
|
|
87
|
+
onBackPress={onBackPress}
|
|
88
|
+
showActionIcon={false}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
<ScrollView className="flex-1" contentContainerStyle={{ paddingBottom: 30 }}>
|
|
92
|
+
{/* Search Bar */}
|
|
93
|
+
<View className="px-4 pt-3.5">
|
|
94
|
+
<SearchFilter
|
|
95
|
+
placeholder="Search reports or creator..."
|
|
96
|
+
value={searchQuery}
|
|
97
|
+
onChangeText={setSearchQuery}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
|
|
101
|
+
{/* Quick Access Featured Carousel */}
|
|
102
|
+
{featuredPicks.length > 0 && (
|
|
103
|
+
<View className="pt-4">
|
|
104
|
+
<View className="px-4 flex-row justify-between items-center mb-2.5">
|
|
105
|
+
<Text className="text-[11px] font-extrabold text-slate-400 dark:text-gray-400 tracking-wider">
|
|
106
|
+
FEATURED REPORTS
|
|
107
|
+
</Text>
|
|
108
|
+
<Text className="text-[11px] font-bold text-violet-600 dark:text-violet-400">
|
|
109
|
+
Swipe ➔
|
|
110
|
+
</Text>
|
|
111
|
+
</View>
|
|
112
|
+
|
|
113
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingHorizontal: 16, gap: 10 }}>
|
|
114
|
+
{featuredPicks.map((pick, i) => (
|
|
115
|
+
<TouchableOpacity
|
|
116
|
+
key={pick.id || i}
|
|
117
|
+
onPress={() => handleReportPress(pick)}
|
|
118
|
+
className="w-[170px] bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3 border border-slate-200 dark:border-gray-800 gap-1.5"
|
|
119
|
+
>
|
|
120
|
+
<View className="w-7 h-7 rounded-lg bg-violet-50 dark:bg-violet-950/40 items-center justify-center">
|
|
121
|
+
<Icon name="bar-chart-2" type="Feather" size={16} color={iconBrandColor} />
|
|
122
|
+
</View>
|
|
123
|
+
<Text style={{ color: isDark ? '#FFFFFF' : '#0F172A' }} className="text-xs font-extrabold" numberOfLines={1}>
|
|
124
|
+
{pick.name}
|
|
125
|
+
</Text>
|
|
126
|
+
<Text className="text-[9px] text-slate-500 dark:text-gray-400" numberOfLines={1}>
|
|
127
|
+
{pick.sub_report_class ? SUB_CLASS_LABEL_MAP[pick.sub_report_class] || pick.sub_report_class : 'Report'}
|
|
128
|
+
</Text>
|
|
129
|
+
</TouchableOpacity>
|
|
130
|
+
))}
|
|
131
|
+
</ScrollView>
|
|
132
|
+
</View>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{/* Category Pills Switcher - Dynamic from response */}
|
|
136
|
+
<View className="pt-4">
|
|
137
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingHorizontal: 16, gap: 8 }}>
|
|
138
|
+
{categoryTabs.map((tab) => {
|
|
139
|
+
const isActive = activeTabId === tab.id;
|
|
140
|
+
const count = tab.id === 'all'
|
|
141
|
+
? (reports || []).length
|
|
142
|
+
: (reports || []).filter((r: IReport) => r.sub_report_class === tab.id).length;
|
|
143
|
+
return (
|
|
144
|
+
<TouchableOpacity
|
|
145
|
+
key={tab.id}
|
|
146
|
+
onPress={() => setActiveTabId(tab.id)}
|
|
147
|
+
style={{
|
|
148
|
+
backgroundColor: isActive ? themeColors.brand[600] : (isDark ? '#1C1C1E' : '#FFFFFF'),
|
|
149
|
+
borderColor: isActive ? themeColors.brand[600] : (isDark ? '#2C2C2E' : '#E2E8F0'),
|
|
150
|
+
}}
|
|
151
|
+
className="flex-row items-center gap-1.5 px-3.5 py-2 rounded-full border"
|
|
152
|
+
>
|
|
153
|
+
<Icon
|
|
154
|
+
name={tab.icon}
|
|
155
|
+
type="Feather"
|
|
156
|
+
size={13}
|
|
157
|
+
color={isActive ? '#FFFFFF' : (isDark ? '#FFFFFF' : themeColors.slate[600])}
|
|
158
|
+
/>
|
|
159
|
+
<Text
|
|
160
|
+
style={{ color: isActive ? '#FFFFFF' : (isDark ? '#FFFFFF' : '#334155') }}
|
|
161
|
+
className="text-[11px] font-bold"
|
|
162
|
+
>
|
|
163
|
+
{tab.label}
|
|
164
|
+
</Text>
|
|
165
|
+
<View
|
|
166
|
+
style={{
|
|
167
|
+
backgroundColor: isActive ? 'rgba(255, 255, 255, 0.25)' : (isDark ? '#2C2C2E' : '#F1F5F9'),
|
|
168
|
+
}}
|
|
169
|
+
className="rounded-full px-1.5 py-0.5"
|
|
170
|
+
>
|
|
171
|
+
<Text
|
|
172
|
+
style={{ color: isActive ? '#FFFFFF' : (isDark ? '#FFFFFF' : '#475569') }}
|
|
173
|
+
className="text-[9px] font-extrabold"
|
|
174
|
+
>
|
|
175
|
+
{count}
|
|
176
|
+
</Text>
|
|
177
|
+
</View>
|
|
178
|
+
</TouchableOpacity>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</ScrollView>
|
|
182
|
+
</View>
|
|
183
|
+
|
|
184
|
+
{/* Report Catalog List */}
|
|
185
|
+
<View className="px-4 pt-3.5 gap-2.5">
|
|
186
|
+
{loading ? (
|
|
187
|
+
<View className="py-10">
|
|
188
|
+
<Loader message="Loading reports catalog..." color={iconBrandColor} />
|
|
189
|
+
</View>
|
|
190
|
+
) : filteredReports.length === 0 ? (
|
|
191
|
+
<View className="bg-white dark:bg-background-darkSecondaryBg p-8 rounded-2xl items-center mt-2.5 border border-slate-200 dark:border-gray-800">
|
|
192
|
+
<Text className="text-slate-500 dark:text-gray-400 text-xs">No reports match the selected category</Text>
|
|
193
|
+
</View>
|
|
194
|
+
) : (
|
|
195
|
+
filteredReports.map((item) => (
|
|
196
|
+
<TouchableOpacity
|
|
197
|
+
key={item.id}
|
|
198
|
+
onPress={() => handleReportPress(item)}
|
|
199
|
+
className="bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3.5 border border-slate-200 dark:border-gray-800 flex-row items-center"
|
|
200
|
+
>
|
|
201
|
+
<View className="flex-row items-center gap-3 flex-1">
|
|
202
|
+
<View className="w-10 h-10 rounded-xl bg-violet-50 dark:bg-violet-950/40 items-center justify-center">
|
|
203
|
+
<Icon
|
|
204
|
+
name={SUB_CLASS_ICON_MAP[item.sub_report_class] || (item.is_dynamic ? "pie-chart" : "file-text")}
|
|
205
|
+
type="Feather"
|
|
206
|
+
size={20}
|
|
207
|
+
color={iconBrandColor}
|
|
208
|
+
/>
|
|
209
|
+
</View>
|
|
210
|
+
|
|
211
|
+
<View className="flex-1">
|
|
212
|
+
<Text style={{ color: isDark ? '#FFFFFF' : '#0F172A' }} className="text-sm font-extrabold" numberOfLines={1}>
|
|
213
|
+
{item.name}
|
|
214
|
+
</Text>
|
|
215
|
+
<View className="flex-row items-center gap-1.5 mt-0.5">
|
|
216
|
+
<Text className="text-[10px] font-bold text-violet-600 dark:text-violet-400">
|
|
217
|
+
{SUB_CLASS_LABEL_MAP[item.sub_report_class] || item.sub_report_class || 'Report'}
|
|
218
|
+
</Text>
|
|
219
|
+
<Text className="text-[10px] text-slate-400 dark:text-gray-500">•</Text>
|
|
220
|
+
<Text className="text-[10px] text-slate-500 dark:text-gray-400">
|
|
221
|
+
{item.report_type || 'LISTING'}
|
|
222
|
+
</Text>
|
|
223
|
+
</View>
|
|
224
|
+
</View>
|
|
225
|
+
</View>
|
|
226
|
+
|
|
227
|
+
<View className="flex-row items-center gap-1.5">
|
|
228
|
+
<Icon name="chevron-right" type="Feather" size={18} color={iconMutedColor} />
|
|
229
|
+
</View>
|
|
230
|
+
</TouchableOpacity>
|
|
231
|
+
))
|
|
232
|
+
)}
|
|
233
|
+
</View>
|
|
234
|
+
</ScrollView>
|
|
235
|
+
</SafeAreaView>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export default Reporting;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const SUB_CLASS_ICON_MAP: Record<string, string> = {
|
|
2
|
+
'APPOINTMENT': 'calendar',
|
|
3
|
+
'PRESCRIPTION': 'file-text',
|
|
4
|
+
'TREATMENT_PLAN': 'activity',
|
|
5
|
+
'BILLING': 'dollar-sign',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Label mapping for known sub_report_class values
|
|
9
|
+
export const SUB_CLASS_LABEL_MAP: Record<string, string> = {
|
|
10
|
+
'APPOINTMENT': 'Appointment',
|
|
11
|
+
'PRESCRIPTION': 'Prescription',
|
|
12
|
+
'TREATMENT_PLAN': 'Treatment Plan',
|
|
13
|
+
'BILLING': 'Billing',
|
|
14
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
4
|
+
import Reporting from '@/modules/Reporting';
|
|
5
|
+
import ReportingDetail from '@/modules/Reporting/component/ReportingDetail';
|
|
6
|
+
|
|
7
|
+
export type AppNavigationProps = {
|
|
8
|
+
onBackPress?: () => void;
|
|
9
|
+
title?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const Stack = createNativeStackNavigator();
|
|
13
|
+
|
|
14
|
+
export function AppNavigation({ onBackPress }: AppNavigationProps = {}) {
|
|
15
|
+
return (
|
|
16
|
+
<View style={styles.container}>
|
|
17
|
+
<Stack.Navigator screenOptions={{ headerShown: false }} initialRouteName="ReportingList">
|
|
18
|
+
<Stack.Screen name="ReportingList">
|
|
19
|
+
{(props) => <Reporting {...props} onBackPress={onBackPress} />}
|
|
20
|
+
</Stack.Screen>
|
|
21
|
+
<Stack.Screen name="ReportingDetail" component={ReportingDetail} />
|
|
22
|
+
</Stack.Navigator>
|
|
23
|
+
</View>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const styles = StyleSheet.create({
|
|
28
|
+
container: {
|
|
29
|
+
flex: 1,
|
|
30
|
+
},
|
|
31
|
+
});
|