Audit Lite: Reports Module Documentation

1. Overview

The Reports module is a crucial part of the Audit Lite application, allowing users to view, filter, and manage audit reports. This documentation provides a comprehensive guide to the architecture, implementation details, and flows of the Reports module.

2. Architecture

The Reports module follows a clean architecture pattern, with well-defined separation of concerns:


graph TD

UI[UI Components] --> Controllers

Controllers --> UseCases

UseCases --> Repositories

Repositories --> APIs[API Services]

  

subgraph Presentation Layer

UI

Controllers

end

  

subgraph Domain Layer

UseCases

end

  

subgraph Data Layer

Repositories

APIs

end

2.1 Directory Structure


packages/app/

├── features/

│ └── report/

│ ├── home/

│ │ ├── components/

│ │ │ ├── search-bar/

│ │ │ ├── list/

│ │ │ ├── date-selector/

│ │ │ └── report-group-by.tsx

│ │ ├── controllers/

│ │ │ ├── grouped-report-controller.ts

│ │ │ └── report-query.ts

│ │ └── screen.tsx

│ ├── filter-option/

│ │ ├── components/

│ │ │ ├── header/

│ │ │ ├── sort-by/

│ │ │ ├── filter-by/

│ │ │ └── filter-by-date/

│ │ ├── controllers/

│ │ │ ├── filter-controller.ts

│ │ │ └── form-controller.ts

│ │ ├── screen.tsx

│ │ └── type.ts

│ ├── controllers/

│ │ └── params-controller.ts

│ └── type.ts

├── shared/

├── domain/

│ ├── usecase/

│ │ └── report-usecase/

│ │ └── report-usecase.ts

│ └── models/

│ └── report/

│ └── report-model.ts

├── repository/

│ └── report-repository.ts

└── type/

└── report-types.ts

3. Key Components

3.1 Report Home Screen

The main entry point for the reports feature, displaying a list of reports with filtering and grouping options.

GitHub Link: report/home/screen.tsx


graph TD

HomeScreen[ReportHomeScreen] --> SearchBar[ListSearchBar]

HomeScreen --> DateSelector[ReportDateSelector]

HomeScreen --> GroupBy[ReportGroupBy]

HomeScreen --> ReportList

  

ReportList --> ReportQuery[useReportQuery]

ReportQuery --> ReportUsecase

ReportUsecase --> ReportRepository

3.2 Filter Options

Comprehensive filtering options allowing users to refine the report list by various criteria.

GitHub Link: report/filter-option/screen.tsx

3.3 Parameters Controller

Manages URL parameters for filtering and pagination.

GitHub Link: report/controllers/params-controller.ts

4. Data Flow

The flow of data in the Reports module follows this pattern:


sequenceDiagram

participant UI as UI Components

participant Params as Params Controller

participant Query as Report Query

participant Usecase as Report Usecase

participant Repo as Report Repository

participant API as API Service

  

UI->>Params: Update filter parameters

Params->>Query: Updated query parameters

Query->>Usecase: Execute query with parameters

Usecase->>Repo: Make repository call

Repo->>API: Make API request

API-->>Repo: Response data

Repo-->>Usecase: Processed data

Usecase-->>Query: Query results

Query-->>UI: Updated UI with filtered data

5. Filter System

The Reports module implements a robust filtering system that allows users to filter reports by:

  1. Date Range

  2. Site/Location

  3. Auditor (if user is not an auditor)

  4. Search Text

  5. Sort Order

5.1 Filter Controller Flow


flowchart TD

A[User interacts with filter] --> B{Filter type?}

B -->|Date| C[Update date parameters]

B -->|Search| D[Update search parameter]

B -->|Site| E[Update site parameter]

B -->|Auditor| F[Update auditor parameter]

B -->|Sort| G[Update sort parameters]

  

C & D & E & F & G --> H[Apply filters]

H --> I[Navigate with updated params]

I --> J[Refresh report list]

5.2 Filter Implementation Details

The filter system uses React Hook Form for managing form state and validation. The URL parameters are synced with the form state, allowing for bookmarkable and shareable filtered views.

GitHub Link: report/filter-option/controllers/form-controller.ts

Key aspects of the filter implementation:

  1. Form Management: Uses react-hook-form to handle form state and validation with:

 
const methods = useForm<ReportFilterForm>({
 
defaultValues: {
 
endDate: today,
 
sortType: 'desc',
 
startDate: today,
 
groupBy,
 
},
 
});
 
  1. Parameter Synchronization: Syncs form values with URL parameters through:

 
useEffect(() => {
 
params.endDate && methods.setValue('endDate', params.endDate);
 
params.sortBy && methods.setValue('sortBy', params.sortBy);
 
params.sortType && methods.setValue('sortType', params.sortType);
 
params.startDate && methods.setValue('startDate', params.startDate);
 
params.sites && methods.setValue('sites', params.sites);
 
params.auditors && methods.setValue('auditors', params.auditors);
 
params.groupBy && methods.setValue('groupBy', params.groupBy);
 
}, [params, methods]);
 
  1. Default Values: Sets sensible defaults (e.g., today’s date) for all filter parameters

  2. Role-Based Filtering: Shows/hides certain filters based on user role:

 
// Only show auditor filter if user is not an auditor
 
{role !== 'auditor' ? <FilterByAuditor /> : null}
 

5.3 Filter Components

5.3.1 Site Filter

The site filter allows users to select one or more sites to filter reports:

GitHub Link: report/filter-option/components/filter-by/site/index.tsx

  1. Implemented as a form control with React Hook Form

  2. Uses a sheet (modal) for site selection

  3. Shows selected count in the filter list

 
<Controller
 
control={control}
 
name="sites"
 
render={({ field: { value, onChange } }) => (
 
<>
 
<ListItem onPress={showSheet}>
 
<ListItem.Text flex={1}>{`${LL.schedule.filter.filter.site()}`}</ListItem.Text>
 
{value?.length ? (
 
<ListItem.HelperText>{Array.isArray(value) ? value.length : Number(Boolean(value))}</ListItem.HelperText>
 
) : null}
 
<ListItem.Icon>
 
<ChevronRight />
 
</ListItem.Icon>
 
</ListItem>
 
<SuspenseInitialRender fallback={null}>
 
<FilterSiteSheet
 
open={open}
 
onOpenChange={setOpen}
 
onClear={() => onChange(undefined)}
 
onSave={(value) => onChange(value)}
 
initialSelectedId={value}
 
/>
 
</SuspenseInitialRender>
 
</>
 
)}
 
/>
 

5.3.2 Date Filter

The date filter uses a date range picker to filter reports by date:

GitHub Link: report/filter-option/components/filter-by-date

  1. Uses dayjs for date manipulation and formatting

  2. Provides preset options (Today, Yesterday, This Week, etc.)

  3. Allows custom date range selection

5.3.3 Auditor Filter

The auditor filter allows filtering by one or more auditors:

GitHub Link: report/filter-option/components/filter-by/auditor

  1. Only visible to users with admin or manager roles

  2. Uses a searchable sheet for auditor selection

  3. Shows selected count in the filter list

5.4 Filter Application

When filters are applied, the controller navigates to the report list screen with updated URL parameters:

 
const updateFilter = formMethods.handleSubmit((params) => {
 
navigateToFilteredScreen(params);
 
});
 
  
 
const navigateToFilteredScreen = useCallback(
 
(params: ReportRouteParams) => {
 
const navigationParams = {
 
pathname: '/home/report',
 
query: params as any,
 
};
 
router.push(navigationParams);
 
},
 
[router],
 
);
 

6. Group By Feature

The Reports module includes a powerful grouping feature that allows users to visualize reports grouped by:

  1. None (flat list)

  2. Site/Location

  3. Auditor (if user is not an auditor)

6.1 Group By Implementation

GitHub Link: report/home/components/report-group-by.tsx

The grouping UI is implemented as a radio group component:

 
<RadioGroup
 
aria-labelledby={LL.report.filter.groupBy.selectOne()}
 
defaultValue={groupBy}
 
onValueChange={setGroupBy}
 
value={groupBy}
 
name="groupBy"
 
flexDirection="row"
 
gap="$tw2.5"
 
>
 
<RadioGroupItemWithLabel size="$3" value="none" label={LL.report.filter.groupBy.none()} />
 
<RadioGroupItemWithLabel size="$3" value="site" label={LL.report.filter.groupBy.location()} />
 
{role !== 'auditor' ? (
 
<RadioGroupItemWithLabel size="$3" value="auditor" label={LL.report.filter.groupBy.auditor()} />
 
) : null}
 
</RadioGroup>
 

The grouping functionality is implemented in the grouped-report-controller.ts which:

GitHub Link: report/home/controllers/grouped-report-controller.ts

  1. Processes the raw report data through the useGroupedQuery method

  2. Groups it according to the selected grouping option:

 
useGroupBy = () => {
 
const { params, setParams } = useReportParams();
 
const groupBy = params?.groupBy ?? 'none';
 
  
 
const setGroupBy = useCallback(
 
(value: string) => {
 
if (!value) throw new Error('Value is required');
 
  
 
if (!['none', 'site', 'auditor'].includes(value)) throw new Error('Invalid value');
 
  
 
setParams({ groupBy: value as GetGroupedReportParams['groupBy'] });
 
},
 
[setParams],
 
);
 
  
 
return { groupBy, setGroupBy };
 
};
 
  1. Handles expandable sections with the section controller:

 
useSection = () => {
 
const [expandedSections, setExpandedSections] = useAtom(this.expandedSectionsAtom);
 
  
 
const toggleSection = useCallback(
 
(sectionId: string) => {
 
if (!sectionId) throw new Error('Section ID is required');
 
  
 
setExpandedSections((prev) => ({ [sectionId]: !prev[sectionId] }));
 
},
 
[setExpandedSections],
 
);
 
  
 
return { expandedSections, toggleSection };
 
};
 
  1. Performs optimized queries for the grouped data:

 
useGroupedQuery = () => {
 
const queryParams = this.useReportListParams();
 
  
 
return useInfiniteQuery({
 
initialPageParam: 1,
 
queryFn: async ({ pageParam = 1 }) => {
 
return groupedReportService.getGroupedReport({ ...queryParams, page: pageParam });
 
},
 
getNextPageParam: (lastPage) => {
 
return lastPage?.data?.page && lastPage.data.totalPages && lastPage?.data?.page < lastPage.data.totalPages
 
? lastPage.data.page + 1
 
: undefined;
 
},
 
queryKey: reportQueryKey.GET_GROUPED_REPORT(queryParams),
 
enabled: !!queryParams.groupBy,
 
});
 
};
 
  1. Handles infinite scrolling and pagination for grouped data:

 
useGroupedDataQuery = () => {
 
const { groupID, expandedSections } = this.useExpandedSection();
 
const expandedSectionsLength = useMemo(
 
() => Object.entries(expandedSections).filter(([, value]) => value).length,
 
[expandedSections],
 
);
 
const queryParams = this.useGroupedDataQueryParams();
 
  
 
return useInfiniteQuery({
 
initialPageParam: 1,
 
queryFn: async ({ pageParam = 1 }) => {
 
const res = await groupedReportService.getGroupedReportData({ ...queryParams, page: 1, dataPage: pageParam });
 
  
 
if (!res.data) throw new Error('No data found');
 
  
 
return res.data;
 
},
 
getNextPageParam: (lastPage) => {
 
if (!groupID) return undefined;
 
  
 
const groupedData = lastPage.docs[groupID];
 
  
 
if (!groupedData) return undefined;
 
  
 
const { dataPage, dataLength, dataLimit } = groupedData;
 
  
 
if (!dataPage || !dataLength || !dataLimit) return undefined;
 
  
 
const totalPages = Math.ceil(dataLength / dataLimit);
 
  
 
if (dataPage < totalPages) {
 
return dataPage + 1;
 
}
 
  
 
return undefined;
 
},
 
queryKey: reportQueryKey.GET_GROUPED_REPORT_DATA(queryParams),
 
enabled: expandedSectionsLength === 1,
 
});
 
};
 

graph TD

RawData[Raw Report Data] --> GroupController[Grouped Report Controller]

GroupController --> |Site Grouping| SiteGroups[Reports Grouped by Site]

GroupController --> |Auditor Grouping| AuditorGroups[Reports Grouped by Auditor]

GroupController --> |No Grouping| FlatList[Flat Report List]

  

SiteGroups & AuditorGroups & FlatList --> UI[UI Rendering]

  

SiteGroups --> ExpandedState{Is Section Expanded?}

AuditorGroups --> ExpandedState

  

ExpandedState -->|Yes| GroupedDataQuery[Fetch Group Data]

ExpandedState -->|No| ShowSummary[Show Summary]

  

GroupedDataQuery --> InfiniteScroll[Infinite Scroll Handler]

InfiniteScroll --> LoadMore[Load More Data]

6.2 Grouping Performance Optimizations

The grouped report controller implements several performance optimizations:

  1. Query Caching: Utilizes React Query’s caching capabilities to minimize network requests

  2. Selective Data Loading: Only loads detailed data for expanded groups

  3. Viewability Tracking: Uses React Native’s ViewToken system to implement efficient infinite scrolling

  4. Query Invalidation: Manages query invalidation carefully to avoid unnecessary re-fetching

7. API Endpoints

The Reports module interacts with several API endpoints to fetch and manage report data.

| Endpoint | Method | Description | File Path |

| ---------------------------- | ------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |

| /reports | GET | Get a list of reports with filtering | report-repository.ts |

| /reports/create | POST | Create a new report | report-repository.ts |

| /[schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md)/check-in | POST | Start a check-in report | schedule-repository.ts |

| /[schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md)/check-in/status | GET | Get check-in status | schedule-repository.ts |

8. Implementation Details

Report List Query

The report list is fetched using TanStack Query (React Query) for efficient data fetching, caching, and pagination.

GitHub Link: report/home/controllers/report-query.ts

Key features:

  1. Infinite Query: Implements infinite scrolling for the report list

  2. Pagination: Handles pagination with page and limit parameters

  3. Query Key Management: Uses dynamic query keys based on filter parameters

  4. Cache Management: Leverages React Query’s caching capabilities

8.2 Parameter Management

URL parameters are managed using Solito’s createParam utility, providing a consistent way to work with route parameters across native and web platforms.

GitHub Link: report/controllers/params-controller.ts

 
const {
 
useParam: useReportParam,
 
useParams,
 
useUpdateParams: useUpdateReportParams,
 
} = createParam<ReportRouteParams>();
 

8.3 Date Handling

The Reports module uses DayJS for consistent date handling across the application.

 
// Get today's date in YYYY-MM-DD format
 
const today = dayjs().format('YYYY-MM-DD');
 
  
 
// Parse and validate date parameters
 
if (!dayjs(value as string).isValid() || typeof value !== 'string') return today;
 

9. UI Components

Allows users to search for reports by text. The search functionality is implemented as part of the filter system and is synchronized with URL parameters.

9.2 Date Selector

Provides a date range selector for filtering reports by date.

GitHub Link: report/home/components/date-selector/index.tsx

The date selector provides three pre-defined date ranges:

  1. Today

  2. Last 7 days

  3. Last 30 days

Implementation details:

 
const options: Option[] = useMemo(
 
() => [
 
{
 
endDate: dayjs().format('YYYY-MM-DD'),
 
key: 'today',
 
label: 'today',
 
startDate: dayjs().format('YYYY-MM-DD'),
 
},
 
{
 
endDate: dayjs().format('YYYY-MM-DD'),
 
key: 'last7days',
 
label: 'last7days',
 
startDate: dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
 
},
 
{
 
endDate: dayjs().format('YYYY-MM-DD'),
 
key: 'last30days',
 
label: 'last30days',
 
startDate: dayjs().subtract(30, 'day').format('YYYY-MM-DD'),
 
},
 
],
 
[],
 
);
 

The selected date range is visually indicated, and clicking on an option updates the date parameters:

 
const onPressItem = (item: Option) => {
 
setDates({
 
end: item.endDate,
 
start: item.startDate,
 
});
 
};
 

9.3 Report List

Displays the list of reports, potentially grouped by site, auditor, or date.

GitHub Link: report/home/components/list/index.tsx

The report list has two major display modes:

  1. Flat list (when groupBy is ‘none’)

  2. Grouped list (when groupBy is ‘site’ or ‘auditor’)

 
const ReportList = () => {
 
const { groupBy } = groupedReportController.useGroupBy();
 
  
 
if (groupBy === 'none') {
 
return (
 
<SuspenseInitialRender fallback={<Spinner py="$tw10" />}>
 
<List />
 
</SuspenseInitialRender>
 
);
 
}
 
  
 
return (
 
<SuspenseInitialRender fallback={<Spinner py="$tw10" />}>
 
<ReportGroupedList />
 
</SuspenseInitialRender>
 
);
 
};
 

Key features:

  1. Virtual List: Uses virtualization for performance optimization

  2. Infinite Scrolling: Implements infinite scrolling for pagination

  3. Pull-to-Refresh: Allows users to refresh the list by pulling down

  4. Loading States: Shows appropriate loading states during data fetching

 
<VirtualList
 
contentContainerStyle={contentContainerStyle}
 
data={mergedPage}
 
keyExtractor={(item, index) => `${item?.downloadUrl}-${index}`}
 
renderItem={({ item }) => (item ? <ReportListItem {...item} /> : null)}
 
onEndReached={() => {
 
if (hasNextPage && !isFetchingNextPage) {
 
fetchNextPage();
 
}
 
}}
 
onEndReachedThreshold={0.5}
 
ItemSeparatorComponent={Separator}
 
estimatedItemSize={110}
 
refreshControl={<RefreshControl refreshing={false} onRefresh={refetch} />}
 
ListEmptyComponent={isFetching || isLoading || isRefetching ? ReportListLoading : ReportListEmpty}
 
/>
 

9.4 Filter Options

Provides a comprehensive interface for applying filters to the report list.

GitHub Link: report/filter-option/screen.tsx

The filter screen is organized into distinct sections for different filter types:

 
<YStack flex={1} bg="$shade0" pt={20}>
 
<FilterOptionHeader />
 
<ScrollView flex={1} pt="$tw5">
 
<YStack gap="$tw2">
 
<YStack mx="$tw4">
 
<Caption pb="$tw2">{`${LL.common.filterBy()}`}</Caption>
 
<Separator />
 
</YStack>
 
<YStack>
 
{role !== 'auditor' ? <FilterByAuditor /> : null}
 
<FilterBySite />
 
<FilterByDate />
 
</YStack>
 
<FilterSortBy />
 
</YStack>
 
</ScrollView>
 
<XStack pb="$tw6" pt="$tw2" px="$tw4" gap="$tw3">
 
<Button flex={1} variant="outlined" onPress={resetFilter}>
 
{LL.common.reset()}
 
</Button>
 
<Button flex={1} onPress={updateFilter}>
 
{LL.common.apply()}
 
</Button>
 
</XStack>
 
</YStack>
 

Key components:

  1. Filter Header: Shows the title and close button

  2. Filter Sections: Different filter options organized in sections

  3. Action Buttons: “Reset” and “Apply” buttons at the bottom

  4. Role-Based Display: Some filters are only shown to users with specific roles


graph TD

FilterScreen[Filter Screen] --> SiteFilter[Filter By Site]

FilterScreen --> AuditorFilter[Filter By Auditor]

FilterScreen --> DateFilter[Filter By Date]

FilterScreen --> SortOptions[Sort Options]

  

SiteFilter & AuditorFilter & DateFilter & SortOptions --> ApplyButton[Apply Filters]

FilterScreen --> ResetButton[Reset Filters]

10. Offline Support

The Reports module includes offline support capabilities:

  1. Offline Report Creation: Ability to create reports while offline

  2. Offline Data Synchronization: Sync offline reports when connectivity is restored

  3. Conflict Resolution: Logic to handle conflicts between offline and server data

10.1 Offline Report Flow


sequenceDiagram

participant User

participant App

participant OfflineStorage

participant Server

  

User->>App: Create report

App->>App: Check connectivity

  

alt Is Online

App->>Server: Create report

Server-->>App: Report created

else Is Offline

App->>OfflineStorage: Store report

OfflineStorage-->>App: Report stored

  

Note over App,OfflineStorage: Later when connectivity is restored

  

App->>OfflineStorage: Get offline reports

OfflineStorage-->>App: Offline reports

App->>Server: Sync reports

Server-->>App: Reports synced

end

10.2 Offline Implementation Details

The offline functionality is implemented in the report usecase with several key components:

  1. Start Check-In Report Logic:

 
public startCheckInReport = async (params: StartOfflineCheckInReportParams): Promise<CheckInCreateReportDetail> => {
 
const { startCheckInReportParams, isOfflineMode = false, schedule } = params;
 
if (isOfflineMode) {
 
const checkInRes = reportOfflineUseCase.startOfflineCheckInReport(params);
 
  
 
reportService.setCache({
 
firebaseReportID: checkInRes.firebaseReportID || checkInRes.reportID,
 
auditType: 'single',
 
value: checkInRes.report,
 
});
 
  
 
return checkInRes;
 
}
 
  
 
// Logic to handle online mode or sync offline reports...
 
}
 
  1. Report Service Cache:

The report service includes caching mechanisms to store reports locally:

 
reportService.setCache({
 
firebaseReportID: uploadedReport.firebaseReportID,
 
auditType: 'single',
 
value: {
 
...uploadedReport.report,
 
schedule,
 
},
 
});
 
  1. Conflict Resolution:

When connectivity is restored, the system compares timestamps and data changes to resolve conflicts between local and server versions:

 
latestReport = await this.useLatestReport.bind(this)(uploadedReport.firebaseReportID, uploadedReport.siteID, [
 
uploadedReport.report,
 
cachedReport,
 
]);
 
  1. Offline Report ID Generation:

Generates a unique ID for offline reports that can be mapped to server reports later:

 
const offlineReportId = reportOfflineUseCase.generateOfflineReportID({
 
schedule,
 
});
 

11. Advanced Topics

11.1 Report Flag Checking

The Reports module includes sophisticated logic for checking report flags based on answer types:

GitHub Link: report-usecase.ts

The flag checking system handles different question types with specific logic for each:

 
// eslint-disable-next-line complexity
 
public checkProgressBasedOnType = (question: Question) => {
 
if (question.answer === 'not-applicable') return true;
 
  
 
if (question.type === QuestionTypes.OPEN && !!question.comment) return true;
 
  
 
if (!question.answer && !question.answers?.length) return false;
 
  
 
switch (question.type) {
 
case QuestionTypes.BINARY:
 
case QuestionTypes.BINARY_WITH_RED_FLAG:
 
return (
 
question.answer === AnswerFlag.GREEN_FLAG ||
 
(question.answer !== AnswerFlag.GREEN_FLAG && !!question.comment)
 
);
 
  
 
case QuestionTypes.MULTIPLE_CHOICE_SCORE:
 
return (
 
question.flag === AnswerFlag.GREEN_FLAG ||
 
(question.flag !== AnswerFlag.GREEN_FLAG && !!question.comment)
 
);
 
  
 
case QuestionTypes.RANGE_FLAG: {
 
const flag = getRangeFlagDetail(question.answer, question.rangeOptions, question);
 
return flag === AnswerFlag.GREEN_FLAG ||
 
(flag && this.nonGreenFlag.includes(flag) && !!question.comment);
 
}
 
  
 
case QuestionTypes.SCORE: {
 
const flag = getScoreFlagDetail(question);
 
return flag === AnswerFlag.GREEN_FLAG ||
 
(flag && this.nonGreenFlag.includes(flag) && !!question.comment);
 
}
 
  
 
case QuestionTypes.CHECKLIST: {
 
if (question.checklistsV2) {
 
const answerSet = new Set(question.answers?.map((answer) => answer));
 
const hasRedFlag = question.checklistsV2.some(
 
(checklist) => checklist.hasRedFlag && answerSet.has(checklist.label)
 
);
 
  
 
return !hasRedFlag || (hasRedFlag && !!question.comment);
 
}
 
}
 
  
 
case QuestionTypes.NUMBER:
 
case QuestionTypes.MULTIPLE_CHOICE:
 
default:
 
return true;
 
}
 
}
 

This flag checking system is critical for:

  1. Progress Tracking: Determining whether a question has been sufficiently answered

  2. Validation: Ensuring required comments are provided for flagged answers

  3. Flag Calculation: Computing the overall flag status of reports

  4. UI Indication: Displaying appropriate flag indicators in the UI

11.2 Report Progress Calculation

The Reports module calculates overall progress of each report by:

  1. Evaluating each question’s completion status

  2. Tracking required vs. completed questions

  3. Computing a percentage of completion

  4. Showing progress indicators in the UI

This progress calculation is essential for tracking audit completion and ensuring all required data is collected before submission.

12. Performance Considerations

The Reports module implements several performance optimizations to ensure a smooth user experience, even with large datasets and on devices with limited resources:

12.1 Virtualized Lists

All list components use virtualization to render only visible items:

 
<VirtualList
 
contentContainerStyle={contentContainerStyle}
 
data={mergedPage}
 
keyExtractor={(item, index) => `${item?.downloadUrl}-${index}`}
 
renderItem={({ item }) => (item ? <ReportListItem {...item} /> : null)}
 
estimatedItemSize={110}
 
// ...additional props
 
/>
 

This significantly reduces memory usage and improves scrolling performance, especially for large lists.

12.2 Query Optimization

The module implements several query optimizations:

  1. Pagination: Data is fetched in smaller chunks using page and limit parameters

  2. Selective Loading: Detailed data is only loaded for expanded group sections

  3. Query Key Management: Dynamic query keys ensure proper cache invalidation

 
return useInfiniteQuery({
 
initialPageParam: 1,
 
queryFn: async ({ pageParam = 1 }) => {
 
return groupedReportService.getGroupedReport({ ...queryParams, page: pageParam });
 
},
 
getNextPageParam: (lastPage) => {
 
// Logic to determine if more pages exist
 
},
 
queryKey: reportQueryKey.GET_GROUPED_REPORT(queryParams),
 
enabled: !!queryParams.groupBy,
 
});
 

12.3 Deferred Rendering

The application uses suspense and deferred rendering to improve perceived performance:

 
<SuspenseInitialRender fallback={<Spinner py="$tw10" />}>
 
<ReportGroupedList />
 
</SuspenseInitialRender>
 

This allows the UI to remain responsive while data is being loaded.

12.4 Memoization

Expensive computations are memoized to prevent unnecessary recalculations:

 
const mergedPage = useMemo(() => {
 
if (!data) return [];
 
const pages = data.pages.filter((page) => Boolean(page?.data?.data?.docs));
 
return pages.flatMap((page) => page.data?.data?.docs);
 
}, [data]);
 

12.5 Viewport-Aware Loading

The module uses viewport awareness to trigger data loading only when needed:

 
onViewableItemsChanged = useCallback(
 
(info: { viewableItems: ViewToken[]; changed: ViewToken[] }) => {
 
if (Object.keys(expandedSections).length === 1) {
 
const viewableItems = info.viewableItems.filter((item) => item.isViewable);
 
// Load more data when certain items are visible
 
}
 
},
 
[expandedSections],
 
);
 

12.6 Progressive Enhancement

The module is designed with progressive enhancement in mind, with:

  1. Optimistic UI Updates: UI updates before server confirmation

  2. Fallbacks: Graceful degradation when components fail to load

  3. Error Boundaries: Isolating failures to prevent cascading [issues](../Issue Tracker/IssueTrackerOverview.md)

These optimizations collectively ensure that the Reports module delivers a responsive and efficient user experience across different devices and network conditions.

13. Conclusion

The Reports module is a sophisticated feature of the Audit Lite application, providing powerful reporting capabilities with extensive filtering, grouping, and offline support. Its clean architecture ensures maintainability and extensibility.