@suflon/rnmd-reporting 0.0.2 → 0.0.4
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 +2 -2
- package/index.js +3 -0
- package/metro.config.js +31 -0
- package/package.json +4 -3
- package/patches/@suflon+native-ui+0.0.18.patch +1845 -4212
- package/src/index.ts +1 -0
- package/src/modules/ManagementReport/components/AnalyticsChartView.tsx +294 -0
- package/src/modules/ManagementReport/components/CategoryTabs.tsx +85 -0
- package/src/modules/ManagementReport/components/DetailsListingView.tsx +781 -0
- package/src/modules/ManagementReport/components/DoctorFilterDropdown.tsx +157 -0
- package/src/modules/ManagementReport/components/KpiMetricsGrid.tsx +183 -0
- package/src/modules/ManagementReport/components/ManagementFilterModal.tsx +142 -0
- package/src/modules/ManagementReport/components/ManagementReportHeader.tsx +115 -0
- package/src/modules/ManagementReport/components/SubModuleTabs.tsx +58 -0
- package/src/modules/ManagementReport/components/TimeframeDropdown.tsx +162 -0
- package/src/modules/ManagementReport/components/ViewModeSwitcher.tsx +71 -0
- package/src/modules/ManagementReport/index.tsx +202 -0
- package/src/modules/Reporting/component/ReportingDetail.tsx +85 -97
- package/src/modules/Reporting/index.tsx +68 -74
- package/src/navigation/index.tsx +15 -4
- package/src/screens/DevToolsCorner.tsx +43 -8
- package/tailwind.config.js +11 -0
|
@@ -1,6 +1,6 @@
|
|
|
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 {
|
|
3
|
+
import { useNavigation, useRoute } 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';
|
|
@@ -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
|
|
17
|
-
const
|
|
18
|
-
|
|
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-
|
|
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 ? '#
|
|
249
|
+
color={isActive ? '#FFFFFF' : (isDarkMode ? '#94a3b8' : themeColors.slate[600])}
|
|
257
250
|
/>
|
|
258
251
|
<Text
|
|
259
|
-
style={{ color: isActive ? '#FFFFFF' : (isDarkMode ? '#
|
|
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
|
-
{/*
|
|
282
|
-
<View className="
|
|
283
|
-
<View className="flex-
|
|
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
|
|
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-
|
|
326
|
-
<
|
|
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
|
|
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-
|
|
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
|
|
346
|
-
<View className="flex-row justify-between items-center
|
|
347
|
-
<View className="flex-row items-center gap-
|
|
348
|
-
<View className=
|
|
349
|
-
<Text style={{ color: isDarkMode ? '#FFFFFF' : '#0F172A' }} className="text-
|
|
350
|
-
{
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
)
|
|
348
|
+
)}
|
|
361
349
|
</View>
|
|
362
350
|
|
|
363
|
-
{/*
|
|
364
|
-
<View className="flex-row flex-wrap gap-2
|
|
365
|
-
{
|
|
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-
|
|
370
|
-
<Text style={{ color: isDarkMode ? '#9CA3AF' : '#64748B' }} className="text-[
|
|
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-
|
|
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-
|
|
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-
|
|
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]
|
|
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
|
|
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>
|
|
@@ -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
|
-
|
|
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">
|
|
@@ -88,7 +86,8 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
|
|
|
88
86
|
showActionIcon={false}
|
|
89
87
|
/>
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
{/* Fixed Top Controls */}
|
|
90
|
+
<View>
|
|
92
91
|
{/* Search Bar */}
|
|
93
92
|
<View className="px-4 pt-3.5">
|
|
94
93
|
<SearchFilter
|
|
@@ -100,9 +99,9 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
|
|
|
100
99
|
|
|
101
100
|
{/* Quick Access Featured Carousel */}
|
|
102
101
|
{featuredPicks.length > 0 && (
|
|
103
|
-
<View className="pt-
|
|
104
|
-
<View className="px-4 flex-row justify-between items-center mb-2
|
|
105
|
-
<Text className="text-[11px] font-extrabold
|
|
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">
|
|
106
105
|
FEATURED REPORTS
|
|
107
106
|
</Text>
|
|
108
107
|
<Text className="text-[11px] font-bold text-violet-600 dark:text-violet-400">
|
|
@@ -116,15 +115,15 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
|
|
|
116
115
|
key={pick.id || i}
|
|
117
116
|
onPress={() => handleReportPress(pick)}
|
|
118
117
|
activeOpacity={0.8}
|
|
119
|
-
className="w-[170px] bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3 border border-slate-200 dark:border-
|
|
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"
|
|
120
119
|
>
|
|
121
120
|
<View className="w-7 h-7 rounded-lg bg-violet-50 dark:bg-violet-950/40 items-center justify-center">
|
|
122
|
-
<Icon name="bar-chart-2" type="Feather" size={
|
|
121
|
+
<Icon name="bar-chart-2" type="Feather" size={15} color={iconBrandColor} />
|
|
123
122
|
</View>
|
|
124
|
-
<Text className="text-xs font-extrabold
|
|
123
|
+
<Text style={{ color: isDark ? '#FFFFFF' : '#0F172A' }} className="text-xs font-extrabold" numberOfLines={1}>
|
|
125
124
|
{pick.name}
|
|
126
125
|
</Text>
|
|
127
|
-
<Text className="text-[9px] font-semibold
|
|
126
|
+
<Text style={{ color: isDark ? '#9CA3AF' : '#94A3B8' }} className="text-[9px] font-semibold" numberOfLines={1}>
|
|
128
127
|
{pick.sub_report_class ? SUB_CLASS_LABEL_MAP[pick.sub_report_class] || pick.sub_report_class : 'Report'}
|
|
129
128
|
</Text>
|
|
130
129
|
</TouchableOpacity>
|
|
@@ -133,8 +132,8 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
|
|
|
133
132
|
</View>
|
|
134
133
|
)}
|
|
135
134
|
|
|
136
|
-
{/* Category Pills Switcher
|
|
137
|
-
<View className="
|
|
135
|
+
{/* Category Pills Switcher */}
|
|
136
|
+
<View className="py-3.5">
|
|
138
137
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingHorizontal: 16, gap: 8 }}>
|
|
139
138
|
{categoryTabs.map((tab) => {
|
|
140
139
|
const isActive = activeTabId === tab.id;
|
|
@@ -146,40 +145,34 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
|
|
|
146
145
|
key={tab.id}
|
|
147
146
|
onPress={() => setActiveTabId(tab.id)}
|
|
148
147
|
activeOpacity={0.8}
|
|
149
|
-
className={`flex-row items-center gap-1.5 px-3.5 py-
|
|
148
|
+
className={`flex-row items-center gap-1.5 px-3.5 py-1.5 rounded-xl border ${
|
|
150
149
|
isActive
|
|
151
150
|
? 'bg-violet-600 border-violet-600'
|
|
152
|
-
: 'bg-white dark:bg-background-darkSecondaryBg border-slate-200 dark:border-
|
|
151
|
+
: 'bg-white dark:bg-background-darkSecondaryBg border-slate-200/70 dark:border-slate-800'
|
|
153
152
|
}`}
|
|
154
153
|
>
|
|
155
154
|
<Icon
|
|
156
155
|
name={tab.icon}
|
|
157
156
|
type="Feather"
|
|
158
|
-
size={
|
|
159
|
-
color={isActive ? '#FFFFFF' :
|
|
157
|
+
size={12}
|
|
158
|
+
color={isActive ? '#FFFFFF' : '#94a3b8'}
|
|
160
159
|
/>
|
|
161
160
|
<Text
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
? 'text-white'
|
|
165
|
-
: 'text-slate-700 dark:text-gray-200'
|
|
166
|
-
}`}
|
|
161
|
+
style={{ color: isActive ? '#FFFFFF' : (isDark ? '#9CA3AF' : '#94A3B8') }}
|
|
162
|
+
className={`text-xs ${isActive ? 'font-bold' : 'font-normal'}`}
|
|
167
163
|
>
|
|
168
164
|
{tab.label}
|
|
169
165
|
</Text>
|
|
170
166
|
<View
|
|
171
|
-
className={`
|
|
167
|
+
className={`px-1.5 py-0.5 rounded-full ${
|
|
172
168
|
isActive
|
|
173
|
-
? 'bg-white/
|
|
174
|
-
: 'bg-slate-100 dark:bg-
|
|
169
|
+
? 'bg-white/20'
|
|
170
|
+
: 'bg-slate-100 dark:bg-slate-800'
|
|
175
171
|
}`}
|
|
176
172
|
>
|
|
177
173
|
<Text
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
? 'text-white'
|
|
181
|
-
: 'text-slate-600 dark:text-gray-300'
|
|
182
|
-
}`}
|
|
174
|
+
style={{ color: isActive ? '#FFFFFF' : (isDark ? '#9CA3AF' : '#94A3B8') }}
|
|
175
|
+
className={`text-[9px] ${isActive ? 'font-bold' : 'font-normal'}`}
|
|
183
176
|
>
|
|
184
177
|
{count}
|
|
185
178
|
</Text>
|
|
@@ -189,59 +182,60 @@ const Reporting = ({ onBackPress }: { onBackPress?: () => void } = {}) => {
|
|
|
189
182
|
})}
|
|
190
183
|
</ScrollView>
|
|
191
184
|
</View>
|
|
185
|
+
</View>
|
|
192
186
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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>
|
|
202
205
|
</View>
|
|
203
206
|
) : (
|
|
204
|
-
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
activeOpacity={0.8}
|
|
209
|
-
className="bg-white dark:bg-background-darkSecondaryBg rounded-2xl p-3.5 border border-slate-200 dark:border-gray-800 flex-row items-center"
|
|
210
|
-
>
|
|
211
|
-
<View className="flex-row items-center gap-3 flex-1">
|
|
212
|
-
<View className="w-10 h-10 rounded-xl bg-violet-50 dark:bg-violet-950/40 items-center justify-center">
|
|
213
|
-
<Icon
|
|
214
|
-
name={SUB_CLASS_ICON_MAP[item.sub_report_class] || (item.is_dynamic ? "pie-chart" : "file-text")}
|
|
215
|
-
type="Feather"
|
|
216
|
-
size={20}
|
|
217
|
-
color={iconBrandColor}
|
|
218
|
-
/>
|
|
219
|
-
</View>
|
|
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>
|
|
220
211
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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}
|
|
228
226
|
</Text>
|
|
229
|
-
<Text className="text-[10px]
|
|
230
|
-
|
|
231
|
-
{item.report_type || 'LISTING'}
|
|
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')}
|
|
232
229
|
</Text>
|
|
233
230
|
</View>
|
|
234
231
|
</View>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
</View>
|
|
240
|
-
</TouchableOpacity>
|
|
241
|
-
))
|
|
232
|
+
<Icon name="chevron-right" type="Feather" size={14} color={isDark ? '#6B7280' : themeColors.slate[400]} />
|
|
233
|
+
</TouchableOpacity>
|
|
234
|
+
))}
|
|
235
|
+
</View>
|
|
242
236
|
)}
|
|
243
|
-
</
|
|
244
|
-
|
|
237
|
+
</ScrollView>
|
|
238
|
+
)}
|
|
245
239
|
</SafeAreaView>
|
|
246
240
|
);
|
|
247
241
|
};
|
package/src/navigation/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { View, StyleSheet } from 'react-native';
|
|
|
3
3
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
4
4
|
import Reporting from '@/modules/Reporting';
|
|
5
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
);
|