diff --git a/src/modules/crm/index.ts b/src/modules/crm/index.ts
new file mode 100644
index 0000000..dab8c07
--- /dev/null
+++ b/src/modules/crm/index.ts
@@ -0,0 +1,5 @@
+// CRM Module Exports
+export { default as CrmNavigator } from './navigation/CrmNavigator';
+
+// Zoho CRM exports
+export * from './zoho';
diff --git a/src/modules/crm/navigation/CrmNavigator.tsx b/src/modules/crm/navigation/CrmNavigator.tsx
index 04c5959..64b888a 100644
--- a/src/modules/crm/navigation/CrmNavigator.tsx
+++ b/src/modules/crm/navigation/CrmNavigator.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
-import CrmDashboardScreen from '@/modules/crm/screens/CrmDashboardScreen';
-import ZohoCrmDataScreen from '@/modules/crm/screens/ZohoCrmDataScreen';
+import CrmDashboardScreen from '@/modules/crm/zoho/screens/CrmDashboardScreen';
+import ZohoCrmDataScreen from '@/modules/crm/zoho/screens/ZohoCrmDataScreen';
const Stack = createStackNavigator();
diff --git a/src/modules/crm/services/index.ts b/src/modules/crm/services/index.ts
deleted file mode 100644
index ca9507a..0000000
--- a/src/modules/crm/services/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// CRM Services Exports
-export { crmAPI } from './crmAPI';
-
-// Re-export types for convenience
-export type {
- CrmData,
- CrmLead,
- CrmTask,
- CrmContact,
- CrmDeal,
- CreateLeadForm,
- CreateTaskForm,
- CreateContactForm,
- CreateDealForm,
- CrmSearchParams,
- CrmApiResponse,
- CrmPaginatedResponse,
- CrmStats,
- CrmFilters,
-} from '../types/CrmTypes';
-
diff --git a/src/modules/crm/components/CrmDataCards.tsx b/src/modules/crm/zoho/components/CrmDataCards.tsx
similarity index 100%
rename from src/modules/crm/components/CrmDataCards.tsx
rename to src/modules/crm/zoho/components/CrmDataCards.tsx
diff --git a/src/modules/crm/zoho/index.ts b/src/modules/crm/zoho/index.ts
new file mode 100644
index 0000000..90cac13
--- /dev/null
+++ b/src/modules/crm/zoho/index.ts
@@ -0,0 +1,7 @@
+// Zoho CRM Module Exports
+export { default as CrmDashboardScreen } from './screens/CrmDashboardScreen';
+export { default as ZohoCrmDataScreen } from './screens/ZohoCrmDataScreen';
+export { default as crmSlice } from './store/crmSlice';
+export { selectDashboardData, selectIsAnyLoading, selectHasAnyError, selectCrmStats } from './store/selectors';
+export * from './types/CrmTypes';
+export { crmAPI } from './services/crmAPI';
diff --git a/src/modules/crm/screens/CrmDashboardScreen.tsx b/src/modules/crm/zoho/screens/CrmDashboardScreen.tsx
similarity index 100%
rename from src/modules/crm/screens/CrmDashboardScreen.tsx
rename to src/modules/crm/zoho/screens/CrmDashboardScreen.tsx
diff --git a/src/modules/crm/screens/ZohoCrmDataScreen.tsx b/src/modules/crm/zoho/screens/ZohoCrmDataScreen.tsx
similarity index 76%
rename from src/modules/crm/screens/ZohoCrmDataScreen.tsx
rename to src/modules/crm/zoho/screens/ZohoCrmDataScreen.tsx
index 390712b..3432a98 100644
--- a/src/modules/crm/screens/ZohoCrmDataScreen.tsx
+++ b/src/modules/crm/zoho/screens/ZohoCrmDataScreen.tsx
@@ -124,6 +124,15 @@ const ZohoCrmDataScreen: React.FC = () => {
const renderTabContent = () => {
+ const commonFlatListProps = {
+ numColumns: 1,
+ showsVerticalScrollIndicator: false,
+ contentContainerStyle: styles.listContainer,
+ refreshControl: (
+
+ ),
+ };
+
switch (selectedTab) {
case 'leads':
return (
@@ -136,9 +145,7 @@ const ZohoCrmDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'tasks':
@@ -152,9 +159,7 @@ const ZohoCrmDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'contacts':
@@ -168,9 +173,7 @@ const ZohoCrmDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'deals':
@@ -184,9 +187,7 @@ const ZohoCrmDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'salesOrders':
@@ -200,9 +201,7 @@ const ZohoCrmDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'purchaseOrders':
@@ -216,9 +215,7 @@ const ZohoCrmDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'invoices':
@@ -232,9 +229,7 @@ const ZohoCrmDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
default:
@@ -243,14 +238,8 @@ const ZohoCrmDataScreen: React.FC = () => {
};
return (
-
-
- }
- >
- {/* Header */}
+
+ {/* Fixed Header */}
Zoho CRM Data
@@ -260,64 +249,65 @@ const ZohoCrmDataScreen: React.FC = () => {
- {/* Tabs */}
-
-
- {tabs.map((tab) => (
- setSelectedTab(tab.key)}
- activeOpacity={0.8}
- >
-
-
+
+
+ {tabs.map((tab) => (
+
- {tab.label}
-
- setSelectedTab(tab.key)}
+ activeOpacity={0.8}
>
+
- {tab.count}
+ {tab.label}
-
-
- ))}
-
-
+
+
+ {tab.count}
+
+
+
+ ))}
+
+
+
- {/* Content */}
+ {/* Scrollable Content */}
{renderTabContent()}
-
-
+
);
};
@@ -336,7 +326,10 @@ const styles = StyleSheet.create({
fontSize: 24,
},
tabsContainer: {
- marginBottom: 16,
+ backgroundColor: '#FFFFFF',
+ paddingVertical: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#E5E7EB',
},
tabs: {
flexDirection: 'row',
@@ -368,9 +361,9 @@ const styles = StyleSheet.create({
},
content: {
flex: 1,
- paddingHorizontal: 16,
},
listContainer: {
+ paddingHorizontal: 16,
paddingBottom: 20,
},
});
diff --git a/src/modules/crm/services/crmAPI.ts b/src/modules/crm/zoho/services/crmAPI.ts
similarity index 100%
rename from src/modules/crm/services/crmAPI.ts
rename to src/modules/crm/zoho/services/crmAPI.ts
diff --git a/src/modules/crm/store/crmSlice.ts b/src/modules/crm/zoho/store/crmSlice.ts
similarity index 100%
rename from src/modules/crm/store/crmSlice.ts
rename to src/modules/crm/zoho/store/crmSlice.ts
diff --git a/src/modules/crm/store/selectors.ts b/src/modules/crm/zoho/store/selectors.ts
similarity index 100%
rename from src/modules/crm/store/selectors.ts
rename to src/modules/crm/zoho/store/selectors.ts
diff --git a/src/modules/crm/types/CrmTypes.ts b/src/modules/crm/zoho/types/CrmTypes.ts
similarity index 100%
rename from src/modules/crm/types/CrmTypes.ts
rename to src/modules/crm/zoho/types/CrmTypes.ts
diff --git a/src/modules/finance/index.ts b/src/modules/finance/index.ts
new file mode 100644
index 0000000..0d4ec15
--- /dev/null
+++ b/src/modules/finance/index.ts
@@ -0,0 +1,6 @@
+// Finance Module Exports
+export { default as FinanceDashboardScreen } from './screens/FinanceDashboardScreen';
+export { default as FinanceNavigator } from './navigation/FinanceNavigator';
+
+// Zoho Books exports
+export * from './zoho';
diff --git a/src/modules/finance/navigation/FinanceNavigator.tsx b/src/modules/finance/navigation/FinanceNavigator.tsx
index 375e699..273ae70 100644
--- a/src/modules/finance/navigation/FinanceNavigator.tsx
+++ b/src/modules/finance/navigation/FinanceNavigator.tsx
@@ -1,12 +1,14 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
-import FinanceDashboardScreen from '@/modules/finance/screens/FinanceDashboardScreen';
+import ZohoBooksDashboardScreen from '@/modules/finance/zoho/screens/ZohoBooksDashboardScreen';
+import ZohoBooksDataScreen from '@/modules/finance/zoho/screens/ZohoBooksDataScreen';
const Stack = createStackNavigator();
const FinanceNavigator = () => (
-
+
+
);
diff --git a/src/modules/finance/screens/FinanceDashboardScreen.tsx b/src/modules/finance/screens/FinanceDashboardScreen.tsx
deleted file mode 100644
index b8db1c5..0000000
--- a/src/modules/finance/screens/FinanceDashboardScreen.tsx
+++ /dev/null
@@ -1,230 +0,0 @@
-import React, { useMemo } from 'react';
-import { View, Text, StyleSheet } from 'react-native';
-import { Container } from '@/shared/components/ui';
-import { useTheme } from '@/shared/styles/useTheme';
-
-const FinanceDashboardScreen: React.FC = () => {
- const { colors, fonts } = useTheme();
-
- const mock = useMemo(() => {
- // Zoho Books oriented metrics
- const cash = 246000;
- const invoices = { total: 485000, outstanding: 162000, overdue: 48000, paidThisMonth: 92000 };
- const monthlySales = [84, 92, 88, 104, 112, 118]; // in thousands
- const arAging = [45, 30, 18, 7]; // 0-30, 31-60, 61-90, 90+
- const invoiceStatus = [
- { label: 'Draft', value: 30, color: '#94A3B8' },
- { label: 'Sent', value: 80, color: '#3AA0FF' },
- { label: 'Viewed', value: 50, color: '#6366F1' },
- { label: 'Paid', value: 210, color: '#10B981' },
- { label: 'Overdue', value: 18, color: '#EF4444' },
- ];
- const taxes = { collected: 38000, paid: 12500 };
- const topCustomers = [
- { client: 'Acme Corp', amount: 82000 },
- { client: 'Initech', amount: 54000 },
- { client: 'Umbrella', amount: 48000 },
- ];
- const bankAccounts = [
- { name: 'HDFC 1234', balance: 152000 },
- { name: 'ICICI 9981', balance: 78000 },
- ];
- const paymentModes = [
- { label: 'Online', value: 120, color: '#3AA0FF' },
- { label: 'Bank Transfer', value: 90, color: '#10B981' },
- { label: 'Cash', value: 40, color: '#F59E0B' },
- { label: 'Cheque', value: 12, color: '#6366F1' },
- ];
- const estimates = { sent: 24, accepted: 16, declined: 3 };
- return { cash, invoices, monthlySales, arAging, invoiceStatus, taxes, topCustomers, bankAccounts, paymentModes, estimates };
- }, []);
- return (
-
-
- Accounts & Finance
-
- {/* KPIs */}
-
-
-
-
-
-
-
- {/* Sales Trend */}
-
- Sales Trend
-
-
-
- {/* Taxes & AR Aging */}
-
-
- Taxes
- {(() => {
- const total = Math.max(1, mock.taxes.collected + mock.taxes.paid);
- const colPct = Math.round((mock.taxes.collected / total) * 100);
- const paidPct = Math.round((mock.taxes.paid / total) * 100);
- return (
- <>
- Collected: ${mock.taxes.collected.toLocaleString()}
-
- Paid: ${mock.taxes.paid.toLocaleString()}
-
- >
- );
- })()}
-
-
- A/R Aging
-
-
- 0-30
- 31-60
- 61-90
- 90+
-
-
-
-
- {/* Invoice Status Distribution */}
-
- Invoice Status
- a + b.value, 0)} />
-
- {mock.invoiceStatus.map(s => (
-
-
- {s.label}
-
- ))}
-
-
-
- {/* Lists */}
-
-
- Top Customers
- {mock.topCustomers.map(r => (
-
- {r.client}
- ${r.amount.toLocaleString()}
-
- ))}
-
-
- Bank Accounts
- {mock.bankAccounts.map(p => (
-
- {p.name}
- ${p.balance.toLocaleString()}
-
- ))}
-
-
-
- {/* Estimates & Payment Modes */}
-
- Estimates
-
-
- Sent: {mock.estimates.sent}
-
-
- Accepted: {mock.estimates.accepted}
-
-
- Declined: {mock.estimates.declined}
-
-
-
-
-
- Payment Modes
- a + b.value, 0)} />
-
- {mock.paymentModes.map(s => (
-
-
- {s.label}
-
- ))}
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- wrap: { flex: 1, padding: 16 },
- title: { fontSize: 18, marginBottom: 8 },
- kpiGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' },
- kpiCard: { width: '48%', borderRadius: 12, borderWidth: 1, borderColor: '#E2E8F0', backgroundColor: '#FFFFFF', padding: 12, marginBottom: 12 },
- kpiLabel: { fontSize: 12, opacity: 0.8 },
- kpiValueRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: 8 },
- card: { borderRadius: 12, borderWidth: 1, padding: 12, marginTop: 12 },
- cardTitle: { fontSize: 16, marginBottom: 8 },
- row: { flexDirection: 'row', marginTop: 12 },
- col: { flex: 1, marginRight: 8 },
- bars: { flexDirection: 'row', alignItems: 'flex-end' },
- bar: { flex: 1, marginRight: 6, borderTopLeftRadius: 4, borderTopRightRadius: 4 },
- progressWrap: { marginTop: 8 },
- progressTrack: { height: 8, borderRadius: 6, backgroundColor: '#E5E7EB', overflow: 'hidden' },
- progressFill: { height: '100%', borderRadius: 6 },
- legendRow: { flexDirection: 'row', flexWrap: 'wrap', marginTop: 8 },
- legendItem: { flexDirection: 'row', alignItems: 'center', marginRight: 12, marginTop: 6 },
- legendDot: { width: 8, height: 8, borderRadius: 4, marginRight: 6 },
- rowJustify: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 8 },
- listRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 10, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: '#E5E7EB' },
- listPrimary: { fontSize: 14, flex: 1, paddingRight: 8 },
- listSecondary: { fontSize: 12 },
-});
-
-export default FinanceDashboardScreen;
-
-// UI bits
-const Kpi: React.FC<{ label: string; value: string; color: string; fonts: any; accent: string }> = ({ label, value, color, fonts, accent }) => {
- return (
-
- {label}
-
- {value}
-
-
-
- );
-};
-
-const Bars: React.FC<{ data: number[]; max: number; color: string }> = ({ data, max, color }) => {
- return (
-
- {data.map((v, i) => (
-
- ))}
-
- );
-};
-
-const Progress: React.FC<{ value: number; color: string; fonts: any }> = ({ value, color, fonts }) => {
- return (
-
-
-
-
- {value}%
-
- );
-};
-
-const Stacked: React.FC<{ segments: { label: string; value: number; color: string }[]; total: number }> = ({ segments, total }) => {
- return (
-
- {segments.map(s => (
-
- ))}
-
- );
-};
-
-
diff --git a/src/modules/finance/zoho/components/widgets/BarChartWidget.tsx b/src/modules/finance/zoho/components/widgets/BarChartWidget.tsx
new file mode 100644
index 0000000..1ab924c
--- /dev/null
+++ b/src/modules/finance/zoho/components/widgets/BarChartWidget.tsx
@@ -0,0 +1,135 @@
+import React from 'react';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+import { useTheme } from '@/shared/styles/useTheme';
+import { StackedBarChart } from '@/shared/components/charts';
+
+interface BarChartWidgetProps {
+ data: Array<{
+ label: string;
+ value: number;
+ color: string;
+ }>;
+ title: string;
+ subtitle?: string;
+ onPress?: () => void;
+ height?: number;
+}
+
+const BarChartWidget: React.FC = ({
+ data,
+ title,
+ subtitle,
+ onPress,
+ height = 120,
+}) => {
+ const { colors, fonts, spacing, shadows } = useTheme();
+
+ const maxValue = Math.max(...data.map(item => item.value));
+ const total = data.reduce((sum, item) => sum + item.value, 0);
+
+ const WidgetContent = () => (
+
+
+
+
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ Total: {total.toLocaleString()}
+
+
+ Max: {maxValue.toLocaleString()}
+
+
+
+
+ );
+
+ if (onPress) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+};
+
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 12,
+ borderWidth: 1,
+ padding: 16,
+ marginBottom: 12,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 16,
+ },
+ titleContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ titleTextContainer: {
+ marginLeft: 8,
+ },
+ title: {
+ fontSize: 16,
+ },
+ subtitle: {
+ fontSize: 12,
+ marginTop: 2,
+ },
+ content: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ footer: {
+ marginTop: 12,
+ paddingTop: 12,
+ borderTopWidth: 1,
+ borderTopColor: '#E5E7EB',
+ },
+ summaryContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ summaryLabel: {
+ fontSize: 12,
+ },
+});
+
+export default BarChartWidget;
diff --git a/src/modules/finance/zoho/components/widgets/KPIWidget.tsx b/src/modules/finance/zoho/components/widgets/KPIWidget.tsx
new file mode 100644
index 0000000..4280819
--- /dev/null
+++ b/src/modules/finance/zoho/components/widgets/KPIWidget.tsx
@@ -0,0 +1,162 @@
+import React from 'react';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+import { useTheme } from '@/shared/styles/useTheme';
+
+interface KPIWidgetProps {
+ title: string;
+ value: string | number;
+ subtitle?: string;
+ icon: string;
+ color: string;
+ backgroundColor?: string;
+ onPress?: () => void;
+ trend?: {
+ value: number;
+ isPositive: boolean;
+ };
+}
+
+const KPIWidget: React.FC = ({
+ title,
+ value,
+ subtitle,
+ icon,
+ color,
+ backgroundColor,
+ onPress,
+ trend,
+}) => {
+ const { colors, fonts, spacing, shadows } = useTheme();
+
+ const formatValue = (val: string | number): string => {
+ if (typeof val === 'number') {
+ return val.toLocaleString();
+ }
+ return val;
+ };
+
+ const WidgetContent = () => (
+
+
+
+
+
+ {trend && (
+
+
+
+ {Math.abs(trend.value)}%
+
+
+ )}
+
+
+
+
+ {title}
+
+
+
+ {formatValue(value)}
+
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+
+ );
+
+ if (onPress) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+};
+
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 12,
+ borderWidth: 1,
+ padding: 16,
+ marginBottom: 12,
+ minHeight: 120,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 12,
+ },
+ iconContainer: {
+ width: 36,
+ height: 36,
+ borderRadius: 8,
+ backgroundColor: 'rgba(0, 0, 0, 0.05)',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ trendContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 6,
+ paddingVertical: 2,
+ borderRadius: 4,
+ },
+ trendText: {
+ fontSize: 10,
+ marginLeft: 2,
+ },
+ content: {
+ flex: 1,
+ },
+ title: {
+ fontSize: 12,
+ marginBottom: 4,
+ },
+ value: {
+ fontSize: 24,
+ marginBottom: 4,
+ },
+ subtitle: {
+ fontSize: 11,
+ },
+});
+
+export default KPIWidget;
diff --git a/src/modules/finance/zoho/components/widgets/ListWidget.tsx b/src/modules/finance/zoho/components/widgets/ListWidget.tsx
new file mode 100644
index 0000000..07c4dab
--- /dev/null
+++ b/src/modules/finance/zoho/components/widgets/ListWidget.tsx
@@ -0,0 +1,227 @@
+import React from 'react';
+import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+import { useTheme } from '@/shared/styles/useTheme';
+
+interface ListItem {
+ id: string;
+ title: string;
+ subtitle?: string;
+ value: string | number;
+ icon?: string;
+ color?: string;
+}
+
+interface ListWidgetProps {
+ title: string;
+ data: ListItem[];
+ onItemPress?: (item: ListItem) => void;
+ onViewAllPress?: () => void;
+ maxItems?: number;
+ showValue?: boolean;
+ emptyMessage?: string;
+}
+
+const ListWidget: React.FC = ({
+ title,
+ data,
+ onItemPress,
+ onViewAllPress,
+ maxItems = 5,
+ showValue = true,
+ emptyMessage = 'No data available',
+}) => {
+ const { colors, fonts, spacing, shadows } = useTheme();
+
+ const displayData = data.slice(0, maxItems);
+
+ const formatValue = (value: string | number): string => {
+ if (typeof value === 'number') {
+ return value.toLocaleString();
+ }
+ return value;
+ };
+
+ return (
+
+
+
+ {title}
+
+ {onViewAllPress && data.length > maxItems && (
+
+
+ View All
+
+
+
+ )}
+
+
+
+ {displayData.length === 0 ? (
+
+
+
+ {emptyMessage}
+
+
+ ) : (
+
+ {displayData.map((item, index) => (
+ onItemPress?.(item)}
+ disabled={!onItemPress}
+ >
+
+ {item.icon && (
+
+
+
+ )}
+
+
+
+ {item.title}
+
+ {item.subtitle && (
+
+ {item.subtitle}
+
+ )}
+
+
+
+ {showValue && (
+
+
+ {formatValue(item.value)}
+
+
+ )}
+
+ ))}
+
+ )}
+
+
+ {data.length > maxItems && !onViewAllPress && (
+
+
+ +{data.length - maxItems} more items
+
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 12,
+ borderWidth: 1,
+ padding: 16,
+ marginBottom: 12,
+ maxHeight: 300,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 16,
+ },
+ title: {
+ fontSize: 16,
+ },
+ viewAllButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ viewAllText: {
+ fontSize: 12,
+ marginRight: 4,
+ },
+ content: {
+ flex: 1,
+ },
+ emptyContainer: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingVertical: 32,
+ },
+ emptyText: {
+ fontSize: 14,
+ marginTop: 8,
+ textAlign: 'center',
+ },
+ listItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ },
+ lastListItem: {
+ borderBottomWidth: 0,
+ },
+ itemContent: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ flex: 1,
+ },
+ iconContainer: {
+ width: 32,
+ height: 32,
+ borderRadius: 6,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginRight: 12,
+ },
+ textContainer: {
+ flex: 1,
+ },
+ itemTitle: {
+ fontSize: 14,
+ marginBottom: 2,
+ },
+ itemSubtitle: {
+ fontSize: 12,
+ },
+ valueContainer: {
+ marginLeft: 12,
+ },
+ itemValue: {
+ fontSize: 14,
+ },
+ footer: {
+ paddingTop: 12,
+ borderTopWidth: 1,
+ borderTopColor: '#E5E7EB',
+ alignItems: 'center',
+ },
+ footerText: {
+ fontSize: 12,
+ },
+});
+
+export default ListWidget;
diff --git a/src/modules/finance/zoho/components/widgets/RevenueChart.tsx b/src/modules/finance/zoho/components/widgets/RevenueChart.tsx
new file mode 100644
index 0000000..cf44670
--- /dev/null
+++ b/src/modules/finance/zoho/components/widgets/RevenueChart.tsx
@@ -0,0 +1,166 @@
+import React from 'react';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+import { useTheme } from '@/shared/styles/useTheme';
+import { PieChart, DonutChart } from '@/shared/components/charts';
+
+interface RevenueChartProps {
+ data: Array<{
+ label: string;
+ value: number;
+ color: string;
+ }>;
+ title: string;
+ total?: number;
+ chartType?: 'pie' | 'donut';
+ onPress?: () => void;
+}
+
+const RevenueChart: React.FC = ({
+ data,
+ title,
+ total,
+ chartType = 'donut',
+ onPress,
+}) => {
+ const { colors, fonts, spacing, shadows } = useTheme();
+
+ const calculatedTotal = total || data.reduce((sum, item) => sum + item.value, 0);
+
+ const WidgetContent = () => (
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+ {chartType === 'pie' ? (
+
+ ) : (
+
+ )}
+
+
+
+ {data.map((item, index) => (
+
+
+
+ {item.label}
+
+
+ {item.value}
+
+
+ ))}
+
+
+
+ {calculatedTotal > 0 && (
+
+
+ Total: {calculatedTotal.toLocaleString()}
+
+
+ )}
+
+ );
+
+ if (onPress) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+};
+
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 12,
+ borderWidth: 1,
+ padding: 16,
+ marginBottom: 12,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 16,
+ },
+ titleContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ title: {
+ fontSize: 16,
+ marginLeft: 8,
+ },
+ content: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ chartContainer: {
+ flex: 1,
+ alignItems: 'center',
+ },
+ legendContainer: {
+ flex: 1,
+ paddingLeft: 16,
+ },
+ legendItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 8,
+ },
+ legendDot: {
+ width: 8,
+ height: 8,
+ borderRadius: 4,
+ marginRight: 8,
+ },
+ legendLabel: {
+ fontSize: 12,
+ flex: 1,
+ },
+ legendValue: {
+ fontSize: 12,
+ },
+ footer: {
+ marginTop: 12,
+ paddingTop: 12,
+ borderTopWidth: 1,
+ borderTopColor: '#E5E7EB',
+ },
+ totalLabel: {
+ fontSize: 12,
+ textAlign: 'center',
+ },
+});
+
+export default RevenueChart;
diff --git a/src/modules/finance/zoho/components/widgets/index.ts b/src/modules/finance/zoho/components/widgets/index.ts
new file mode 100644
index 0000000..cf4970f
--- /dev/null
+++ b/src/modules/finance/zoho/components/widgets/index.ts
@@ -0,0 +1,4 @@
+export { default as KPIWidget } from './KPIWidget';
+export { default as RevenueChart } from './RevenueChart';
+export { default as BarChartWidget } from './BarChartWidget';
+export { default as ListWidget } from './ListWidget';
diff --git a/src/modules/finance/zoho/index.ts b/src/modules/finance/zoho/index.ts
new file mode 100644
index 0000000..0e3215b
--- /dev/null
+++ b/src/modules/finance/zoho/index.ts
@@ -0,0 +1,25 @@
+// Zoho Books Module Exports
+export { default as ZohoBooksDashboardScreen } from './screens/ZohoBooksDashboardScreen';
+export { default as ZohoBooksDataScreen } from './screens/ZohoBooksDataScreen';
+export { default as zohoBooksSlice } from './store/zohoBooksSlice';
+export {
+ selectZohoBooksLoading,
+ selectZohoBooksError,
+ selectZohoBooksKPIData,
+ selectCustomers,
+ selectVendors,
+ selectActiveInvoices,
+ selectPaidInvoices,
+ selectDraftInvoices,
+ selectTopCustomersByOutstanding,
+ selectTopVendorsByOutstanding,
+ selectInvoiceStatusDistribution,
+} from './store/selectors';
+export * from './types/ZohoBooksTypes';
+export { zohoBooksAPI } from './services/zohoBooksAPI';
+export {
+ KPIWidget,
+ RevenueChart,
+ BarChartWidget,
+ ListWidget,
+} from './components/widgets';
diff --git a/src/modules/finance/zoho/screens/ZohoBooksDashboardScreen.tsx b/src/modules/finance/zoho/screens/ZohoBooksDashboardScreen.tsx
new file mode 100644
index 0000000..3420b6d
--- /dev/null
+++ b/src/modules/finance/zoho/screens/ZohoBooksDashboardScreen.tsx
@@ -0,0 +1,425 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import {
+ View,
+ Text,
+ ScrollView,
+ RefreshControl,
+ StyleSheet,
+ TouchableOpacity,
+} from 'react-native';
+import { useSelector, useDispatch } from 'react-redux';
+import { useNavigation } from '@react-navigation/native';
+import type { AppDispatch } from '@/store/store';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+import { Container, LoadingSpinner, ErrorState } from '@/shared/components/ui';
+import { useTheme } from '@/shared/styles/useTheme';
+import { fetchZohoBooksData } from '../store/zohoBooksSlice';
+import {
+ selectZohoBooksLoading,
+ selectZohoBooksError,
+ selectZohoBooksKPIData,
+ selectCustomers,
+ selectVendors,
+ selectActiveInvoices,
+ selectPaidInvoices,
+ selectDraftInvoices,
+ selectTopCustomersByOutstanding,
+ selectTopVendorsByOutstanding,
+ selectInvoiceStatusDistribution,
+} from '../store/selectors';
+import {
+ KPIWidget,
+ RevenueChart,
+ BarChartWidget,
+ ListWidget,
+} from '../components/widgets';
+
+const ZohoBooksDashboardScreen: React.FC = () => {
+ const { colors, fonts, spacing, shadows } = useTheme();
+ const navigation = useNavigation();
+ const [refreshing, setRefreshing] = useState(false);
+
+ // Redux
+ const dispatch = useDispatch();
+ const loading = useSelector(selectZohoBooksLoading);
+ const error = useSelector(selectZohoBooksError);
+ const kpiData = useSelector(selectZohoBooksKPIData);
+ const customers = useSelector(selectCustomers);
+ const vendors = useSelector(selectVendors);
+ const activeInvoices = useSelector(selectActiveInvoices);
+ const paidInvoices = useSelector(selectPaidInvoices);
+ const draftInvoices = useSelector(selectDraftInvoices);
+ const topCustomers = useSelector(selectTopCustomersByOutstanding);
+ const topVendors = useSelector(selectTopVendorsByOutstanding);
+ const invoiceStatusDistribution = useSelector(selectInvoiceStatusDistribution);
+
+ // Effects
+ useEffect(() => {
+ dispatch(fetchZohoBooksData());
+ }, [dispatch]);
+
+ // Handlers
+ const handleRefresh = async () => {
+ setRefreshing(true);
+ try {
+ await dispatch(fetchZohoBooksData()).unwrap();
+ } catch (error) {
+ console.error('Error refreshing data:', error);
+ } finally {
+ setRefreshing(false);
+ }
+ };
+
+ const handleRetry = () => {
+ dispatch(fetchZohoBooksData());
+ };
+
+ // Computed data for charts
+ const invoiceStatusData = useMemo(() => {
+ return invoiceStatusDistribution.map(item => ({
+ label: item.label,
+ value: item.value,
+ color: item.color,
+ }));
+ }, [invoiceStatusDistribution]);
+
+ const customerVsVendorData = useMemo(() => {
+ return [
+ { label: 'Customers', value: kpiData.totalCustomers, color: colors.chartPrimary },
+ { label: 'Vendors', value: kpiData.totalVendors, color: colors.chartSecondary },
+ ];
+ }, [kpiData.totalCustomers, kpiData.totalVendors, colors]);
+
+ const revenueVsExpensesData = useMemo(() => {
+ return [
+ { label: 'Revenue', value: Math.round(kpiData.totalRevenue / 1000), color: colors.success },
+ { label: 'Expenses', value: Math.round(kpiData.totalExpensesAmount / 1000), color: colors.error },
+ ];
+ }, [kpiData.totalRevenue, kpiData.totalExpensesAmount, colors]);
+
+ // Top customers list data
+ const topCustomersListData = useMemo(() => {
+ return topCustomers.map(customer => ({
+ id: customer.contact_id,
+ title: customer.contact_name,
+ subtitle: customer.company_name || 'Individual',
+ value: `₹${customer.outstanding_receivable_amount.toLocaleString()}`,
+ icon: 'person',
+ color: colors.primary,
+ }));
+ }, [topCustomers, colors]);
+
+ // Top vendors list data
+ const topVendorsListData = useMemo(() => {
+ return topVendors.map(vendor => ({
+ id: vendor.contact_id,
+ title: vendor.contact_name,
+ subtitle: vendor.company_name || 'Individual',
+ value: `₹${vendor.outstanding_payable_amount.toLocaleString()}`,
+ icon: 'business',
+ color: colors.secondary,
+ }));
+ }, [topVendors, colors]);
+
+ // Loading state
+ if (loading && !kpiData.totalCustomers && !kpiData.totalVendors) {
+ return ;
+ }
+
+ // Error state
+ if (error) {
+ return ;
+ }
+
+ return (
+
+
+ }
+ showsVerticalScrollIndicator={false}
+ >
+ {/* Header */}
+
+
+
+
+ Zoho Books Dashboard
+
+
+ navigation.navigate('ZohoBooksData' as never)}
+ >
+
+
+ View Data
+
+
+
+
+ {/* KPI Cards Row 1 */}
+
+
+
+
+
+
+
+
+
+ {/* KPI Cards Row 2 */}
+
+
+
+
+
+
+
+
+
+ {/* KPI Cards Row 3 */}
+
+
+
+
+
+
+
+
+
+ {/* Charts Row */}
+
+
+
+
+
+
+
+
+
+ {/* Bar Chart */}
+
+
+ {/* Lists Row */}
+
+
+
+
+
+
+
+
+
+ {/* Summary Cards */}
+
+
+
+
+
+ Documents
+
+
+
+
+
+ Invoices
+
+
+ {kpiData.totalInvoices}
+
+
+
+
+ Sales Orders
+
+
+ {kpiData.totalSalesOrders}
+
+
+
+
+ Purchase Orders
+
+
+ {kpiData.totalPurchaseOrders}
+
+
+
+
+ Bills
+
+
+ {kpiData.totalBills}
+
+
+
+
+
+
+ {/* Bottom spacing */}
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ paddingBottom: 8,
+ },
+ headerContent: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ title: {
+ fontSize: 24,
+ marginLeft: 12,
+ },
+ dataButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ borderRadius: 8,
+ backgroundColor: 'rgba(44, 95, 74, 0.1)',
+ borderWidth: 1,
+ borderColor: 'rgba(44, 95, 74, 0.3)',
+ },
+ dataButtonText: {
+ fontSize: 14,
+ marginLeft: 6,
+ },
+ kpiRow: {
+ flexDirection: 'row',
+ paddingHorizontal: 16,
+ marginBottom: 8,
+ },
+ kpiColumn: {
+ flex: 1,
+ marginHorizontal: 4,
+ },
+ chartsRow: {
+ flexDirection: 'column',
+ paddingHorizontal: 16,
+ marginBottom: 8,
+ },
+ chartColumn: {
+ marginVertical: 4,
+ },
+ listsRow: {
+ flexDirection: 'row',
+ paddingHorizontal: 16,
+ marginBottom: 8,
+ },
+ listColumn: {
+ flex: 1,
+ marginHorizontal: 4,
+ },
+ summaryRow: {
+ paddingHorizontal: 16,
+ marginBottom: 8,
+ },
+ summaryCard: {
+ borderRadius: 12,
+ borderWidth: 1,
+ padding: 16,
+ ...StyleSheet.flatten({
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 3,
+ }),
+ },
+ summaryHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 16,
+ },
+ summaryTitle: {
+ fontSize: 16,
+ marginLeft: 8,
+ },
+ summaryContent: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'space-between',
+ },
+ summaryItem: {
+ width: '48%',
+ marginBottom: 12,
+ },
+ summaryLabel: {
+ fontSize: 12,
+ marginBottom: 4,
+ },
+ summaryValue: {
+ fontSize: 18,
+ },
+ bottomSpacing: {
+ height: 20,
+ },
+});
+
+export default ZohoBooksDashboardScreen;
diff --git a/src/modules/finance/zoho/screens/ZohoBooksDataScreen.tsx b/src/modules/finance/zoho/screens/ZohoBooksDataScreen.tsx
new file mode 100644
index 0000000..5bab8b2
--- /dev/null
+++ b/src/modules/finance/zoho/screens/ZohoBooksDataScreen.tsx
@@ -0,0 +1,736 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ ScrollView,
+ TouchableOpacity,
+ RefreshControl,
+ FlatList,
+} from 'react-native';
+import { useSelector, useDispatch } from 'react-redux';
+import type { AppDispatch } from '@/store/store';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+import { Container, LoadingSpinner, ErrorState } from '@/shared/components/ui';
+import { useTheme } from '@/shared/styles/useTheme';
+import { fetchZohoBooksData } from '../store/zohoBooksSlice';
+import {
+ selectZohoBooksLoading,
+ selectZohoBooksError,
+ selectZohoBooksContacts,
+ selectZohoBooksInvoices,
+ selectZohoBooksExpenses,
+ selectZohoBooksSalesOrders,
+ selectZohoBooksPurchaseOrders,
+ selectZohoBooksBills,
+ selectCustomers,
+ selectVendors,
+} from '../store/selectors';
+import type { RootState } from '@/store/store';
+
+const ZohoBooksDataScreen: React.FC = () => {
+ const { colors, fonts, spacing, shadows } = useTheme();
+ const dispatch = useDispatch();
+ const [selectedTab, setSelectedTab] = useState<'contacts' | 'customers' | 'vendors' | 'invoices' | 'expenses' | 'salesOrders' | 'purchaseOrders' | 'bills'>('contacts');
+ const [refreshing, setRefreshing] = useState(false);
+
+ // Redux selectors
+ const contacts = useSelector(selectZohoBooksContacts);
+ const invoices = useSelector(selectZohoBooksInvoices);
+ const expenses = useSelector(selectZohoBooksExpenses);
+ const salesOrders = useSelector(selectZohoBooksSalesOrders);
+ const purchaseOrders = useSelector(selectZohoBooksPurchaseOrders);
+ const bills = useSelector(selectZohoBooksBills);
+ const customers = useSelector(selectCustomers);
+ const vendors = useSelector(selectVendors);
+ const loading = useSelector(selectZohoBooksLoading);
+ const error = useSelector(selectZohoBooksError);
+
+ // Fetch data using Redux
+ const fetchData = async (showRefresh = false) => {
+ try {
+ if (showRefresh) {
+ setRefreshing(true);
+ }
+
+ await dispatch(fetchZohoBooksData()).unwrap();
+ } catch (err) {
+ console.error('Error fetching Zoho Books data:', err);
+ } finally {
+ setRefreshing(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ const handleRefresh = () => {
+ fetchData(true);
+ };
+
+ const handleRetry = () => {
+ fetchData();
+ };
+
+ const handleItemPress = (item: any, type: string) => {
+ console.log(`Viewing ${type}:`, item);
+ };
+
+ // Loading state
+ if (loading && !contacts.length && !invoices.length) {
+ return ;
+ }
+
+ // Error state
+ if (error) {
+ return ;
+ }
+
+ // Tab configuration
+ const tabs = [
+ { key: 'contacts', label: 'All Contacts', icon: 'contacts', count: contacts.length },
+ { key: 'customers', label: 'Customers', icon: 'person', count: customers.length },
+ { key: 'vendors', label: 'Vendors', icon: 'business', count: vendors.length },
+ { key: 'invoices', label: 'Invoices', icon: 'receipt', count: invoices.length },
+ { key: 'expenses', label: 'Expenses', icon: 'money-off', count: expenses.length },
+ { key: 'salesOrders', label: 'Sales Orders', icon: 'shopping-cart', count: salesOrders.length },
+ { key: 'purchaseOrders', label: 'Purchase Orders', icon: 'add-shopping-cart', count: purchaseOrders.length },
+ { key: 'bills', label: 'Bills', icon: 'description', count: bills.length },
+ ] as const;
+
+ const renderContactCard = (item: any) => (
+
+
+
+
+
+ {item.contact_name}
+
+
+
+
+ {item.status}
+
+
+
+
+
+
+ Type:
+
+ {item.contact_type_formatted}
+
+
+
+ {item.email && (
+
+ Email:
+ {item.email}
+
+ )}
+
+ {item.phone && (
+
+ Phone:
+ {item.phone}
+
+ )}
+
+
+ Currency:
+ {item.currency_code}
+
+
+
+
+ Outstanding Receivable:
+
+ ₹{item.outstanding_receivable_amount.toLocaleString()}
+
+
+
+ Outstanding Payable:
+
+ ₹{item.outstanding_payable_amount.toLocaleString()}
+
+
+
+
+
+ );
+
+ const renderInvoiceCard = (item: any) => (
+
+
+
+
+
+ {item.invoice_number}
+
+
+
+
+ {item.status}
+
+
+
+
+
+
+ Customer:
+
+ {item.customer_name}
+
+
+
+
+ Date:
+ {item.date}
+
+
+
+ Due Date:
+ {item.due_date}
+
+
+
+
+ Total:
+
+ {item.currency_symbol}{item.total.toLocaleString()}
+
+
+
+ Balance:
+ 0 ? '#EF4444' : '#10B981', fontFamily: fonts.bold }]}>
+ {item.currency_symbol}{item.balance.toLocaleString()}
+
+
+
+
+
+ );
+
+ const renderExpenseCard = (item: any) => (
+
+
+
+
+
+ {item.reference_number || 'Expense'}
+
+
+
+
+ {item.status}
+
+
+
+
+
+
+ Account:
+
+ {item.account_name}
+
+
+
+
+ Date:
+ {item.date}
+
+
+ {item.vendor_name && (
+
+ Vendor:
+ {item.vendor_name}
+
+ )}
+
+
+
+ Total:
+
+ {item.currency_code} {item.total.toLocaleString()}
+
+
+
+ Billable:
+
+ {item.is_billable ? 'Yes' : 'No'}
+
+
+
+
+
+ );
+
+ const renderSalesOrderCard = (item: any) => (
+
+
+
+
+
+ {item.salesorder_number}
+
+
+
+
+ {item.status}
+
+
+
+
+
+
+ Customer:
+
+ {item.customer_name}
+
+
+
+
+ Date:
+ {item.date}
+
+
+
+ Shipment Date:
+ {item.shipment_date}
+
+
+
+
+ Total:
+
+ {item.currency_code} {item.total.toLocaleString()}
+
+
+
+ Invoiced:
+
+ {item.currency_code} {item.total_invoiced_amount.toLocaleString()}
+
+
+
+
+
+ );
+
+ const renderPurchaseOrderCard = (item: any) => (
+
+
+
+
+
+ {item.purchaseorder_number}
+
+
+
+
+ {item.status}
+
+
+
+
+
+
+ Vendor:
+
+ {item.vendor_name}
+
+
+
+
+ Date:
+ {item.date}
+
+
+
+ Delivery Date:
+ {item.delivery_date}
+
+
+
+
+ Total:
+
+ {item.currency_code} {item.total.toLocaleString()}
+
+
+
+ Quantity:
+
+ {item.quantity_yet_to_receive}
+
+
+
+
+
+ );
+
+ const renderBillCard = (item: any) => (
+
+
+
+
+
+ {item.bill_number}
+
+
+
+
+ {item.status}
+
+
+
+
+
+
+ Vendor:
+
+ {item.vendor_name}
+
+
+
+
+ Date:
+ {item.date}
+
+
+
+ Due Date:
+ {item.due_date}
+
+
+
+
+ Total:
+
+ {item.currency_code} {item.total.toLocaleString()}
+
+
+
+ Balance:
+ 0 ? '#EF4444' : '#10B981', fontFamily: fonts.bold }]}>
+ {item.currency_code} {item.balance.toLocaleString()}
+
+
+
+
+
+ );
+
+ const renderTabContent = () => {
+ const commonFlatListProps = {
+ showsVerticalScrollIndicator: false,
+ contentContainerStyle: styles.listContainer,
+ refreshControl: (
+
+ ),
+ };
+
+ switch (selectedTab) {
+ case 'contacts':
+ return (
+ renderContactCard(item)}
+ keyExtractor={(item) => item.contact_id}
+ {...commonFlatListProps}
+ />
+ );
+ case 'customers':
+ return (
+ renderContactCard(item)}
+ keyExtractor={(item) => item.contact_id}
+ {...commonFlatListProps}
+ />
+ );
+ case 'vendors':
+ return (
+ renderContactCard(item)}
+ keyExtractor={(item) => item.contact_id}
+ {...commonFlatListProps}
+ />
+ );
+ case 'invoices':
+ return (
+ renderInvoiceCard(item)}
+ keyExtractor={(item) => item.invoice_id}
+ {...commonFlatListProps}
+ />
+ );
+ case 'expenses':
+ return (
+ renderExpenseCard(item)}
+ keyExtractor={(item) => item.expense_id}
+ {...commonFlatListProps}
+ />
+ );
+ case 'salesOrders':
+ return (
+ renderSalesOrderCard(item)}
+ keyExtractor={(item) => item.salesorder_id}
+ {...commonFlatListProps}
+ />
+ );
+ case 'purchaseOrders':
+ return (
+ renderPurchaseOrderCard(item)}
+ keyExtractor={(item) => item.purchaseorder_id}
+ {...commonFlatListProps}
+ />
+ );
+ case 'bills':
+ return (
+ renderBillCard(item)}
+ keyExtractor={(item) => item.bill_id}
+ {...commonFlatListProps}
+ />
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ {/* Fixed Header */}
+
+
+ Zoho Books Data
+
+
+
+
+
+
+ {/* Fixed Tabs */}
+
+
+
+ {tabs.map((tab) => (
+ setSelectedTab(tab.key)}
+ activeOpacity={0.8}
+ >
+
+
+ {tab.label}
+
+
+
+ {tab.count}
+
+
+
+ ))}
+
+
+
+
+ {/* Scrollable Content */}
+
+ {renderTabContent()}
+
+
+ );
+};
+
+// Helper function to get status colors
+const getStatusColor = (status: string): string => {
+ const statusColors: Record = {
+ active: '#E9FAF2',
+ inactive: '#FEF2F2',
+ draft: '#F3F4F6',
+ sent: '#E0F2FE',
+ paid: '#E9FAF2',
+ overdue: '#FEF2F2',
+ open: '#E0F2FE',
+ confirmed: '#E9FAF2',
+ received: '#E9FAF2',
+ cancelled: '#FEF2F2',
+ billable: '#E9FAF2',
+ nonbillable: '#FEF2F2',
+ };
+ return statusColors[status.toLowerCase()] || '#F3F4F6';
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ },
+ title: {
+ fontSize: 24,
+ },
+ tabsContainer: {
+ backgroundColor: '#FFFFFF',
+ paddingVertical: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#E5E7EB',
+ },
+ tabs: {
+ flexDirection: 'row',
+ paddingHorizontal: 16,
+ },
+ tab: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 16,
+ paddingVertical: 10,
+ marginRight: 8,
+ borderRadius: 20,
+ backgroundColor: '#F1F5F9',
+ },
+ tabText: {
+ marginLeft: 6,
+ fontSize: 14,
+ },
+ countBadge: {
+ marginLeft: 6,
+ paddingHorizontal: 8,
+ paddingVertical: 2,
+ borderRadius: 10,
+ minWidth: 20,
+ alignItems: 'center',
+ },
+ countText: {
+ fontSize: 12,
+ },
+ content: {
+ flex: 1,
+ },
+ listContainer: {
+ paddingHorizontal: 16,
+ paddingBottom: 20,
+ },
+ card: {
+ borderRadius: 12,
+ borderWidth: 1,
+ padding: 16,
+ marginBottom: 12,
+ ...StyleSheet.flatten({
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 3,
+ }),
+ },
+ cardHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 12,
+ },
+ cardTitleContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ flex: 1,
+ },
+ cardTitle: {
+ fontSize: 16,
+ marginLeft: 8,
+ },
+ statusBadge: {
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 12,
+ },
+ statusText: {
+ fontSize: 12,
+ textTransform: 'capitalize',
+ },
+ cardContent: {
+ gap: 8,
+ },
+ infoRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ infoLabel: {
+ fontSize: 14,
+ flex: 1,
+ },
+ infoValue: {
+ fontSize: 14,
+ textAlign: 'right',
+ flex: 1,
+ },
+ amountRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginTop: 8,
+ paddingTop: 8,
+ borderTopWidth: StyleSheet.hairlineWidth,
+ borderTopColor: '#E5E7EB',
+ },
+ amountItem: {
+ flex: 1,
+ alignItems: 'center',
+ },
+ amountLabel: {
+ fontSize: 12,
+ marginBottom: 4,
+ },
+ amountValue: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ },
+});
+
+export default ZohoBooksDataScreen;
diff --git a/src/modules/finance/zoho/services/zohoBooksAPI.ts b/src/modules/finance/zoho/services/zohoBooksAPI.ts
new file mode 100644
index 0000000..9821584
--- /dev/null
+++ b/src/modules/finance/zoho/services/zohoBooksAPI.ts
@@ -0,0 +1,96 @@
+import httpClient from '@/services/http';
+import type {
+ ZohoBooksApiResponse,
+ ZohoBooksContact,
+ ZohoBooksInvoice,
+ ZohoBooksExpense,
+ ZohoBooksSalesOrder,
+ ZohoBooksPurchaseOrder,
+ ZohoBooksBill,
+} from '../types/ZohoBooksTypes';
+
+const BASE_URL = '/api/v1/integrations/zoho/books';
+
+export const zohoBooksAPI = {
+ // Get all contacts (customers and vendors)
+ getContacts: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/contacts?provider=zoho`);
+ console.log('contacts response in zoho books api',response)
+ return response.data;
+ },
+
+ // Get vendors only
+ getVendors: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/vendors?provider=zoho`);
+ return response.data;
+ },
+
+ // Get customers only
+ getCustomers: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/customers?provider=zoho`);
+ return response.data;
+ },
+
+ // Get invoices
+ getInvoices: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/invoices?provider=zoho`);
+ return response.data;
+ },
+
+ // Get expenses
+ getExpenses: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/expenses?provider=zoho`);
+ return response.data;
+ },
+
+ // Get sales orders
+ getSalesOrders: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/sales-orders?provider=zoho`);
+ return response.data;
+ },
+
+ // Get purchase orders
+ getPurchaseOrders: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/purchase-orders?provider=zoho`);
+ return response.data;
+ },
+
+ // Get bills
+ getBills: async (): Promise> => {
+ const response = await httpClient.get(`${BASE_URL}/bills?provider=zoho`);
+ return response.data;
+ },
+
+ // Get all data for dashboard
+ getAllData: async () => {
+ try {
+ const [
+ contactsResponse,
+ invoicesResponse,
+ expensesResponse,
+ salesOrdersResponse,
+ purchaseOrdersResponse,
+ billsResponse,
+ ] = await Promise.all([
+ zohoBooksAPI.getContacts(),
+ zohoBooksAPI.getInvoices(),
+ zohoBooksAPI.getExpenses(),
+ zohoBooksAPI.getSalesOrders(),
+ zohoBooksAPI.getPurchaseOrders(),
+ zohoBooksAPI.getBills(),
+ ]);
+
+ return {
+ contacts: contactsResponse.data.data,
+ invoices: invoicesResponse.data.data,
+ expenses: expensesResponse.data.data,
+ salesOrders: salesOrdersResponse.data.data,
+ purchaseOrders: purchaseOrdersResponse.data.data,
+ bills: billsResponse.data.data,
+ };
+ } catch (error) {
+ console.error('Error fetching Zoho Books data:', error);
+ throw error;
+ }
+ },
+};
diff --git a/src/modules/finance/zoho/store/selectors.ts b/src/modules/finance/zoho/store/selectors.ts
new file mode 100644
index 0000000..0d23b41
--- /dev/null
+++ b/src/modules/finance/zoho/store/selectors.ts
@@ -0,0 +1,133 @@
+import { createSelector } from '@reduxjs/toolkit';
+import type { RootState } from '@/store/store';
+import type { ZohoBooksContact, ZohoBooksInvoice } from '../types/ZohoBooksTypes';
+
+// Base selectors
+export const selectZohoBooksState = (state: RootState) => state.zohoBooks;
+
+export const selectZohoBooksLoading = createSelector(
+ [selectZohoBooksState],
+ (state) => state.loading
+);
+
+export const selectZohoBooksError = createSelector(
+ [selectZohoBooksState],
+ (state) => state.error
+);
+
+export const selectZohoBooksLastUpdated = createSelector(
+ [selectZohoBooksState],
+ (state) => state.lastUpdated
+);
+
+// Data selectors
+export const selectZohoBooksContacts = createSelector(
+ [selectZohoBooksState],
+ (state) => state.contacts
+);
+
+export const selectZohoBooksInvoices = createSelector(
+ [selectZohoBooksState],
+ (state) => state.invoices
+);
+
+export const selectZohoBooksExpenses = createSelector(
+ [selectZohoBooksState],
+ (state) => state.expenses
+);
+
+export const selectZohoBooksSalesOrders = createSelector(
+ [selectZohoBooksState],
+ (state) => state.salesOrders
+);
+
+export const selectZohoBooksPurchaseOrders = createSelector(
+ [selectZohoBooksState],
+ (state) => state.purchaseOrders
+);
+
+export const selectZohoBooksBills = createSelector(
+ [selectZohoBooksState],
+ (state) => state.bills
+);
+
+export const selectZohoBooksKPIData = createSelector(
+ [selectZohoBooksState],
+ (state) => state.kpiData
+);
+
+// Computed selectors
+export const selectCustomers = createSelector(
+ [selectZohoBooksContacts],
+ (contacts) => contacts.filter((contact: ZohoBooksContact) => contact.contact_type === 'customer')
+);
+
+export const selectVendors = createSelector(
+ [selectZohoBooksContacts],
+ (contacts) => contacts.filter((contact: ZohoBooksContact) => contact.contact_type === 'vendor')
+);
+
+export const selectActiveInvoices = createSelector(
+ [selectZohoBooksInvoices],
+ (invoices) => invoices.filter((invoice: ZohoBooksInvoice) =>
+ invoice.status === 'sent' || invoice.status === 'overdue'
+ )
+);
+
+export const selectPaidInvoices = createSelector(
+ [selectZohoBooksInvoices],
+ (invoices) => invoices.filter((invoice: ZohoBooksInvoice) => invoice.status === 'paid')
+);
+
+export const selectDraftInvoices = createSelector(
+ [selectZohoBooksInvoices],
+ (invoices) => invoices.filter((invoice: ZohoBooksInvoice) => invoice.status === 'draft')
+);
+
+// Top customers by outstanding amount
+export const selectTopCustomersByOutstanding = createSelector(
+ [selectCustomers],
+ (customers) =>
+ customers
+ .sort((a, b) => b.outstanding_receivable_amount - a.outstanding_receivable_amount)
+ .slice(0, 5)
+);
+
+// Top vendors by outstanding amount
+export const selectTopVendorsByOutstanding = createSelector(
+ [selectVendors],
+ (vendors) =>
+ vendors
+ .sort((a, b) => b.outstanding_payable_amount - a.outstanding_payable_amount)
+ .slice(0, 5)
+);
+
+// Invoice status distribution
+export const selectInvoiceStatusDistribution = createSelector(
+ [selectZohoBooksInvoices],
+ (invoices) => {
+ const distribution = invoices.reduce((acc, invoice) => {
+ acc[invoice.status] = (acc[invoice.status] || 0) + 1;
+ return acc;
+ }, {} as Record);
+
+ return Object.entries(distribution).map(([status, count]) => ({
+ label: status.charAt(0).toUpperCase() + status.slice(1),
+ value: count,
+ color: getStatusColor(status),
+ }));
+ }
+);
+
+// Helper function to get status colors
+const getStatusColor = (status: string): string => {
+ const colors: Record = {
+ draft: '#94A3B8',
+ sent: '#3AA0FF',
+ invoiced: '#6366F1',
+ paid: '#10B981',
+ overdue: '#EF4444',
+ void: '#6B7280',
+ };
+ return colors[status] || '#6B7280';
+};
diff --git a/src/modules/finance/zoho/store/zohoBooksSlice.ts b/src/modules/finance/zoho/store/zohoBooksSlice.ts
new file mode 100644
index 0000000..b196b8b
--- /dev/null
+++ b/src/modules/finance/zoho/store/zohoBooksSlice.ts
@@ -0,0 +1,229 @@
+import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
+import { zohoBooksAPI } from '../services/zohoBooksAPI';
+import type {
+ ZohoBooksDashboardState,
+ ZohoBooksContact,
+ ZohoBooksInvoice,
+ ZohoBooksExpense,
+ ZohoBooksSalesOrder,
+ ZohoBooksPurchaseOrder,
+ ZohoBooksBill,
+ ZohoBooksKPIData,
+} from '../types/ZohoBooksTypes';
+
+// Initial state
+const initialState: ZohoBooksDashboardState = {
+ contacts: [],
+ invoices: [],
+ expenses: [],
+ salesOrders: [],
+ purchaseOrders: [],
+ bills: [],
+ kpiData: {
+ totalCustomers: 0,
+ totalVendors: 0,
+ totalInvoices: 0,
+ totalExpenses: 0,
+ totalSalesOrders: 0,
+ totalPurchaseOrders: 0,
+ totalBills: 0,
+ outstandingReceivables: 0,
+ outstandingPayables: 0,
+ totalRevenue: 0,
+ totalExpensesAmount: 0,
+ },
+ loading: false,
+ error: null,
+ lastUpdated: null,
+};
+
+// Helper function to calculate KPI data
+const calculateKPIData = (
+ contacts: ZohoBooksContact[],
+ invoices: ZohoBooksInvoice[],
+ expenses: ZohoBooksExpense[],
+ salesOrders: ZohoBooksSalesOrder[],
+ purchaseOrders: ZohoBooksPurchaseOrder[],
+ bills: ZohoBooksBill[]
+): ZohoBooksKPIData => {
+ // Calculate totals
+ const totalCustomers = contacts.filter(c => c.contact_type === 'customer').length;
+ const totalVendors = contacts.filter(c => c.contact_type === 'vendor').length;
+ const totalInvoices = invoices.length;
+ const totalExpenses = expenses.length;
+ const totalSalesOrders = salesOrders.length;
+ const totalPurchaseOrders = purchaseOrders.length;
+ const totalBills = bills.length;
+
+ // Calculate financial metrics
+ const outstandingReceivables = invoices
+ .filter(inv => inv.status === 'sent' || inv.status === 'overdue')
+ .reduce((sum, inv) => sum + inv.balance, 0);
+
+ const outstandingPayables = bills
+ .filter(bill => bill.status === 'open' || bill.status === 'overdue')
+ .reduce((sum, bill) => sum + bill.balance, 0);
+
+ const totalRevenue = invoices
+ .filter(inv => inv.status === 'paid')
+ .reduce((sum, inv) => sum + inv.total, 0);
+
+ const totalExpensesAmount = expenses.reduce((sum, exp) => sum + exp.total, 0);
+
+ return {
+ totalCustomers,
+ totalVendors,
+ totalInvoices,
+ totalExpenses,
+ totalSalesOrders,
+ totalPurchaseOrders,
+ totalBills,
+ outstandingReceivables,
+ outstandingPayables,
+ totalRevenue,
+ totalExpensesAmount,
+ };
+};
+
+// Async thunks
+export const fetchZohoBooksData = createAsyncThunk(
+ 'zohoBooks/fetchAllData',
+ async (_, { rejectWithValue }) => {
+ try {
+ const data = await zohoBooksAPI.getAllData();
+ return data;
+ } catch (error: any) {
+ return rejectWithValue(error.message || 'Failed to fetch Zoho Books data');
+ }
+ }
+);
+
+export const fetchZohoBooksContacts = createAsyncThunk(
+ 'zohoBooks/fetchContacts',
+ async (_, { rejectWithValue }) => {
+ try {
+ const response = await zohoBooksAPI.getContacts();
+ return response.data.data;
+ } catch (error: any) {
+ return rejectWithValue(error.message || 'Failed to fetch contacts');
+ }
+ }
+);
+
+export const fetchZohoBooksInvoices = createAsyncThunk(
+ 'zohoBooks/fetchInvoices',
+ async (_, { rejectWithValue }) => {
+ try {
+ const response = await zohoBooksAPI.getInvoices();
+ return response.data.data;
+ } catch (error: any) {
+ return rejectWithValue(error.message || 'Failed to fetch invoices');
+ }
+ }
+);
+
+export const fetchZohoBooksExpenses = createAsyncThunk(
+ 'zohoBooks/fetchExpenses',
+ async (_, { rejectWithValue }) => {
+ try {
+ const response = await zohoBooksAPI.getExpenses();
+ return response.data.data;
+ } catch (error: any) {
+ return rejectWithValue(error.message || 'Failed to fetch expenses');
+ }
+ }
+);
+
+// Slice
+const zohoBooksSlice = createSlice({
+ name: 'zohoBooks',
+ initialState,
+ reducers: {
+ clearError: (state) => {
+ state.error = null;
+ },
+ resetState: () => initialState,
+ },
+ extraReducers: (builder) => {
+ builder
+ // Fetch all data
+ .addCase(fetchZohoBooksData.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksData.fulfilled, (state, action) => {
+ state.loading = false;
+ state.contacts = action.payload.contacts;
+ state.invoices = action.payload.invoices;
+ state.expenses = action.payload.expenses;
+ state.salesOrders = action.payload.salesOrders;
+ state.purchaseOrders = action.payload.purchaseOrders;
+ state.bills = action.payload.bills;
+
+ // Calculate KPI data
+ state.kpiData = calculateKPIData(
+ action.payload.contacts,
+ action.payload.invoices,
+ action.payload.expenses,
+ action.payload.salesOrders,
+ action.payload.purchaseOrders,
+ action.payload.bills
+ );
+
+ state.lastUpdated = new Date().toISOString();
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksData.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload as string;
+ })
+
+ // Fetch contacts
+ .addCase(fetchZohoBooksContacts.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksContacts.fulfilled, (state, action) => {
+ state.loading = false;
+ state.contacts = action.payload;
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksContacts.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload as string;
+ })
+
+ // Fetch invoices
+ .addCase(fetchZohoBooksInvoices.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksInvoices.fulfilled, (state, action) => {
+ state.loading = false;
+ state.invoices = action.payload;
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksInvoices.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload as string;
+ })
+
+ // Fetch expenses
+ .addCase(fetchZohoBooksExpenses.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksExpenses.fulfilled, (state, action) => {
+ state.loading = false;
+ state.expenses = action.payload;
+ state.error = null;
+ })
+ .addCase(fetchZohoBooksExpenses.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload as string;
+ });
+ },
+});
+
+export const { clearError, resetState } = zohoBooksSlice.actions;
+export default zohoBooksSlice.reducer;
diff --git a/src/modules/finance/zoho/types/ZohoBooksTypes.ts b/src/modules/finance/zoho/types/ZohoBooksTypes.ts
new file mode 100644
index 0000000..1efdf90
--- /dev/null
+++ b/src/modules/finance/zoho/types/ZohoBooksTypes.ts
@@ -0,0 +1,333 @@
+// Zoho Books API Response Types
+
+export interface ZohoBooksContact {
+ contact_id: string;
+ contact_name: string;
+ customer_name: string;
+ vendor_name: string;
+ company_name: string;
+ website: string;
+ language_code: string;
+ language_code_formatted: string;
+ contact_type: 'customer' | 'vendor';
+ contact_type_formatted: string;
+ status: 'active' | 'inactive';
+ customer_sub_type: 'business' | 'individual';
+ source: string;
+ is_linked_with_zohocrm: boolean;
+ payment_terms: number;
+ payment_terms_label: string;
+ currency_id: string;
+ twitter: string;
+ facebook: string;
+ currency_code: string;
+ outstanding_receivable_amount: number;
+ outstanding_receivable_amount_bcy: number;
+ outstanding_payable_amount: number;
+ outstanding_payable_amount_bcy: number;
+ unused_credits_receivable_amount: number;
+ unused_credits_receivable_amount_bcy: number;
+ unused_credits_payable_amount: number;
+ unused_credits_payable_amount_bcy: number;
+ first_name: string;
+ last_name: string;
+ email: string;
+ phone: string;
+ mobile: string;
+ has_photo: boolean;
+ portal_status: 'enabled' | 'disabled';
+ portal_status_formatted: string;
+ created_time: string;
+ created_time_formatted: string;
+ last_modified_time: string;
+ last_modified_time_formatted: string;
+ custom_fields: any[];
+ custom_field_hash: Record;
+ tags: string[];
+ ach_supported: boolean;
+ has_attachment: boolean;
+ pan_no: string;
+}
+
+export interface ZohoBooksInvoice {
+ ach_payment_initiated: boolean;
+ invoice_id: string;
+ zcrm_potential_id: string;
+ customer_id: string;
+ zcrm_potential_name: string;
+ customer_name: string;
+ company_name: string;
+ status: 'draft' | 'sent' | 'invoiced' | 'paid' | 'overdue' | 'void';
+ invoice_number: string;
+ reference_number: string;
+ date: string;
+ due_date: string;
+ due_days: string;
+ email: string;
+ type: 'invoice' | 'estimate' | 'creditnote';
+ project_name: string;
+ billing_address: {
+ address: string;
+ street2: string;
+ city: string;
+ state: string;
+ zipcode: string;
+ country: string;
+ phone: string;
+ fax: string;
+ attention: string;
+ };
+ shipping_address: {
+ address: string;
+ street2: string;
+ city: string;
+ state: string;
+ zipcode: string;
+ country: string;
+ phone: string;
+ fax: string;
+ attention: string;
+ };
+ country: string;
+ phone: string;
+ created_by: string;
+ total: number;
+ balance: number;
+ payment_expected_date: string;
+ custom_fields: any[];
+ custom_field_hash: Record;
+ tags: string[];
+ salesperson_name: string;
+ shipping_charge: number;
+ adjustment: number;
+ created_time: string;
+ last_modified_time: string;
+ updated_time: string;
+ is_viewed_by_client: boolean;
+ has_attachment: boolean;
+ client_viewed_time: string;
+ is_emailed: boolean;
+ color_code: string;
+ current_sub_status_id: string;
+ current_sub_status: string;
+ currency_id: string;
+ schedule_time: string;
+ currency_code: string;
+ currency_symbol: string;
+ is_pre_gst: boolean;
+ template_type: string;
+ no_of_copies: number;
+ show_no_of_copies: boolean;
+ transaction_type: string;
+ reminders_sent: number;
+ last_reminder_sent_date: string;
+ last_payment_date: string;
+ template_id: string;
+ documents: string;
+ salesperson_id: string;
+ write_off_amount: number;
+ exchange_rate: number;
+ unprocessed_payment_amount: number;
+}
+
+export interface ZohoBooksExpense {
+ expense_id: string;
+ date: string;
+ user_name: string;
+ paid_through_account_name: string;
+ account_name: string;
+ description: string;
+ currency_id: string;
+ currency_code: string;
+ bcy_total: number;
+ bcy_total_without_tax: number;
+ total: number;
+ total_without_tax: number;
+ is_billable: boolean;
+ reference_number: string;
+ customer_id: string;
+ is_personal: boolean;
+ customer_name: string;
+ vendor_id: string;
+ vendor_name: string;
+ status: 'billable' | 'nonbillable';
+ created_time: string;
+ last_modified_time: string;
+ expense_receipt_name: string;
+ exchange_rate: number;
+ distance: number;
+ mileage_rate: number;
+ mileage_unit: string;
+ mileage_type: string;
+ expense_type: string;
+ report_id: string;
+ start_reading: string;
+ end_reading: string;
+ report_name: string;
+ report_number: string;
+ has_attachment: boolean;
+ custom_fields_list: string;
+}
+
+export interface ZohoBooksSalesOrder {
+ salesorder_id: string;
+ zcrm_potential_id: string;
+ zcrm_potential_name: string;
+ customer_name: string;
+ customer_id: string;
+ email: string;
+ delivery_date: string;
+ company_name: string;
+ color_code: string;
+ current_sub_status_id: string;
+ current_sub_status: string;
+ pickup_location_id: string;
+ salesorder_number: string;
+ reference_number: string;
+ date: string;
+ shipment_date: string;
+ shipment_days: string;
+ due_by_days: string;
+ due_in_days: string;
+ currency_id: string;
+ source: string;
+ currency_code: string;
+ total: number;
+ bcy_total: number;
+ total_invoiced_amount: number;
+ created_time: string;
+ last_modified_time: string;
+ is_emailed: boolean;
+ quantity_invoiced: number;
+ order_status: 'draft' | 'confirmed' | 'delivered' | 'cancelled';
+ invoiced_status: string;
+ paid_status: string;
+ shipped_status: string;
+ status: 'draft' | 'confirmed' | 'delivered' | 'cancelled';
+ order_fulfillment_type: string;
+ is_manually_fulfilled: boolean;
+ salesperson_name: string;
+ has_attachment: boolean;
+ tags: string[];
+ custom_fields_list: string;
+ delivery_method: string;
+ delivery_method_id: string;
+ is_viewed_in_mail: boolean;
+ mail_first_viewed_time: string;
+ mail_last_viewed_time: string;
+ is_scheduled_for_quick_shipment_create: boolean;
+}
+
+export interface ZohoBooksPurchaseOrder {
+ purchaseorder_id: string;
+ vendor_id: string;
+ vendor_name: string;
+ company_name: string;
+ order_status: 'draft' | 'confirmed' | 'received' | 'cancelled';
+ billed_status: string;
+ received_status: string;
+ status: 'draft' | 'confirmed' | 'received' | 'cancelled';
+ color_code: string;
+ current_sub_status_id: string;
+ current_sub_status: string;
+ purchaseorder_number: string;
+ reference_number: string;
+ date: string;
+ delivery_date: string;
+ expected_delivery_date: string;
+ delivery_days: string;
+ due_by_days: string;
+ due_in_days: string;
+ currency_id: string;
+ currency_code: string;
+ price_precision: string;
+ total: number;
+ has_attachment: boolean;
+ tags: string[];
+ created_time: string;
+ last_modified_time: string;
+ quantity_yet_to_receive: number;
+ quantity_marked_as_received: number;
+ is_po_marked_as_received: boolean;
+ receives: any[];
+ client_viewed_time: string;
+ is_viewed_by_client: boolean;
+}
+
+export interface ZohoBooksBill {
+ bill_id: string;
+ vendor_id: string;
+ vendor_name: string;
+ status: 'draft' | 'open' | 'paid' | 'overdue' | 'void';
+ color_code: string;
+ current_sub_status_id: string;
+ current_sub_status: string;
+ bill_number: string;
+ reference_number: string;
+ date: string;
+ due_date: string;
+ due_days: string;
+ currency_id: string;
+ currency_code: string;
+ price_precision: number;
+ exchange_rate: number;
+ total: number;
+ tds_total: number;
+ balance: number;
+ unprocessed_payment_amount: number;
+ created_time: string;
+ last_modified_time: string;
+ is_opening_balance: string;
+ attachment_name: string;
+ has_attachment: boolean;
+ tags: string[];
+ is_uber_bill: boolean;
+ is_tally_bill: boolean;
+ entity_type: string;
+ client_viewed_time: string;
+ is_viewed_by_client: boolean;
+ is_bill_reconciliation_violated: boolean;
+ balance_due: number;
+}
+
+export interface ZohoBooksApiResponse {
+ status: 'success' | 'error';
+ message: string;
+ data: {
+ data: T[];
+ info: {
+ count: number;
+ moreRecords: boolean;
+ page: number;
+ };
+ };
+ timestamp: string;
+}
+
+// Dashboard specific types
+export interface ZohoBooksKPIData {
+ totalCustomers: number;
+ totalVendors: number;
+ totalInvoices: number;
+ totalExpenses: number;
+ totalSalesOrders: number;
+ totalPurchaseOrders: number;
+ totalBills: number;
+ outstandingReceivables: number;
+ outstandingPayables: number;
+ totalRevenue: number;
+ totalExpensesAmount: number;
+}
+
+export interface ZohoBooksDashboardState {
+ contacts: ZohoBooksContact[];
+ invoices: ZohoBooksInvoice[];
+ expenses: ZohoBooksExpense[];
+ salesOrders: ZohoBooksSalesOrder[];
+ purchaseOrders: ZohoBooksPurchaseOrder[];
+ bills: ZohoBooksBill[];
+ kpiData: ZohoBooksKPIData;
+ loading: boolean;
+ error: string | null;
+ lastUpdated: string | null;
+}
diff --git a/src/modules/hr/index.ts b/src/modules/hr/index.ts
new file mode 100644
index 0000000..bdd7428
--- /dev/null
+++ b/src/modules/hr/index.ts
@@ -0,0 +1,5 @@
+// HR Module Exports
+export { default as HRNavigator } from './navigation/HRNavigator';
+
+// Zoho HR exports
+export * from './zoho';
diff --git a/src/modules/hr/navigation/HRNavigator.tsx b/src/modules/hr/navigation/HRNavigator.tsx
index 63f45fa..590d1ac 100644
--- a/src/modules/hr/navigation/HRNavigator.tsx
+++ b/src/modules/hr/navigation/HRNavigator.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
-import HRDashboardScreen from '@/modules/hr/screens/HRDashboardScreen';
+import HRDashboardScreen from '@/modules/hr/zoho/screens/HRDashboardScreen';
const Stack = createStackNavigator();
diff --git a/src/modules/hr/zoho/index.ts b/src/modules/hr/zoho/index.ts
new file mode 100644
index 0000000..bd1ff24
--- /dev/null
+++ b/src/modules/hr/zoho/index.ts
@@ -0,0 +1,4 @@
+// Zoho HR Module Exports
+export { default as HRDashboardScreen } from './screens/HRDashboardScreen';
+export { default as hrSlice } from './store/hrSlice';
+export { hrAPI } from './services/hrAPI';
diff --git a/src/modules/hr/screens/HRDashboardScreen.tsx b/src/modules/hr/zoho/screens/HRDashboardScreen.tsx
similarity index 99%
rename from src/modules/hr/screens/HRDashboardScreen.tsx
rename to src/modules/hr/zoho/screens/HRDashboardScreen.tsx
index 4ac85d9..10d0ad5 100644
--- a/src/modules/hr/screens/HRDashboardScreen.tsx
+++ b/src/modules/hr/zoho/screens/HRDashboardScreen.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { Container, LoadingSpinner, ErrorState } from '@/shared/components/ui';
-import { fetchHRMetrics } from '@/modules/hr/store/hrSlice';
+import { fetchHRMetrics } from '../store/hrSlice';
import type { RootState } from '@/store/store';
import { useTheme } from '@/shared/styles/useTheme';
diff --git a/src/modules/hr/services/hrAPI.ts b/src/modules/hr/zoho/services/hrAPI.ts
similarity index 100%
rename from src/modules/hr/services/hrAPI.ts
rename to src/modules/hr/zoho/services/hrAPI.ts
diff --git a/src/modules/hr/store/hrSlice.ts b/src/modules/hr/zoho/store/hrSlice.ts
similarity index 100%
rename from src/modules/hr/store/hrSlice.ts
rename to src/modules/hr/zoho/store/hrSlice.ts
diff --git a/src/modules/zohoProjects/screens/ZohoProjectsDashboardScreen.tsx b/src/modules/zohoProjects/screens/ZohoProjectsDashboardScreen.tsx
index bbcd3b7..fe0f6e7 100644
--- a/src/modules/zohoProjects/screens/ZohoProjectsDashboardScreen.tsx
+++ b/src/modules/zohoProjects/screens/ZohoProjectsDashboardScreen.tsx
@@ -128,13 +128,12 @@ const ZohoProjectsDashboardScreen: React.FC = () => {
>
-
{/* Projects Overview Header */}
-
+
Projects Overview
diff --git a/src/modules/zohoProjects/screens/ZohoProjectsDataScreen.tsx b/src/modules/zohoProjects/screens/ZohoProjectsDataScreen.tsx
index 479de16..23e5f70 100644
--- a/src/modules/zohoProjects/screens/ZohoProjectsDataScreen.tsx
+++ b/src/modules/zohoProjects/screens/ZohoProjectsDataScreen.tsx
@@ -138,6 +138,15 @@ const ZohoProjectsDataScreen: React.FC = () => {
}
const renderTabContent = () => {
+ const commonFlatListProps = {
+ numColumns: 1,
+ showsVerticalScrollIndicator: false,
+ contentContainerStyle: styles.listContainer,
+ refreshControl: (
+
+ ),
+ };
+
switch (selectedTab) {
case 'projects':
return (
@@ -150,9 +159,7 @@ const ZohoProjectsDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'tasks':
@@ -166,9 +173,7 @@ const ZohoProjectsDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'issues':
@@ -182,9 +187,7 @@ const ZohoProjectsDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
case 'phases':
@@ -198,9 +201,7 @@ const ZohoProjectsDataScreen: React.FC = () => {
/>
)}
keyExtractor={(item) => item.id}
- numColumns={1}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContainer}
+ {...commonFlatListProps}
/>
);
default:
@@ -209,13 +210,7 @@ const ZohoProjectsDataScreen: React.FC = () => {
};
return (
-
-
- }
- >
+
{/* Header */}
@@ -226,67 +221,68 @@ const ZohoProjectsDataScreen: React.FC = () => {
- {/* Tabs */}
-
-
- {tabs.map((tab) => (
- setSelectedTab(tab.key)}
- activeOpacity={0.8}
- >
-
-
+
+
+ {tabs.map((tab) => (
+
- {tab.label}
-
- setSelectedTab(tab.key)}
+ activeOpacity={0.8}
>
+
- {tab.count}
+ {tab.label}
-
-
- ))}
-
-
+
+
+ {tab.count}
+
+
+
+ ))}
+
+
+
{/* Content */}
{renderTabContent()}
-
-
+
);
};
@@ -305,7 +301,10 @@ const styles = StyleSheet.create({
fontSize: 24,
},
tabsContainer: {
- marginBottom: 16,
+ backgroundColor: '#FFFFFF',
+ paddingVertical: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#E5E7EB',
},
tabs: {
flexDirection: 'row',
@@ -336,9 +335,9 @@ const styles = StyleSheet.create({
},
content: {
flex: 1,
- paddingHorizontal: 16,
},
listContainer: {
+ paddingHorizontal: 16,
paddingBottom: 20,
},
});
diff --git a/src/store/store.ts b/src/store/store.ts
index a4653f3..8ecc04e 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -5,11 +5,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
// Feature slices (to be added)
import uiSlice from '@/shared/store/uiSlice';
import authSlice from '@/modules/auth/store/authSlice';
-import hrSlice from '@/modules/hr/store/hrSlice';
+import hrSlice from '@/modules/hr/zoho/store/hrSlice';
import zohoProjectsSlice from '@/modules/zohoProjects/store/zohoProjectsSlice';
import profileSlice from '@/modules/profile/store/profileSlice';
import integrationsSlice from '@/modules/integrations/store/integrationsSlice';
-import crmSlice from '@/modules/crm/store/crmSlice';
+import crmSlice from '@/modules/crm/zoho/store/crmSlice';
+import zohoBooksSlice from '@/modules/finance/zoho/store/zohoBooksSlice';
const rootReducer = combineReducers({
auth: authSlice.reducer,
@@ -18,13 +19,14 @@ const rootReducer = combineReducers({
profile: profileSlice.reducer,
integrations: integrationsSlice.reducer,
crm: crmSlice,
+ zohoBooks: zohoBooksSlice,
ui: uiSlice.reducer,
});
const persistConfig = {
key: 'root',
storage: AsyncStorage,
- whitelist: ['auth', 'hr', 'zohoProjects', 'profile', 'integrations', 'crm'],
+ whitelist: ['auth', 'hr', 'zohoProjects', 'profile', 'integrations', 'crm', 'zohoBooks'],
blacklist: ['ui'],
};