diff --git a/src/modules/crm/zoho/screens/ZohoCrmDataScreen.tsx b/src/modules/crm/zoho/screens/ZohoCrmDataScreen.tsx
index 3432a98..a6689ba 100644
--- a/src/modules/crm/zoho/screens/ZohoCrmDataScreen.tsx
+++ b/src/modules/crm/zoho/screens/ZohoCrmDataScreen.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useMemo } from 'react';
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
import {
View,
Text,
@@ -8,6 +8,7 @@ import {
RefreshControl,
FlatList,
Alert,
+ ActivityIndicator,
} from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import type { AppDispatch } from '@/store/store';
@@ -26,9 +27,26 @@ import {
selectPurchaseOrders,
selectInvoices,
selectCrmLoading,
- selectCrmErrors
+ selectCrmErrors,
+ selectLeadsPagination,
+ selectTasksPagination,
+ selectContactsPagination,
+ selectDealsPagination,
+ selectSalesOrdersPagination,
+ selectPurchaseOrdersPagination,
+ selectInvoicesPagination
} from '../store/selectors';
-import { fetchAllCrmData } from '../store/crmSlice';
+import {
+ fetchAllCrmData,
+ fetchLeads,
+ fetchTasks,
+ fetchContacts,
+ fetchDeals,
+ resetLeadsPagination,
+ resetTasksPagination,
+ resetContactsPagination,
+ resetDealsPagination
+} from '../store/crmSlice';
import type { RootState } from '@/store/store';
const ZohoCrmDataScreen: React.FC = () => {
@@ -47,6 +65,15 @@ const ZohoCrmDataScreen: React.FC = () => {
const invoices = useSelector(selectInvoices);
const loading = useSelector(selectCrmLoading);
const errors = useSelector(selectCrmErrors);
+
+ // Pagination selectors
+ const leadsPagination = useSelector(selectLeadsPagination);
+ const tasksPagination = useSelector(selectTasksPagination);
+ const contactsPagination = useSelector(selectContactsPagination);
+ const dealsPagination = useSelector(selectDealsPagination);
+ const salesOrdersPagination = useSelector(selectSalesOrdersPagination);
+ const purchaseOrdersPagination = useSelector(selectPurchaseOrdersPagination);
+ const invoicesPagination = useSelector(selectInvoicesPagination);
// Create CRM data object from Redux state
const crmData: CrmData = useMemo(() => ({
@@ -60,7 +87,7 @@ const ZohoCrmDataScreen: React.FC = () => {
}), [leads, tasks, contacts, deals, salesOrders, purchaseOrders, invoices]);
// Fetch CRM data using Redux
- const fetchCrmData = async (showRefresh = false) => {
+ const fetchCrmData = useCallback(async (showRefresh = false) => {
try {
if (showRefresh) {
setRefreshing(true);
@@ -78,23 +105,87 @@ const ZohoCrmDataScreen: React.FC = () => {
} finally {
setRefreshing(false);
}
- };
+ }, [dispatch]);
+
+ // Load more data for infinite scrolling
+ const loadMoreData = useCallback(async (dataType: string) => {
+ try {
+ let pagination;
+ let fetchAction;
+ let resetAction;
+
+ switch (dataType) {
+ case 'leads':
+ pagination = leadsPagination;
+ fetchAction = fetchLeads;
+ resetAction = resetLeadsPagination;
+ break;
+ case 'tasks':
+ pagination = tasksPagination;
+ fetchAction = fetchTasks;
+ resetAction = resetTasksPagination;
+ break;
+ case 'contacts':
+ pagination = contactsPagination;
+ fetchAction = fetchContacts;
+ resetAction = resetContactsPagination;
+ break;
+ case 'deals':
+ pagination = dealsPagination;
+ fetchAction = fetchDeals;
+ resetAction = resetDealsPagination;
+ break;
+ default:
+ return; // Only leads, tasks, contacts, and deals support infinite scrolling for now
+ }
+
+ // Check if there are more records and not currently loading
+ if (!pagination.moreRecords || loading[dataType as keyof typeof loading]) {
+ return;
+ }
+
+ // Fetch next page
+ await (dispatch(fetchAction({
+ page: pagination.page + 1,
+ limit: 20,
+ append: true
+ }) as any)).unwrap();
+
+ } catch (err) {
+ showError(`Failed to load more ${dataType}`);
+ }
+ }, [dispatch, leadsPagination, tasksPagination, contactsPagination, dealsPagination, loading]);
useEffect(() => {
fetchCrmData();
}, []);
- const handleRefresh = () => {
+ const handleRefresh = useCallback(() => {
fetchCrmData(true);
- };
+ }, [fetchCrmData]);
- const handleRetry = () => {
+ const handleRetry = useCallback(() => {
fetchCrmData();
- };
+ }, [fetchCrmData]);
- const handleCardPress = (item: any, type: string) => {
+ const handleCardPress = useCallback((item: any, type: string) => {
showInfo(`Viewing ${type}: ${item.name || item.subject || `${item.firstName} ${item.lastName}`}`);
- };
+ }, []);
+
+ // Render loading footer for infinite scroll
+ const renderFooter = useCallback((dataType: string) => {
+ const isLoadingMore = loading[dataType as keyof typeof loading];
+ if (!isLoadingMore) return null;
+
+ return (
+
+
+
+ Loading more...
+
+
+ );
+ }, [loading, colors]);
// Get current loading state and error
const isLoading = loading.leads || loading.tasks || loading.contacts || loading.deals ||
@@ -123,7 +214,7 @@ const ZohoCrmDataScreen: React.FC = () => {
}
- const renderTabContent = () => {
+ const renderTabContent = useCallback(() => {
const commonFlatListProps = {
numColumns: 1,
showsVerticalScrollIndicator: false,
@@ -144,7 +235,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Lead')}
/>
)}
- keyExtractor={(item) => item.id}
+ keyExtractor={(item) => `lead-${item.id}`}
+ onEndReached={() => loadMoreData('leads')}
+ onEndReachedThreshold={0.1}
+ ListFooterComponent={() => renderFooter('leads')}
{...commonFlatListProps}
/>
);
@@ -158,7 +252,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Task')}
/>
)}
- keyExtractor={(item) => item.id}
+ keyExtractor={(item) => `task-${item.id}`}
+ onEndReached={() => loadMoreData('tasks')}
+ onEndReachedThreshold={0.1}
+ ListFooterComponent={() => renderFooter('tasks')}
{...commonFlatListProps}
/>
);
@@ -172,7 +269,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Contact')}
/>
)}
- keyExtractor={(item) => item.id}
+ keyExtractor={(item) => `contact-${item.id}`}
+ onEndReached={() => loadMoreData('contacts')}
+ onEndReachedThreshold={0.1}
+ ListFooterComponent={() => renderFooter('contacts')}
{...commonFlatListProps}
/>
);
@@ -186,7 +286,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Deal')}
/>
)}
- keyExtractor={(item) => item.id}
+ keyExtractor={(item) => `deal-${item.id}`}
+ onEndReached={() => loadMoreData('deals')}
+ onEndReachedThreshold={0.1}
+ ListFooterComponent={() => renderFooter('deals')}
{...commonFlatListProps}
/>
);
@@ -200,7 +303,7 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Sales Order')}
/>
)}
- keyExtractor={(item) => item.id}
+ keyExtractor={(item) => `sales-order-${item.id}`}
{...commonFlatListProps}
/>
);
@@ -214,7 +317,7 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Purchase Order')}
/>
)}
- keyExtractor={(item) => item.id}
+ keyExtractor={(item) => `purchase-order-${item.id}`}
{...commonFlatListProps}
/>
);
@@ -228,14 +331,14 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Invoice')}
/>
)}
- keyExtractor={(item) => item.id}
+ keyExtractor={(item) => `invoice-${item.id}`}
{...commonFlatListProps}
/>
);
default:
return null;
}
- };
+ }, [selectedTab, crmData, handleCardPress, loadMoreData, renderFooter, refreshing, handleRefresh]);
return (
@@ -366,6 +469,16 @@ const styles = StyleSheet.create({
paddingHorizontal: 16,
paddingBottom: 20,
},
+ footerLoader: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 16,
+ },
+ footerText: {
+ marginLeft: 8,
+ fontSize: 14,
+ },
});
export default ZohoCrmDataScreen;
diff --git a/src/modules/crm/zoho/services/crmAPI.ts b/src/modules/crm/zoho/services/crmAPI.ts
index 3efc410..8c196bc 100644
--- a/src/modules/crm/zoho/services/crmAPI.ts
+++ b/src/modules/crm/zoho/services/crmAPI.ts
@@ -25,8 +25,8 @@ export const crmAPI = {
provider: 'zoho',
service: 'crm',
resource,
- page: 1,
- limit: 20,
+ page: params?.page || 1,
+ limit: params?.limit || 20,
...params
};
@@ -47,13 +47,34 @@ export const crmAPI = {
crmAPI.getCrmData('deals', params),
// New API endpoints for sales orders, purchase orders, and invoices
- getSalesOrders: (params?: CrmSearchParams) =>
- http.get>>(`/api/v1/integrations/zoho/crm/sales-orders?provider=zoho`, params),
+ getSalesOrders: (params?: CrmSearchParams) => {
+ const queryParams = {
+ provider: 'zoho',
+ page: params?.page || 1,
+ limit: params?.limit || 20,
+ ...params
+ };
+ return http.get>>(`/api/v1/integrations/zoho/crm/sales-orders`, queryParams);
+ },
- getPurchaseOrders: (params?: CrmSearchParams) =>
- http.get>>(`/api/v1/integrations/zoho/crm/purchase-orders?provider=zoho`, params),
+ getPurchaseOrders: (params?: CrmSearchParams) => {
+ const queryParams = {
+ provider: 'zoho',
+ page: params?.page || 1,
+ limit: params?.limit || 20,
+ ...params
+ };
+ return http.get>>(`/api/v1/integrations/zoho/crm/purchase-orders`, queryParams);
+ },
- getInvoices: (params?: CrmSearchParams) =>
- http.get>>(`/api/v1/integrations/zoho/crm/invoices?provider=zoho`, params),
+ getInvoices: (params?: CrmSearchParams) => {
+ const queryParams = {
+ provider: 'zoho',
+ page: params?.page || 1,
+ limit: params?.limit || 20,
+ ...params
+ };
+ return http.get>>(`/api/v1/integrations/zoho/crm/invoices`, queryParams);
+ },
};
diff --git a/src/modules/crm/zoho/store/crmSlice.ts b/src/modules/crm/zoho/store/crmSlice.ts
index 8c6620b..11d854f 100644
--- a/src/modules/crm/zoho/store/crmSlice.ts
+++ b/src/modules/crm/zoho/store/crmSlice.ts
@@ -127,36 +127,48 @@ const initialState: CrmState = {
},
};
-// Async thunks
+// Async thunks for infinite scrolling
export const fetchLeads = createAsyncThunk(
'crm/fetchLeads',
- async (params?: CrmSearchParams) => {
+ async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getLeads(params);
- return response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } };
+ return {
+ data: response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } },
+ append: params?.append || false
+ };
}
);
export const fetchTasks = createAsyncThunk(
'crm/fetchTasks',
- async (params?: CrmSearchParams) => {
+ async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getTasks(params);
- return response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } };
+ return {
+ data: response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } },
+ append: params?.append || false
+ };
}
);
export const fetchContacts = createAsyncThunk(
'crm/fetchContacts',
- async (params?: CrmSearchParams) => {
+ async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getContacts(params);
- return response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } };
+ return {
+ data: response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } },
+ append: params?.append || false
+ };
}
);
export const fetchDeals = createAsyncThunk(
'crm/fetchDeals',
- async (params?: CrmSearchParams) => {
+ async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getDeals(params);
- return response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } };
+ return {
+ data: response.data?.data || { data: [], info: { count: 0, moreRecords: false, page: 1 } },
+ append: params?.append || false
+ };
}
);
@@ -266,6 +278,23 @@ const crmSlice = createSlice({
setInvoicesPage: (state, action: PayloadAction) => {
state.pagination.invoices.page = action.payload;
},
+ // Reset pagination for specific data types
+ resetLeadsPagination: (state) => {
+ state.pagination.leads = { page: 1, count: 0, moreRecords: false };
+ state.leads = [];
+ },
+ resetTasksPagination: (state) => {
+ state.pagination.tasks = { page: 1, count: 0, moreRecords: false };
+ state.tasks = [];
+ },
+ resetContactsPagination: (state) => {
+ state.pagination.contacts = { page: 1, count: 0, moreRecords: false };
+ state.contacts = [];
+ },
+ resetDealsPagination: (state) => {
+ state.pagination.deals = { page: 1, count: 0, moreRecords: false };
+ state.deals = [];
+ },
},
extraReducers: (builder) => {
// Fetch leads
@@ -276,8 +305,17 @@ const crmSlice = createSlice({
})
.addCase(fetchLeads.fulfilled, (state, action) => {
state.loading.leads = false;
- state.leads = action.payload.data || [];
- state.pagination.leads = action.payload.info || { page: 1, count: 0, moreRecords: false };
+ const { data, append } = action.payload;
+
+ if (append) {
+ // Append new data to existing data for infinite scrolling
+ state.leads = [...state.leads, ...(data.data || [])];
+ } else {
+ // Replace data for initial load or refresh
+ state.leads = data.data || [];
+ }
+
+ state.pagination.leads = data.info || { page: 1, count: 0, moreRecords: false };
state.lastUpdated.leads = new Date().toISOString();
})
.addCase(fetchLeads.rejected, (state, action) => {
@@ -292,8 +330,17 @@ const crmSlice = createSlice({
})
.addCase(fetchTasks.fulfilled, (state, action) => {
state.loading.tasks = false;
- state.tasks = action.payload.data || [];
- state.pagination.tasks = action.payload.info || { page: 1, count: 0, moreRecords: false };
+ const { data, append } = action.payload;
+
+ if (append) {
+ // Append new data to existing data for infinite scrolling
+ state.tasks = [...state.tasks, ...(data.data || [])];
+ } else {
+ // Replace data for initial load or refresh
+ state.tasks = data.data || [];
+ }
+
+ state.pagination.tasks = data.info || { page: 1, count: 0, moreRecords: false };
state.lastUpdated.tasks = new Date().toISOString();
})
.addCase(fetchTasks.rejected, (state, action) => {
@@ -308,8 +355,17 @@ const crmSlice = createSlice({
})
.addCase(fetchContacts.fulfilled, (state, action) => {
state.loading.contacts = false;
- state.contacts = action.payload.data || [];
- state.pagination.contacts = action.payload.info || { page: 1, count: 0, moreRecords: false };
+ const { data, append } = action.payload;
+
+ if (append) {
+ // Append new data to existing data for infinite scrolling
+ state.contacts = [...state.contacts, ...(data.data || [])];
+ } else {
+ // Replace data for initial load or refresh
+ state.contacts = data.data || [];
+ }
+
+ state.pagination.contacts = data.info || { page: 1, count: 0, moreRecords: false };
state.lastUpdated.contacts = new Date().toISOString();
})
.addCase(fetchContacts.rejected, (state, action) => {
@@ -324,8 +380,17 @@ const crmSlice = createSlice({
})
.addCase(fetchDeals.fulfilled, (state, action) => {
state.loading.deals = false;
- state.deals = action.payload.data || [];
- state.pagination.deals = action.payload.info || { page: 1, count: 0, moreRecords: false };
+ const { data, append } = action.payload;
+
+ if (append) {
+ // Append new data to existing data for infinite scrolling
+ state.deals = [...state.deals, ...(data.data || [])];
+ } else {
+ // Replace data for initial load or refresh
+ state.deals = data.data || [];
+ }
+
+ state.pagination.deals = data.info || { page: 1, count: 0, moreRecords: false };
state.lastUpdated.deals = new Date().toISOString();
})
.addCase(fetchDeals.rejected, (state, action) => {
@@ -465,6 +530,10 @@ export const {
setSalesOrdersPage,
setPurchaseOrdersPage,
setInvoicesPage,
+ resetLeadsPagination,
+ resetTasksPagination,
+ resetContactsPagination,
+ resetDealsPagination,
} = crmSlice.actions;
export default crmSlice.reducer;
diff --git a/src/modules/crm/zoho/store/selectors.ts b/src/modules/crm/zoho/store/selectors.ts
index 988f75a..ba9615b 100644
--- a/src/modules/crm/zoho/store/selectors.ts
+++ b/src/modules/crm/zoho/store/selectors.ts
@@ -17,6 +17,15 @@ export const selectCrmLoading = (state: RootState) => state.crm.loading;
export const selectCrmErrors = (state: RootState) => state.crm.errors;
export const selectCrmPagination = (state: RootState) => state.crm.pagination;
+// Pagination selectors for infinite scrolling
+export const selectLeadsPagination = (state: RootState) => state.crm.pagination.leads;
+export const selectTasksPagination = (state: RootState) => state.crm.pagination.tasks;
+export const selectContactsPagination = (state: RootState) => state.crm.pagination.contacts;
+export const selectDealsPagination = (state: RootState) => state.crm.pagination.deals;
+export const selectSalesOrdersPagination = (state: RootState) => state.crm.pagination.salesOrders;
+export const selectPurchaseOrdersPagination = (state: RootState) => state.crm.pagination.purchaseOrders;
+export const selectInvoicesPagination = (state: RootState) => state.crm.pagination.invoices;
+
// Loading selectors
export const selectLeadsLoading = (state: RootState) => state.crm.loading.leads;
export const selectTasksLoading = (state: RootState) => state.crm.loading.tasks;
diff --git a/src/services/http.ts b/src/services/http.ts
index b3df1cb..5b711cf 100644
--- a/src/services/http.ts
+++ b/src/services/http.ts
@@ -8,7 +8,7 @@ import { clearSelectedService } from '@/modules/integrations/store/integrationsS
let pendingRequest: any = null;
const http = create({
- baseURL: 'http://192.168.1.17:4000',
+ baseURL: 'http://10.175.59.235:4000',
// baseURL: 'http://160.187.167.216',
timeout: 10000,
});