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 {
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 (
<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
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 (
<View style={[styles.container, { backgroundColor: colors.background }]}>
@ -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;

View File

@ -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<CrmDeal>('deals', params),
// New API endpoints for sales orders, purchase orders, and invoices
getSalesOrders: (params?: CrmSearchParams) =>
http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/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<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/sales-orders`, queryParams);
},
getPurchaseOrders: (params?: CrmSearchParams) =>
http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/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<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/purchase-orders`, queryParams);
},
getInvoices: (params?: CrmSearchParams) =>
http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/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<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(
'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<number>) => {
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;

View File

@ -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;

View File

@ -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,
});