@suflon/rnmd-reporting 0.0.3 → 0.0.5

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.
@@ -1,8 +1,8 @@
1
- import React, { useEffect, useState, useMemo } from 'react';
1
+ import React, { useEffect, useState, useMemo, useContext } from 'react';
2
2
  import { ScrollView, View, Text, TouchableOpacity, SafeAreaView, useColorScheme } from 'react-native';
3
- import { useRoute, useNavigation } from '@react-navigation/native';
3
+ import { useNavigation, useRoute } from '@react-navigation/native';
4
4
  import { useReportStore, IReport, Header, SearchFilter, Loader, Icon } from '@suflon/native-ui';
5
- import { themeColors } from '@/theme/colors';
5
+ import { themeColors } from '../../../theme/colors';
6
6
  import ReportChart from './ReportChart';
7
7
  import ReportFilterModal from './ReportFilterModal';
8
8
 
@@ -12,10 +12,19 @@ const formatKeyLabel = (key: string) => {
12
12
  .replace(/\b\w/g, (char) => char.toUpperCase());
13
13
  };
14
14
 
15
- const ReportingDetail = () => {
16
- const navigation = useNavigation<any>();
17
- const route = useRoute<any>();
18
- const report = route.params?.report as IReport;
15
+ const ReportingDetail = (props: any) => {
16
+ const navHook = useNavigation<any>();
17
+ const navigation = props.navigation || navHook;
18
+
19
+ let routeParams: any = {};
20
+ try {
21
+ const r = useRoute<any>();
22
+ routeParams = props.route?.params || r?.params || {};
23
+ } catch (e) {
24
+ routeParams = props.route?.params || {};
25
+ }
26
+
27
+ const report = routeParams?.report as IReport;
19
28
  const colorScheme = useColorScheme();
20
29
  const isDarkMode = colorScheme === 'dark';
21
30
 
@@ -137,6 +146,7 @@ const ReportingDetail = () => {
137
146
  reportMetadata?.metadata?.meta?.is_dynamic === true ||
138
147
  report?.is_dynamic === true
139
148
  );
149
+
140
150
  executeReport(report.id, isMetaDynamic, payload).finally(() => {
141
151
  setIsLocalLoading(false);
142
152
  });
@@ -203,23 +213,6 @@ const ReportingDetail = () => {
203
213
  });
204
214
  }, [records]);
205
215
 
206
- const activeFilters = useMemo(() => {
207
- const filters: string[] = [];
208
- const filterList = reportMetadata?.metadata?.filter_by || [];
209
- filterList.forEach((f: any) => {
210
- if (f.defaultValue) {
211
- if (typeof f.defaultValue === 'object' && (f.defaultValue.from_date || f.defaultValue.from)) {
212
- const from = f.defaultValue.from_date || f.defaultValue.from;
213
- const to = f.defaultValue.to_date || f.defaultValue.to;
214
- filters.push(`${from} - ${to}`);
215
- } else if (typeof f.defaultValue === 'string') {
216
- filters.push(f.defaultValue);
217
- }
218
- }
219
- });
220
- return filters.length > 0 ? filters : ['All Filters'];
221
- }, [reportMetadata]);
222
-
223
216
  return (
224
217
  <SafeAreaView className="flex-1 bg-slate-50 dark:bg-background-lightBlack">
225
218
  {/* Suflon Header Component */}
@@ -237,7 +230,7 @@ const ReportingDetail = () => {
237
230
 
238
231
  {/* Top View Mode Switcher Pills - Hidden in Chart View mode */}
239
232
  {!isChartReport && (
240
- <View className="flex-row bg-white dark:bg-background-darkSecondaryBg px-4 py-2.5 gap-2 border-b border-slate-100 dark:border-gray-800">
233
+ <View className="flex-row bg-white dark:bg-background-darkSecondaryBg px-4 py-2.5 gap-2 border-b border-slate-200/70 dark:border-gray-800">
241
234
  {VIEW_TABS.map((tab) => {
242
235
  const isActive = viewTab === tab.key;
243
236
  return (
@@ -253,10 +246,10 @@ const ReportingDetail = () => {
253
246
  name={tab.icon}
254
247
  type="Feather"
255
248
  size={14}
256
- color={isActive ? '#FFFFFF' : (isDarkMode ? '#FFFFFF' : themeColors.slate[600])}
249
+ color={isActive ? '#FFFFFF' : (isDarkMode ? '#94a3b8' : themeColors.slate[600])}
257
250
  />
258
251
  <Text
259
- style={{ color: isActive ? '#FFFFFF' : (isDarkMode ? '#FFFFFF' : '#334155') }}
252
+ style={{ color: isActive ? '#FFFFFF' : (isDarkMode ? '#E2E8F0' : '#334155') }}
260
253
  className="text-xs font-extrabold"
261
254
  >
262
255
  {tab.label}
@@ -278,96 +271,91 @@ const ReportingDetail = () => {
278
271
  showsVerticalScrollIndicator={false}
279
272
  contentContainerStyle={{ paddingHorizontal: 16, paddingTop: 14, paddingBottom: 130 }}
280
273
  >
281
- {/* Active Filter Banner */}
282
- <View className="bg-white dark:bg-background-darkSecondaryBg px-3 py-2.5 rounded-2xl border border-slate-200 dark:border-gray-800 flex-row items-center justify-between mb-3">
283
- <View className="flex-row items-center gap-1.5 flex-wrap flex-1">
284
- <Text className="text-[9px] font-extrabold text-slate-400 dark:text-gray-400 tracking-wider">
285
- ACTIVE FILTER:
286
- </Text>
287
- {activeFilters.map((f, i) => (
288
- <View key={i} className="bg-slate-100 dark:bg-gray-800 px-2 py-0.5 rounded-md">
289
- <Text className="text-[10px] font-bold text-slate-700 dark:text-gray-100">
290
- {f}
291
- </Text>
292
- </View>
293
- ))}
294
- </View>
295
-
296
- <TouchableOpacity onPress={() => setFilterModalVisible(true)} className="flex-row items-center gap-1">
297
- <Icon name="edit-3" type="Feather" size={12} color={themeColors.brand[600]} />
298
- <Text className="text-[11px] font-extrabold text-violet-600 dark:text-violet-400">
299
- Edit
300
- </Text>
301
- </TouchableOpacity>
302
- </View>
303
-
304
- {/* Chart View Content */}
305
- {viewTab === 'chart' ? (
306
- <ReportChart
307
- data={chartData}
308
- records={records}
309
- visibleKeys={visibleKeys}
310
- title={report?.name || reportMetadata?.report_name || 'Diagnosis Frequency Curve'}
311
- subtitle="GRAPH VISUALIZATION"
312
- isDarkMode={isDarkMode}
313
- />
314
- ) : (
315
- <View className="gap-3">
316
- {/* Search Component from Suflon Native UI */}
274
+ {/* Search and Action Bar */}
275
+ <View className="flex-row gap-2 mb-4">
276
+ <View className="flex-1">
317
277
  <SearchFilter
318
- placeholder="Search records..."
278
+ placeholder="Search in results..."
319
279
  value={searchQuery}
320
280
  onChangeText={setSearchQuery}
321
281
  />
282
+ </View>
283
+ </View>
284
+
285
+ {/* Chart Mode */}
286
+ {viewTab === 'chart' && (
287
+ <View className="gap-4">
288
+ <ReportChart
289
+ data={chartData}
290
+ records={filteredRecords}
291
+ visibleKeys={visibleKeys}
292
+ title={report?.name || 'Analytics Chart'}
293
+ subtitle={`Visual breakdown of ${filteredRecords.length} records`}
294
+ color={themeColors.brand[600]}
295
+ height={240}
296
+ isDarkMode={isDarkMode}
297
+ />
298
+ </View>
299
+ )}
300
+
301
+ {/* Records List / Table */}
302
+ {viewTab !== 'chart' && (
303
+ <View className="gap-3">
304
+ <View className="flex-row justify-between items-center px-1">
305
+ <Text style={{ color: isDarkMode ? '#E2E8F0' : '#0F172A' }} className="text-xs font-black uppercase tracking-wider">
306
+ Records Dataset ({filteredRecords.length})
307
+ </Text>
308
+ </View>
322
309
 
323
- {/* Mobile Cards Mode */}
324
310
  {filteredRecords.length === 0 ? (
325
- <View className="bg-white dark:bg-background-darkSecondaryBg p-6 rounded-2xl items-center mt-2.5 border border-slate-200 dark:border-gray-800">
326
- <Text className="text-slate-500 dark:text-gray-300 text-xs font-semibold">No records found</Text>
311
+ <View className="bg-white dark:bg-background-darkSecondaryBg p-8 rounded-2xl border border-slate-200 dark:border-gray-800 items-center justify-center">
312
+ <Icon name="inbox" type="Feather" size={32} color={isDarkMode ? '#6B7280' : themeColors.slate[300]} />
313
+ <Text style={{ color: isDarkMode ? '#E2E8F0' : '#1E293B' }} className="text-xs font-bold mt-2">No records found</Text>
314
+ <Text style={{ color: isDarkMode ? '#9CA3AF' : '#64748B' }} className="text-[10px] text-center mt-1">
315
+ Try adjusting your search query or reset active filters.
316
+ </Text>
327
317
  </View>
328
318
  ) : viewTab === 'card' ? (
319
+ /* Structured Clean Card View */
329
320
  filteredRecords.map((item: any, idx: number) => {
330
- const cardId = item.txn_number || item.id || item.app_id || item.code || item.sysid || item.appointment_num || `#${idx + 1}`;
321
+ const primaryKey = visibleKeys[0] || 'id';
322
+ const primaryVal = item[primaryKey] ?? `#${idx + 1}`;
323
+
324
+ const secondaryKeys = visibleKeys.slice(1, 7);
325
+ const detailKeys = visibleKeys.slice(7);
326
+ const cardId = item.id || idx;
331
327
  const isExpanded = expandedCardId === cardId;
332
328
 
333
- const statusKey = visibleKeys.find(k => k.toLowerCase().includes('status')) || '';
334
- const statusVal = statusKey ? String(item[statusKey] || '') : '';
335
- const isSuccess = statusVal.toUpperCase().includes('COMPLETED') || statusVal.toUpperCase().includes('PAID') || statusVal.toUpperCase().includes('APPROVED');
336
-
337
- const gridKeys = visibleKeys.filter(k => k !== statusKey).slice(0, 4);
338
- const detailKeys = visibleKeys.filter(k => k !== statusKey && !gridKeys.includes(k));
339
-
340
329
  return (
341
330
  <View
342
331
  key={idx}
343
- className="bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3.5 border border-slate-200 dark:border-gray-800 gap-2.5"
332
+ className="bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-4 border border-slate-200/70 dark:border-gray-800 gap-2.5 "
344
333
  >
345
- {/* Card Top Header Row */}
346
- <View className="flex-row justify-between items-center border-b border-slate-100 dark:border-gray-800 pb-2">
347
- <View className="flex-row items-center gap-1.5">
348
- <View className={`w-2 h-2 rounded-full ${isSuccess ? 'bg-emerald-500' : 'bg-amber-500'}`} />
349
- <Text style={{ color: isDarkMode ? '#FFFFFF' : '#0F172A' }} className="text-xs font-extrabold">
350
- {cardId}
334
+ {/* Top Card Header */}
335
+ <View className="flex-row justify-between items-center">
336
+ <View className="flex-row items-center gap-2">
337
+ <View className="w-2 h-2 rounded-full bg-violet-600 dark:bg-violet-400" />
338
+ <Text style={{ color: isDarkMode ? '#FFFFFF' : '#0F172A' }} className="text-sm font-black">
339
+ {formatKeyLabel(primaryKey)}: {String(primaryVal)}
351
340
  </Text>
352
341
  </View>
353
-
354
- {statusVal ? (
355
- <View className={`px-2 py-0.5 rounded-md ${isSuccess ? 'bg-emerald-100 dark:bg-emerald-950/50' : 'bg-amber-100 dark:bg-amber-950/50'}`}>
356
- <Text className={`text-[9px] font-extrabold ${isSuccess ? 'text-emerald-800 dark:text-emerald-300' : 'text-amber-800 dark:text-amber-300'}`}>
357
- {statusVal}
342
+ {item.status && (
343
+ <View className="px-2 py-0.5 rounded-full bg-violet-50 dark:bg-violet-950/40 border border-violet-100 dark:border-violet-800/40">
344
+ <Text className="text-[9px] font-extrabold text-violet-700 dark:text-violet-300 uppercase">
345
+ {String(item.status)}
358
346
  </Text>
359
347
  </View>
360
- ) : null}
348
+ )}
361
349
  </View>
362
350
 
363
- {/* 2x2 Grid Dynamic Info */}
364
- <View className="flex-row flex-wrap gap-2.5">
365
- {gridKeys.map((key) => {
351
+ {/* Key Metrics Grid */}
352
+ <View className="flex-row flex-wrap gap-y-2 pt-1 border-t border-slate-50 dark:border-gray-800/60">
353
+ {secondaryKeys.map((key) => {
366
354
  const rawVal = item[key];
367
355
  const displayVal = (rawVal === null || rawVal === undefined || rawVal === '' || String(rawVal).trim() === '') ? '-' : String(rawVal);
368
356
  return (
369
- <View key={key} className="w-[47%]">
370
- <Text style={{ color: isDarkMode ? '#9CA3AF' : '#64748B' }} className="text-[8px] font-extrabold tracking-wider">
357
+ <View key={key} className="w-1/2 pr-2">
358
+ <Text style={{ color: isDarkMode ? '#9CA3AF' : '#64748B' }} className="text-[10px] font-semibold" numberOfLines={1}>
371
359
  {formatKeyLabel(key)}
372
360
  </Text>
373
361
  <Text style={{ color: isDarkMode ? '#FFFFFF' : '#0F172A' }} className="text-xs font-bold mt-0.5" numberOfLines={1}>
@@ -383,7 +371,7 @@ const ReportingDetail = () => {
383
371
  <>
384
372
  <TouchableOpacity
385
373
  onPress={() => setExpandedCardId(isExpanded ? null : cardId)}
386
- className="flex-row justify-between items-center pt-2 border-t border-slate-100 dark:border-gray-800"
374
+ className="flex-row justify-between items-center pt-2 border-t border-slate-200/70 dark:border-gray-800"
387
375
  >
388
376
  <Text className="text-[10px] font-bold text-violet-600 dark:text-violet-400">
389
377
  View Full Response Details
@@ -397,7 +385,7 @@ const ReportingDetail = () => {
397
385
  </TouchableOpacity>
398
386
 
399
387
  {isExpanded && (
400
- <View className="bg-slate-50 dark:bg-background-lightBlack p-2.5 rounded-xl gap-1.5 border border-slate-100 dark:border-gray-800">
388
+ <View className="bg-slate-50 dark:bg-background-lightBlack p-2.5 rounded-xl gap-1.5 border border-slate-200/70 dark:border-gray-800">
401
389
  {detailKeys.map((key) => {
402
390
  const rawDetailVal = item[key];
403
391
  const displayDetailVal = (rawDetailVal === null || rawDetailVal === undefined || rawDetailVal === '' || String(rawDetailVal).trim() === '') ? '-' : String(rawDetailVal);
@@ -430,7 +418,7 @@ const ReportingDetail = () => {
430
418
  ))}
431
419
  </View>
432
420
  {filteredRecords.map((item: any, idx: number) => (
433
- <View key={idx} className="flex-row p-2.5 border-b border-slate-100 dark:border-gray-800 items-center">
421
+ <View key={idx} className="flex-row p-2.5 border-b border-slate-200/70 dark:border-gray-800 items-center">
434
422
  {visibleKeys.map((key) => {
435
423
  const rawCellVal = item[key];
436
424
  const displayCellVal = (rawCellVal === null || rawCellVal === undefined || rawCellVal === '' || String(rawCellVal).trim() === '') ? '-' : String(rawCellVal);
@@ -449,14 +437,14 @@ const ReportingDetail = () => {
449
437
 
450
438
  {/* Pagination Footer */}
451
439
  <View className="flex-row justify-between items-center pt-2">
452
- <Text className="text-[11px] text-slate-500 dark:text-gray-300">
440
+ <Text style={{ color: isDarkMode ? '#9CA3AF' : '#64748B' }} className="text-[11px]">
453
441
  Showing 1-{filteredRecords.length} of {filteredRecords.length} records
454
442
  </Text>
455
443
  <View className="flex-row items-center gap-1.5">
456
444
  <TouchableOpacity className="w-6 h-6 rounded-md bg-white dark:bg-background-darkSecondaryBg border border-slate-200 dark:border-gray-800 items-center justify-center">
457
445
  <Icon name="chevron-left" type="Feather" size={14} color={isDarkMode ? '#FFFFFF' : themeColors.slate[400]} />
458
446
  </TouchableOpacity>
459
- <Text className="text-[11px] font-extrabold text-slate-900 dark:text-white">1</Text>
447
+ <Text style={{ color: isDarkMode ? '#FFFFFF' : '#0F172A' }} className="text-[11px] font-extrabold">1</Text>
460
448
  <TouchableOpacity className="w-6 h-6 rounded-md bg-white dark:bg-background-darkSecondaryBg border border-slate-200 dark:border-gray-800 items-center justify-center">
461
449
  <Icon name="chevron-right" type="Feather" size={14} color={isDarkMode ? '#FFFFFF' : themeColors.slate[400]} />
462
450
  </TouchableOpacity>
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react';
2
2
  import { ScrollView, TouchableOpacity, View, Text, SafeAreaView, useColorScheme } from 'react-native';
3
3
  import { useNavigation } from '@react-navigation/native';
4
4
  import { useReportStore, IReport, Header, SearchFilter, Loader, Icon } from '@suflon/native-ui';
5
- import { themeColors } from '@/theme/colors';
5
+ import { themeColors } from '../../theme/colors';
6
6
  import { SUB_CLASS_ICON_MAP, SUB_CLASS_LABEL_MAP } from './utils';
7
7
 
8
8
  const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
@@ -71,13 +71,11 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
71
71
 
72
72
  const featuredPicks = useMemo(() => {
73
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);
74
+ const featured = filteredReports.filter((r: IReport) => (r as any).featured);
76
75
  return featured.length > 0 ? featured.slice(0, 4) : filteredReports.slice(0, 4);
77
76
  }, [filteredReports]);
78
77
 
79
78
  const iconBrandColor = isDark ? '#a78bfa' : themeColors.brand[600];
80
- const iconMutedColor = isDark ? '#9CA3AF' : themeColors.slate[400];
81
79
 
82
80
  return (
83
81
  <SafeAreaView className="flex-1 bg-slate-50 dark:bg-background-lightBlack">
@@ -101,9 +99,9 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
101
99
 
102
100
  {/* Quick Access Featured Carousel */}
103
101
  {featuredPicks.length > 0 && (
104
- <View className="pt-4">
105
- <View className="px-4 flex-row justify-between items-center mb-2.5">
106
- <Text className="text-[11px] font-extrabold text-slate-400 dark:text-gray-400 tracking-wider">
102
+ <View className="pt-3.5">
103
+ <View className="px-4 flex-row justify-between items-center mb-2">
104
+ <Text style={{ color: isDark ? '#9CA3AF' : '#94A3B8' }} className="text-[11px] font-extrabold tracking-wider">
107
105
  FEATURED REPORTS
108
106
  </Text>
109
107
  <Text className="text-[11px] font-bold text-violet-600 dark:text-violet-400">
@@ -117,15 +115,15 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
117
115
  key={pick.id || i}
118
116
  onPress={() => handleReportPress(pick)}
119
117
  activeOpacity={0.8}
120
- className="w-[170px] bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3 border border-slate-200 dark:border-gray-800 gap-1.5"
118
+ className="w-[170px] bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3 border border-slate-200/70 dark:border-slate-800 gap-1.5"
121
119
  >
122
120
  <View className="w-7 h-7 rounded-lg bg-violet-50 dark:bg-violet-950/40 items-center justify-center">
123
- <Icon name="bar-chart-2" type="Feather" size={16} color={iconBrandColor} />
121
+ <Icon name="bar-chart-2" type="Feather" size={15} color={iconBrandColor} />
124
122
  </View>
125
- <Text className="text-xs font-extrabold text-slate-900 dark:text-text-dark-primary" numberOfLines={1}>
123
+ <Text style={{ color: isDark ? '#FFFFFF' : '#0F172A' }} className="text-xs font-extrabold" numberOfLines={1}>
126
124
  {pick.name}
127
125
  </Text>
128
- <Text className="text-[9px] font-semibold text-slate-500 dark:text-gray-400" numberOfLines={1}>
126
+ <Text style={{ color: isDark ? '#9CA3AF' : '#94A3B8' }} className="text-[9px] font-semibold" numberOfLines={1}>
129
127
  {pick.sub_report_class ? SUB_CLASS_LABEL_MAP[pick.sub_report_class] || pick.sub_report_class : 'Report'}
130
128
  </Text>
131
129
  </TouchableOpacity>
@@ -134,8 +132,8 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
134
132
  </View>
135
133
  )}
136
134
 
137
- {/* Category Pills Switcher - Dynamic from response */}
138
- <View className="py-4">
135
+ {/* Category Pills Switcher */}
136
+ <View className="py-3.5">
139
137
  <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingHorizontal: 16, gap: 8 }}>
140
138
  {categoryTabs.map((tab) => {
141
139
  const isActive = activeTabId === tab.id;
@@ -147,40 +145,34 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
147
145
  key={tab.id}
148
146
  onPress={() => setActiveTabId(tab.id)}
149
147
  activeOpacity={0.8}
150
- className={`flex-row items-center gap-1.5 px-3.5 py-2 rounded-full border ${
148
+ className={`flex-row items-center gap-1.5 px-3.5 py-1.5 rounded-xl border ${
151
149
  isActive
152
150
  ? 'bg-violet-600 border-violet-600'
153
- : 'bg-white dark:bg-background-darkSecondaryBg border-slate-200 dark:border-gray-800'
151
+ : 'bg-white dark:bg-background-darkSecondaryBg border-slate-200/70 dark:border-slate-800'
154
152
  }`}
155
153
  >
156
154
  <Icon
157
155
  name={tab.icon}
158
156
  type="Feather"
159
- size={13}
160
- color={isActive ? '#FFFFFF' : (isDark ? '#FFFFFF' : themeColors.slate[600])}
157
+ size={12}
158
+ color={isActive ? '#FFFFFF' : '#94a3b8'}
161
159
  />
162
160
  <Text
163
- className={`text-[11px] font-bold ${
164
- isActive
165
- ? 'text-white'
166
- : 'text-slate-700 dark:text-gray-200'
167
- }`}
161
+ style={{ color: isActive ? '#FFFFFF' : (isDark ? '#9CA3AF' : '#94A3B8') }}
162
+ className={`text-xs ${isActive ? 'font-bold' : 'font-normal'}`}
168
163
  >
169
164
  {tab.label}
170
165
  </Text>
171
166
  <View
172
- className={`rounded-full px-1.5 py-0.5 ${
167
+ className={`px-1.5 py-0.5 rounded-full ${
173
168
  isActive
174
- ? 'bg-white/25'
175
- : 'bg-slate-100 dark:bg-gray-800'
169
+ ? 'bg-white/20'
170
+ : 'bg-slate-100 dark:bg-slate-800'
176
171
  }`}
177
172
  >
178
173
  <Text
179
- className={`text-[9px] font-extrabold ${
180
- isActive
181
- ? 'text-white'
182
- : 'text-slate-600 dark:text-gray-300'
183
- }`}
174
+ style={{ color: isActive ? '#FFFFFF' : (isDark ? '#9CA3AF' : '#94A3B8') }}
175
+ className={`text-[9px] ${isActive ? 'font-bold' : 'font-normal'}`}
184
176
  >
185
177
  {count}
186
178
  </Text>
@@ -192,57 +184,58 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
192
184
  </View>
193
185
  </View>
194
186
 
195
- {/* Scrollable Report Catalog List ONLY */}
196
- <ScrollView className="flex-1" contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 30, gap: 10 }}>
197
- {loading ? (
198
- <View className="py-10">
199
- <Loader message="Loading reports catalog..." color={iconBrandColor} />
200
- </View>
201
- ) : filteredReports.length === 0 ? (
202
- <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">
203
- <Text className="text-slate-500 dark:text-gray-400 text-xs font-semibold">No reports match the selected category</Text>
204
- </View>
205
- ) : (
206
- filteredReports.map((item) => (
207
- <TouchableOpacity
208
- key={item.id}
209
- onPress={() => handleReportPress(item)}
210
- activeOpacity={0.8}
211
- className="bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3.5 border border-slate-200 dark:border-gray-800 flex-row items-center"
212
- >
213
- <View className="flex-row items-center gap-3 flex-1">
214
- <View className="w-10 h-10 rounded-xl bg-violet-50 dark:bg-violet-950/40 items-center justify-center">
215
- <Icon
216
- name={SUB_CLASS_ICON_MAP[item.sub_report_class] || (item.is_dynamic ? "pie-chart" : "file-text")}
217
- type="Feather"
218
- size={20}
219
- color={iconBrandColor}
220
- />
221
- </View>
187
+ {/* Content Area */}
188
+ {loading ? (
189
+ <View className="flex-1 justify-center items-center">
190
+ <Loader message="Loading reports catalog..." color={themeColors.brand[600]} />
191
+ </View>
192
+ ) : (
193
+ <ScrollView
194
+ className="flex-1"
195
+ showsVerticalScrollIndicator={false}
196
+ contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 120 }}
197
+ >
198
+ {filteredReports.length === 0 ? (
199
+ <View className="bg-white dark:bg-background-darkSecondaryBg p-8 rounded-2xl border border-slate-200/70 dark:border-slate-800 items-center justify-center mt-2">
200
+ <Icon name="inbox" type="Feather" size={32} color={isDark ? '#6B7280' : themeColors.slate[300]} />
201
+ <Text style={{ color: isDark ? '#FFFFFF' : '#0F172A' }} className="text-xs font-bold mt-2">No reports found</Text>
202
+ <Text style={{ color: isDark ? '#9CA3AF' : '#64748B' }} className="text-[10px] text-center mt-1">
203
+ {searchQuery ? 'Try matching another keyword or filter' : 'No available reports listed for this category'}
204
+ </Text>
205
+ </View>
206
+ ) : (
207
+ <View className="gap-2">
208
+ <Text style={{ color: isDark ? '#9CA3AF' : '#94A3B8' }} className="text-[10px] font-extrabold uppercase tracking-wider px-1 mb-0.5">
209
+ {activeTabId === 'all' ? 'All Catalog Reports' : `${SUB_CLASS_LABEL_MAP[activeTabId] || activeTabId} Reports`} ({filteredReports.length})
210
+ </Text>
222
211
 
223
- <View className="flex-1">
224
- <Text className="text-[13px] font-extrabold text-slate-900 dark:text-text-dark-primary" numberOfLines={1}>
225
- {item.name}
226
- </Text>
227
- <View className="flex-row items-center gap-1.5 mt-0.5">
228
- <Text className="text-[10px] font-bold text-violet-600 dark:text-violet-400">
229
- {SUB_CLASS_LABEL_MAP[item.sub_report_class] || item.sub_report_class || 'Report'}
230
- </Text>
231
- <Text className="text-[10px] text-slate-400 dark:text-gray-500">•</Text>
232
- <Text className="text-[10px] font-semibold text-slate-500 dark:text-gray-400">
233
- {item.report_type || 'LISTING'}
234
- </Text>
212
+ {filteredReports.map((report: IReport) => (
213
+ <TouchableOpacity
214
+ key={report.id}
215
+ onPress={() => handleReportPress(report)}
216
+ activeOpacity={0.8}
217
+ className="flex-row items-center justify-between bg-white dark:bg-background-darkSecondaryBg p-3 rounded-2xl border border-slate-200/70 dark:border-slate-800"
218
+ >
219
+ <View className="flex-row items-center gap-3 flex-1 mr-2">
220
+ <View className="w-9 h-9 rounded-xl bg-violet-50 dark:bg-violet-950/40 items-center justify-center shrink-0">
221
+ <Icon name="file-text" type="Feather" size={15} color={iconBrandColor} />
222
+ </View>
223
+ <View className="flex-1">
224
+ <Text style={{ color: isDark ? '#FFFFFF' : '#0F172A' }} className="text-xs font-bold" numberOfLines={1}>
225
+ {report.name}
226
+ </Text>
227
+ <Text style={{ color: isDark ? '#9CA3AF' : '#64748B' }} className="text-[10px] mt-0.5" numberOfLines={1}>
228
+ {report.description || (report.sub_report_class ? SUB_CLASS_LABEL_MAP[report.sub_report_class] : 'Ready for export & view')}
229
+ </Text>
230
+ </View>
235
231
  </View>
236
- </View>
237
- </View>
238
-
239
- <View className="flex-row items-center gap-1.5">
240
- <Icon name="chevron-right" type="Feather" size={18} color={iconMutedColor} />
241
- </View>
242
- </TouchableOpacity>
243
- ))
244
- )}
245
- </ScrollView>
232
+ <Icon name="chevron-right" type="Feather" size={14} color={isDark ? '#6B7280' : themeColors.slate[400]} />
233
+ </TouchableOpacity>
234
+ ))}
235
+ </View>
236
+ )}
237
+ </ScrollView>
238
+ )}
246
239
  </SafeAreaView>
247
240
  );
248
241
  };
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
2
  import { View, StyleSheet } from 'react-native';
3
3
  import { createNativeStackNavigator } from '@react-navigation/native-stack';
4
- import Reporting from '@/modules/Reporting';
5
- import ReportingDetail from '@/modules/Reporting/component/ReportingDetail';
4
+ import Reporting from '../modules/Reporting';
5
+ import ReportingDetail from '../modules/Reporting/component/ReportingDetail';
6
+ import ManagementReport from '../modules/ManagementReport';
6
7
 
7
8
  export type AppNavigationProps = {
8
9
  onBackPress?: () => void;
@@ -15,10 +16,20 @@ export function AppNavigation({ onBackPress }: AppNavigationProps = {}) {
15
16
  return (
16
17
  <View style={styles.container}>
17
18
  <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} />
19
+ <Stack.Screen
20
+ name="ReportingList"
21
+ component={Reporting}
22
+ initialParams={{ onBackPress }}
23
+ />
24
+ <Stack.Screen
25
+ name="ReportingDetail"
26
+ component={ReportingDetail}
27
+ />
28
+ <Stack.Screen
29
+ name="ManagementReport"
30
+ component={ManagementReport}
31
+ initialParams={{ onBackPress }}
32
+ />
22
33
  </Stack.Navigator>
23
34
  </View>
24
35
  );