@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.
Files changed (39) hide show
  1. package/App.tsx +69 -0
  2. package/README.md +97 -0
  3. package/app.json +4 -0
  4. package/babel.config.js +18 -0
  5. package/global.css +143 -0
  6. package/index.js +9 -0
  7. package/metro.config.js +209 -0
  8. package/nativewind-env.d.ts +1 -0
  9. package/package.json +106 -0
  10. package/patches/@react-navigation+stack+7.6.16.patch +11 -0
  11. package/patches/@suflon+native-ui+0.0.18.patch +26020 -0
  12. package/patches/react-native+0.83.1.patch +52 -0
  13. package/react-native.config.js +27 -0
  14. package/scripts/fix-suflon-native-ui.js +25 -0
  15. package/scripts/link-react-native-pnpm.js +42 -0
  16. package/src/config/index.ts +10 -0
  17. package/src/context/ConnectionI18nContext.tsx +61 -0
  18. package/src/modules/Reporting/component/README.md +3 -0
  19. package/src/modules/Reporting/component/ReportChart.tsx +882 -0
  20. package/src/modules/Reporting/component/ReportFilterModal.tsx +403 -0
  21. package/src/modules/Reporting/component/ReportingDetail.tsx +481 -0
  22. package/src/modules/Reporting/index.tsx +239 -0
  23. package/src/modules/Reporting/utils.tsx +14 -0
  24. package/src/navigation/index.tsx +31 -0
  25. package/src/screens/ConnectionListScreen.tsx +471 -0
  26. package/src/screens/DevToolsCorner.tsx +810 -0
  27. package/src/screens/NewConnectionModal.tsx +282 -0
  28. package/src/services/ApiService.ts +22 -0
  29. package/src/services/ConnectionService.ts +81 -0
  30. package/src/services/api.ts +83 -0
  31. package/src/stores/connection.store.ts +3 -0
  32. package/src/stores/language.store.ts +54 -0
  33. package/src/theme/colors.ts +56 -0
  34. package/src/types/connection.ts +69 -0
  35. package/src/utils/AsyncStorageUtils.ts +56 -0
  36. package/src/utils/connectionStrings.ts +158 -0
  37. package/src/utils/errorMessage.ts +11 -0
  38. package/tailwind.config.js +196 -0
  39. 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
+ });