549 lines
19 KiB
TypeScript
549 lines
19 KiB
TypeScript
import React from 'react';
|
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
import { useTheme } from '@/shared/styles/useTheme';
|
|
import type { CrmLead, CrmTask, CrmContact, CrmDeal, CrmSalesOrder, CrmPurchaseOrder, CrmInvoice } from '../types/CrmTypes';
|
|
|
|
interface BaseCardProps {
|
|
onPress: () => void;
|
|
}
|
|
|
|
interface LeadCardProps extends BaseCardProps {
|
|
lead: CrmLead;
|
|
}
|
|
|
|
interface TaskCardProps extends BaseCardProps {
|
|
task: CrmTask;
|
|
}
|
|
|
|
interface ContactCardProps extends BaseCardProps {
|
|
contact: CrmContact;
|
|
}
|
|
|
|
interface DealCardProps extends BaseCardProps {
|
|
deal: CrmDeal;
|
|
}
|
|
|
|
interface SalesOrderCardProps extends BaseCardProps {
|
|
salesOrder: CrmSalesOrder;
|
|
}
|
|
|
|
interface PurchaseOrderCardProps extends BaseCardProps {
|
|
purchaseOrder: CrmPurchaseOrder;
|
|
}
|
|
|
|
interface InvoiceCardProps extends BaseCardProps {
|
|
invoice: CrmInvoice;
|
|
}
|
|
|
|
const getStatusColor = (status: string, colors: any) => {
|
|
// return '#3AA0FF';
|
|
switch (status.toLowerCase()) {
|
|
case 'new':
|
|
case 'not started':
|
|
case 'attempted to contact':
|
|
return '#3AA0FF';
|
|
case 'contacted':
|
|
case 'in progress':
|
|
case 'qualification':
|
|
return '#F59E0B';
|
|
case 'qualified':
|
|
case 'proposal':
|
|
return '#10B981';
|
|
case 'completed':
|
|
case 'closed won':
|
|
return '#22C55E';
|
|
case 'unqualified':
|
|
case 'cancelled':
|
|
case 'lost lead':
|
|
return '#EF4444';
|
|
default:
|
|
return colors.textLight;
|
|
}
|
|
};
|
|
|
|
const getPriorityColor = (priority: string) => {
|
|
return '#EF4444';
|
|
switch (priority.toLowerCase()) {
|
|
case 'high':
|
|
return '#EF4444';
|
|
case 'medium':
|
|
return '#F59E0B';
|
|
case 'low':
|
|
return '#10B981';
|
|
default:
|
|
return '#6B7280';
|
|
}
|
|
};
|
|
|
|
export const LeadCard: React.FC<LeadCardProps> = ({ lead, onPress }) => {
|
|
const { colors, fonts, shadows } = useTheme();
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border, ...shadows.medium }]}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={styles.cardHeader}>
|
|
<View style={styles.cardTitleRow}>
|
|
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]} numberOfLines={1}>
|
|
{lead.Full_Name}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(lead.Lead_Status, colors) }]}>
|
|
<Text style={[styles.statusText, { color: colors.surface, fontFamily: fonts.medium }]}>
|
|
{lead.Lead_Status}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={[styles.cardSubtitle, { color: colors.textLight, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
{lead.Company}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.cardContent}>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="email-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
{lead.Email}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="phone-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
{lead.Phone}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="source-branch" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Source: {lead.Lead_Source}
|
|
</Text>
|
|
</View>
|
|
{lead.Annual_Revenue && (
|
|
<View style={styles.infoRow}>
|
|
<Icon name="currency-usd" size={16} color={colors.primary} />
|
|
<Text style={[styles.infoText, { color: colors.primary, fontFamily: fonts.bold }]}>
|
|
${lead.Annual_Revenue.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
<View style={styles.cardFooter}>
|
|
<Text style={[styles.dateText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
|
Created: {new Date(lead.Created_Time)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
export const TaskCard: React.FC<TaskCardProps> = ({ task, onPress }) => {
|
|
const { colors, fonts, shadows } = useTheme();
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border, ...shadows.medium }]}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={styles.cardHeader}>
|
|
<View style={styles.cardTitleRow}>
|
|
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]} numberOfLines={2}>
|
|
{task.Subject}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(task.Status, colors) }]}>
|
|
<Text style={[styles.statusText, { color: colors.surface, fontFamily: fonts.medium }]}>
|
|
{task.Status}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={[styles.cardSubtitle, { color: colors.textLight, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
{task.Priority}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.cardContent}>
|
|
<Text style={[styles.descriptionText, { color: colors.text, fontFamily: fonts.regular }]} numberOfLines={2}>
|
|
{task.Description}
|
|
</Text>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="flag-outline" size={16} color={getPriorityColor(task.Priority)} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Priority: {task.Priority}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="account-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Assigned: {task.Owner.name}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.cardFooter}>
|
|
<Text style={[styles.dateText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
|
Due: {new Date(task.Due_Date)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
export const ContactCard: React.FC<ContactCardProps> = ({ contact, onPress }) => {
|
|
const { colors, fonts, shadows } = useTheme();
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border, ...shadows.medium }]}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={styles.cardHeader}>
|
|
<View style={styles.cardTitleRow}>
|
|
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]} numberOfLines={1}>
|
|
{contact.Full_Name}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor('Active', colors) }]}>
|
|
<Text style={[styles.statusText, { color: colors.surface, fontFamily: fonts.medium }]}>
|
|
Active
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={[styles.cardSubtitle, { color: colors.textLight, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
{contact.Title} at {contact.Account_Name?.name || 'N/A'}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.cardContent}>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="email-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
{contact.Email}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="phone-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
{contact.Phone}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="source-branch" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Source: {contact.Lead_Source}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.cardFooter}>
|
|
<Text style={[styles.dateText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
|
Last contact: {new Date(contact.Last_Activity_Time)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
export const DealCard: React.FC<DealCardProps> = ({ deal, onPress }) => {
|
|
const { colors, fonts, shadows } = useTheme();
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border, ...shadows.medium }]}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={styles.cardHeader}>
|
|
<View style={styles.cardTitleRow}>
|
|
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]} numberOfLines={1}>
|
|
{deal.Deal_Name}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(deal.Stage, colors) }]}>
|
|
<Text style={[styles.statusText, { color: colors.surface, fontFamily: fonts.medium }]}>
|
|
{deal.Stage}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={[styles.cardSubtitle, { color: colors.textLight, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
{deal.Account_Name?.name || 'N/A'}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.cardContent}>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="currency-usd" size={16} color={colors.primary} />
|
|
<Text style={[styles.infoText, { color: colors.primary, fontFamily: fonts.bold }]}>
|
|
${deal.Amount?.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="trending-up" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Probability: {deal.Probability}%
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="account-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Contact: {deal.Contact_Name?.name || 'N/A'}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.cardFooter}>
|
|
<Text style={[styles.dateText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
|
Close date: {new Date(deal.Closing_Date)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
export const SalesOrderCard: React.FC<SalesOrderCardProps> = ({ salesOrder, onPress }) => {
|
|
const { colors, fonts, shadows } = useTheme();
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border, ...shadows.medium }]}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={styles.cardHeader}>
|
|
<View style={styles.cardTitleRow}>
|
|
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]} numberOfLines={1}>
|
|
{salesOrder.Subject}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(salesOrder.Status, colors) }]}>
|
|
<Text style={[styles.statusText, { color: colors.surface, fontFamily: fonts.medium }]}>
|
|
{salesOrder.Status}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={[styles.cardSubtitle, { color: colors.textLight, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
SO: {salesOrder.SO_Number}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.cardContent}>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="currency-usd" size={16} color={colors.primary} />
|
|
<Text style={[styles.infoText, { color: colors.primary, fontFamily: fonts.bold }]}>
|
|
{salesOrder.$currency_symbol}{salesOrder.Grand_Total?.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="account-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Account: {salesOrder.Account_Name?.name || 'N/A'}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="map-marker-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
{salesOrder.Billing_City}, {salesOrder.Billing_Country}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="truck-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Carrier: {salesOrder.Carrier}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.cardFooter}>
|
|
<Text style={[styles.dateText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
|
Created: {new Date(salesOrder.Created_Time)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
export const PurchaseOrderCard: React.FC<PurchaseOrderCardProps> = ({ purchaseOrder, onPress }) => {
|
|
const { colors, fonts, shadows } = useTheme();
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border, ...shadows.medium }]}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={styles.cardHeader}>
|
|
<View style={styles.cardTitleRow}>
|
|
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]} numberOfLines={1}>
|
|
{purchaseOrder.Subject}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(purchaseOrder.Status, colors) }]}>
|
|
<Text style={[styles.statusText, { color: colors.surface, fontFamily: fonts.medium }]}>
|
|
{purchaseOrder.Status}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={[styles.cardSubtitle, { color: colors.textLight, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
PO: {purchaseOrder.PO_Number || 'N/A'}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.cardContent}>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="currency-usd" size={16} color={colors.primary} />
|
|
<Text style={[styles.infoText, { color: colors.primary, fontFamily: fonts.bold }]}>
|
|
{purchaseOrder.$currency_symbol}{purchaseOrder.Grand_Total?.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="store-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Vendor: {purchaseOrder.Vendor_Name?.name || 'N/A'}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="map-marker-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
{purchaseOrder.Billing_City}, {purchaseOrder.Billing_Country}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="truck-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Carrier: {purchaseOrder.Carrier}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.cardFooter}>
|
|
<Text style={[styles.dateText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
|
PO Date: {new Date(purchaseOrder.PO_Date)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
export const InvoiceCard: React.FC<InvoiceCardProps> = ({ invoice, onPress }) => {
|
|
const { colors, fonts, shadows } = useTheme();
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border, ...shadows.medium }]}
|
|
onPress={onPress}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View style={styles.cardHeader}>
|
|
<View style={styles.cardTitleRow}>
|
|
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]} numberOfLines={1}>
|
|
{invoice.Subject}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(invoice.Status, colors) }]}>
|
|
<Text style={[styles.statusText, { color: colors.surface, fontFamily: fonts.medium }]}>
|
|
{invoice.Status}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={[styles.cardSubtitle, { color: colors.textLight, fontFamily: fonts.regular }]} numberOfLines={1}>
|
|
Invoice: {invoice.Invoice_Number}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.cardContent}>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="currency-usd" size={16} color={colors.primary} />
|
|
<Text style={[styles.infoText, { color: colors.primary, fontFamily: fonts.bold }]}>
|
|
{invoice.$currency_symbol}{invoice.Grand_Total?.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="account-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Account: {invoice.Account_Name?.name || 'N/A'}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="map-marker-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
{invoice.Billing_City}, {invoice.Billing_Country}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoRow}>
|
|
<Icon name="calendar-outline" size={16} color={colors.textLight} />
|
|
<Text style={[styles.infoText, { color: colors.text, fontFamily: fonts.regular }]}>
|
|
Due: {new Date(invoice.Due_Date)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.cardFooter}>
|
|
<Text style={[styles.dateText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
|
Invoice Date: {new Date(invoice.Invoice_Date)?.toLocaleDateString()}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
card: {
|
|
borderRadius: 12,
|
|
borderWidth: 1,
|
|
marginBottom: 16,
|
|
overflow: 'hidden',
|
|
},
|
|
cardHeader: {
|
|
padding: 16,
|
|
paddingBottom: 12,
|
|
},
|
|
cardTitleRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'flex-start',
|
|
marginBottom: 4,
|
|
},
|
|
cardTitle: {
|
|
fontSize: 16,
|
|
flex: 1,
|
|
marginRight: 8,
|
|
},
|
|
cardSubtitle: {
|
|
fontSize: 14,
|
|
},
|
|
statusBadge: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 12,
|
|
},
|
|
statusText: {
|
|
fontSize: 12,
|
|
},
|
|
cardContent: {
|
|
paddingHorizontal: 16,
|
|
paddingBottom: 12,
|
|
},
|
|
infoRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: 6,
|
|
},
|
|
infoText: {
|
|
marginLeft: 8,
|
|
fontSize: 14,
|
|
flex: 1,
|
|
},
|
|
descriptionText: {
|
|
fontSize: 14,
|
|
marginBottom: 8,
|
|
lineHeight: 20,
|
|
},
|
|
cardFooter: {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
backgroundColor: '#F8F9FA',
|
|
},
|
|
dateText: {
|
|
fontSize: 12,
|
|
},
|
|
});
|
|
|