@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.
- 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 +1715 -4257
- package/src/index.ts +1 -0
- package/src/modules/ManagementReport/components/AnalyticsChartView.tsx +530 -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/ReportChart.tsx +1 -1
- package/src/modules/Reporting/component/ReportFilterModal.tsx +1 -1
- package/src/modules/Reporting/component/ReportingDetail.tsx +86 -98
- package/src/modules/Reporting/index.tsx +72 -79
- package/src/navigation/index.tsx +17 -6
- package/src/screens/DevToolsCorner.tsx +43 -8
- package/tailwind.config.js +11 -0
|
@@ -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 {
|
|
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 '
|
|
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
|
|
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>
|
|
@@ -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 '
|
|
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
|
-
|
|
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-
|
|
105
|
-
<View className="px-4 flex-row justify-between items-center mb-2
|
|
106
|
-
<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">
|
|
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-
|
|
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={
|
|
121
|
+
<Icon name="bar-chart-2" type="Feather" size={15} color={iconBrandColor} />
|
|
124
122
|
</View>
|
|
125
|
-
<Text className="text-xs font-extrabold
|
|
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
|
|
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
|
|
138
|
-
<View className="py-
|
|
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-
|
|
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-
|
|
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={
|
|
160
|
-
color={isActive ? '#FFFFFF' :
|
|
157
|
+
size={12}
|
|
158
|
+
color={isActive ? '#FFFFFF' : '#94a3b8'}
|
|
161
159
|
/>
|
|
162
160
|
<Text
|
|
163
|
-
|
|
164
|
-
|
|
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={`
|
|
167
|
+
className={`px-1.5 py-0.5 rounded-full ${
|
|
173
168
|
isActive
|
|
174
|
-
? 'bg-white/
|
|
175
|
-
: 'bg-slate-100 dark:bg-
|
|
169
|
+
? 'bg-white/20'
|
|
170
|
+
: 'bg-slate-100 dark:bg-slate-800'
|
|
176
171
|
}`}
|
|
177
172
|
>
|
|
178
173
|
<Text
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
{/*
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
filteredReports.
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
};
|
package/src/navigation/index.tsx
CHANGED
|
@@ -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 '
|
|
5
|
-
import ReportingDetail from '
|
|
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
|
|
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
|
);
|