added pagination for the leads tasks and deals accounts

This commit is contained in:
yashwin-foxy 2025-09-24 20:26:49 +05:30
parent e91bbbbd1d
commit e53b229c03
5 changed files with 258 additions and 46 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { import {
View, View,
Text, Text,
@ -8,6 +8,7 @@ import {
RefreshControl, RefreshControl,
FlatList, FlatList,
Alert, Alert,
ActivityIndicator,
} from 'react-native'; } from 'react-native';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import type { AppDispatch } from '@/store/store'; import type { AppDispatch } from '@/store/store';
@ -26,9 +27,26 @@ import {
selectPurchaseOrders, selectPurchaseOrders,
selectInvoices, selectInvoices,
selectCrmLoading, selectCrmLoading,
selectCrmErrors selectCrmErrors,
selectLeadsPagination,
selectTasksPagination,
selectContactsPagination,
selectDealsPagination,
selectSalesOrdersPagination,
selectPurchaseOrdersPagination,
selectInvoicesPagination
} from '../store/selectors'; } 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'; import type { RootState } from '@/store/store';
const ZohoCrmDataScreen: React.FC = () => { const ZohoCrmDataScreen: React.FC = () => {
@ -48,6 +66,15 @@ const ZohoCrmDataScreen: React.FC = () => {
const loading = useSelector(selectCrmLoading); const loading = useSelector(selectCrmLoading);
const errors = useSelector(selectCrmErrors); 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 // Create CRM data object from Redux state
const crmData: CrmData = useMemo(() => ({ const crmData: CrmData = useMemo(() => ({
leads: leads || [], leads: leads || [],
@ -60,7 +87,7 @@ const ZohoCrmDataScreen: React.FC = () => {
}), [leads, tasks, contacts, deals, salesOrders, purchaseOrders, invoices]); }), [leads, tasks, contacts, deals, salesOrders, purchaseOrders, invoices]);
// Fetch CRM data using Redux // Fetch CRM data using Redux
const fetchCrmData = async (showRefresh = false) => { const fetchCrmData = useCallback(async (showRefresh = false) => {
try { try {
if (showRefresh) { if (showRefresh) {
setRefreshing(true); setRefreshing(true);
@ -78,23 +105,87 @@ const ZohoCrmDataScreen: React.FC = () => {
} finally { } finally {
setRefreshing(false); 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(() => { useEffect(() => {
fetchCrmData(); fetchCrmData();
}, []); }, []);
const handleRefresh = () => { const handleRefresh = useCallback(() => {
fetchCrmData(true); fetchCrmData(true);
}; }, [fetchCrmData]);
const handleRetry = () => { const handleRetry = useCallback(() => {
fetchCrmData(); 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}`}`); 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 (
<View style={styles.footerLoader}>
<ActivityIndicator size="small" color={colors.primary} />
<Text style={[styles.footerText, { color: colors.textLight }]}>
Loading more...
</Text>
</View>
);
}, [loading, colors]);
// Get current loading state and error // Get current loading state and error
const isLoading = loading.leads || loading.tasks || loading.contacts || loading.deals || 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 = { const commonFlatListProps = {
numColumns: 1, numColumns: 1,
showsVerticalScrollIndicator: false, showsVerticalScrollIndicator: false,
@ -144,7 +235,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Lead')} onPress={() => handleCardPress(item, 'Lead')}
/> />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => `lead-${item.id}`}
onEndReached={() => loadMoreData('leads')}
onEndReachedThreshold={0.1}
ListFooterComponent={() => renderFooter('leads')}
{...commonFlatListProps} {...commonFlatListProps}
/> />
); );
@ -158,7 +252,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Task')} onPress={() => handleCardPress(item, 'Task')}
/> />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => `task-${item.id}`}
onEndReached={() => loadMoreData('tasks')}
onEndReachedThreshold={0.1}
ListFooterComponent={() => renderFooter('tasks')}
{...commonFlatListProps} {...commonFlatListProps}
/> />
); );
@ -172,7 +269,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Contact')} onPress={() => handleCardPress(item, 'Contact')}
/> />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => `contact-${item.id}`}
onEndReached={() => loadMoreData('contacts')}
onEndReachedThreshold={0.1}
ListFooterComponent={() => renderFooter('contacts')}
{...commonFlatListProps} {...commonFlatListProps}
/> />
); );
@ -186,7 +286,10 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Deal')} onPress={() => handleCardPress(item, 'Deal')}
/> />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => `deal-${item.id}`}
onEndReached={() => loadMoreData('deals')}
onEndReachedThreshold={0.1}
ListFooterComponent={() => renderFooter('deals')}
{...commonFlatListProps} {...commonFlatListProps}
/> />
); );
@ -200,7 +303,7 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Sales Order')} onPress={() => handleCardPress(item, 'Sales Order')}
/> />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => `sales-order-${item.id}`}
{...commonFlatListProps} {...commonFlatListProps}
/> />
); );
@ -214,7 +317,7 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Purchase Order')} onPress={() => handleCardPress(item, 'Purchase Order')}
/> />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => `purchase-order-${item.id}`}
{...commonFlatListProps} {...commonFlatListProps}
/> />
); );
@ -228,14 +331,14 @@ const ZohoCrmDataScreen: React.FC = () => {
onPress={() => handleCardPress(item, 'Invoice')} onPress={() => handleCardPress(item, 'Invoice')}
/> />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => `invoice-${item.id}`}
{...commonFlatListProps} {...commonFlatListProps}
/> />
); );
default: default:
return null; return null;
} }
}; }, [selectedTab, crmData, handleCardPress, loadMoreData, renderFooter, refreshing, handleRefresh]);
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={[styles.container, { backgroundColor: colors.background }]}>
@ -366,6 +469,16 @@ const styles = StyleSheet.create({
paddingHorizontal: 16, paddingHorizontal: 16,
paddingBottom: 20, paddingBottom: 20,
}, },
footerLoader: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 16,
},
footerText: {
marginLeft: 8,
fontSize: 14,
},
}); });
export default ZohoCrmDataScreen; export default ZohoCrmDataScreen;

View File

@ -25,8 +25,8 @@ export const crmAPI = {
provider: 'zoho', provider: 'zoho',
service: 'crm', service: 'crm',
resource, resource,
page: 1, page: params?.page || 1,
limit: 20, limit: params?.limit || 20,
...params ...params
}; };
@ -47,13 +47,34 @@ export const crmAPI = {
crmAPI.getCrmData<CrmDeal>('deals', params), crmAPI.getCrmData<CrmDeal>('deals', params),
// New API endpoints for sales orders, purchase orders, and invoices // New API endpoints for sales orders, purchase orders, and invoices
getSalesOrders: (params?: CrmSearchParams) => getSalesOrders: (params?: CrmSearchParams) => {
http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/sales-orders?provider=zoho`, params), const queryParams = {
provider: 'zoho',
page: params?.page || 1,
limit: params?.limit || 20,
...params
};
return http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/sales-orders`, queryParams);
},
getPurchaseOrders: (params?: CrmSearchParams) => getPurchaseOrders: (params?: CrmSearchParams) => {
http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/purchase-orders?provider=zoho`, params), const queryParams = {
provider: 'zoho',
page: params?.page || 1,
limit: params?.limit || 20,
...params
};
return http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/purchase-orders`, queryParams);
},
getInvoices: (params?: CrmSearchParams) => getInvoices: (params?: CrmSearchParams) => {
http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/invoices?provider=zoho`, params), const queryParams = {
provider: 'zoho',
page: params?.page || 1,
limit: params?.limit || 20,
...params
};
return http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/invoices`, queryParams);
},
}; };

View File

@ -127,36 +127,48 @@ const initialState: CrmState = {
}, },
}; };
// Async thunks // Async thunks for infinite scrolling
export const fetchLeads = createAsyncThunk( export const fetchLeads = createAsyncThunk(
'crm/fetchLeads', 'crm/fetchLeads',
async (params?: CrmSearchParams) => { async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getLeads(params); 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( export const fetchTasks = createAsyncThunk(
'crm/fetchTasks', 'crm/fetchTasks',
async (params?: CrmSearchParams) => { async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getTasks(params); 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( export const fetchContacts = createAsyncThunk(
'crm/fetchContacts', 'crm/fetchContacts',
async (params?: CrmSearchParams) => { async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getContacts(params); 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( export const fetchDeals = createAsyncThunk(
'crm/fetchDeals', 'crm/fetchDeals',
async (params?: CrmSearchParams) => { async (params?: CrmSearchParams & { append?: boolean }) => {
const response = await crmAPI.getDeals(params); 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<number>) => { setInvoicesPage: (state, action: PayloadAction<number>) => {
state.pagination.invoices.page = action.payload; 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) => { extraReducers: (builder) => {
// Fetch leads // Fetch leads
@ -276,8 +305,17 @@ const crmSlice = createSlice({
}) })
.addCase(fetchLeads.fulfilled, (state, action) => { .addCase(fetchLeads.fulfilled, (state, action) => {
state.loading.leads = false; state.loading.leads = false;
state.leads = action.payload.data || []; const { data, append } = action.payload;
state.pagination.leads = action.payload.info || { page: 1, count: 0, moreRecords: false };
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(); state.lastUpdated.leads = new Date().toISOString();
}) })
.addCase(fetchLeads.rejected, (state, action) => { .addCase(fetchLeads.rejected, (state, action) => {
@ -292,8 +330,17 @@ const crmSlice = createSlice({
}) })
.addCase(fetchTasks.fulfilled, (state, action) => { .addCase(fetchTasks.fulfilled, (state, action) => {
state.loading.tasks = false; state.loading.tasks = false;
state.tasks = action.payload.data || []; const { data, append } = action.payload;
state.pagination.tasks = action.payload.info || { page: 1, count: 0, moreRecords: false };
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(); state.lastUpdated.tasks = new Date().toISOString();
}) })
.addCase(fetchTasks.rejected, (state, action) => { .addCase(fetchTasks.rejected, (state, action) => {
@ -308,8 +355,17 @@ const crmSlice = createSlice({
}) })
.addCase(fetchContacts.fulfilled, (state, action) => { .addCase(fetchContacts.fulfilled, (state, action) => {
state.loading.contacts = false; state.loading.contacts = false;
state.contacts = action.payload.data || []; const { data, append } = action.payload;
state.pagination.contacts = action.payload.info || { page: 1, count: 0, moreRecords: false };
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(); state.lastUpdated.contacts = new Date().toISOString();
}) })
.addCase(fetchContacts.rejected, (state, action) => { .addCase(fetchContacts.rejected, (state, action) => {
@ -324,8 +380,17 @@ const crmSlice = createSlice({
}) })
.addCase(fetchDeals.fulfilled, (state, action) => { .addCase(fetchDeals.fulfilled, (state, action) => {
state.loading.deals = false; state.loading.deals = false;
state.deals = action.payload.data || []; const { data, append } = action.payload;
state.pagination.deals = action.payload.info || { page: 1, count: 0, moreRecords: false };
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(); state.lastUpdated.deals = new Date().toISOString();
}) })
.addCase(fetchDeals.rejected, (state, action) => { .addCase(fetchDeals.rejected, (state, action) => {
@ -465,6 +530,10 @@ export const {
setSalesOrdersPage, setSalesOrdersPage,
setPurchaseOrdersPage, setPurchaseOrdersPage,
setInvoicesPage, setInvoicesPage,
resetLeadsPagination,
resetTasksPagination,
resetContactsPagination,
resetDealsPagination,
} = crmSlice.actions; } = crmSlice.actions;
export default crmSlice.reducer; export default crmSlice.reducer;

View File

@ -17,6 +17,15 @@ export const selectCrmLoading = (state: RootState) => state.crm.loading;
export const selectCrmErrors = (state: RootState) => state.crm.errors; export const selectCrmErrors = (state: RootState) => state.crm.errors;
export const selectCrmPagination = (state: RootState) => state.crm.pagination; 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 // Loading selectors
export const selectLeadsLoading = (state: RootState) => state.crm.loading.leads; export const selectLeadsLoading = (state: RootState) => state.crm.loading.leads;
export const selectTasksLoading = (state: RootState) => state.crm.loading.tasks; export const selectTasksLoading = (state: RootState) => state.crm.loading.tasks;

View File

@ -8,7 +8,7 @@ import { clearSelectedService } from '@/modules/integrations/store/integrationsS
let pendingRequest: any = null; let pendingRequest: any = null;
const http = create({ const http = create({
baseURL: 'http://192.168.1.17:4000', baseURL: 'http://10.175.59.235:4000',
// baseURL: 'http://160.187.167.216', // baseURL: 'http://160.187.167.216',
timeout: 10000, timeout: 10000,
}); });