parent
69f1ee64fb
commit
7e735e3e7d
|
|
@ -1,7 +1,7 @@
|
|||
// Environment Configuration
|
||||
const ENV = {
|
||||
dev: {
|
||||
API_URL: 'http://192.168.65.3:3000', // Local Dev IP
|
||||
API_URL: 'http://192.168.50.27:3004', // Local Dev IP
|
||||
},
|
||||
prod: {
|
||||
API_URL: 'https://crmapi.ignosimoney.in', // Change this to your public IP/Domain
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useCallback, useContext } from 'react';
|
||||
import {
|
||||
View, Text, StyleSheet, SectionList, TouchableOpacity,
|
||||
RefreshControl, StatusBar, Alert, Linking
|
||||
View, Text, StyleSheet, FlatList, TouchableOpacity,
|
||||
RefreshControl, StatusBar, Alert, Linking, TextInput, ScrollView
|
||||
} from 'react-native';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { AuthContext } from '../context/AuthContext';
|
||||
|
|
@ -19,7 +19,7 @@ const TYPE_ICONS = {
|
|||
'VISIT_SCHEDULED': '📍',
|
||||
'VISIT_COMPLETED': '🏁',
|
||||
'NEGOTIATION': '🤝',
|
||||
'FOLLOWUP': '📅',
|
||||
'FOLLOWUP': '📌',
|
||||
'DEMO': '📽️',
|
||||
'QUOTE': '📝',
|
||||
};
|
||||
|
|
@ -39,41 +39,68 @@ const TYPE_COLORS = {
|
|||
'QUOTE': '#a855f7',
|
||||
};
|
||||
|
||||
const MANDATORY_TYPES = ['DEMO_COMPLETED', 'VISIT_COMPLETED', 'DEMO'];
|
||||
|
||||
const CATEGORIES = [
|
||||
{ key: 'today', label: "Today's Focus", color: '#16a34a', bg: '#dcfce7' },
|
||||
{ key: 'tomorrow', label: 'Tomorrow', color: '#d97706', bg: '#fef3c7' },
|
||||
{ key: 'thisWeek', label: 'Later This Week', color: '#2563eb', bg: '#dbeafe' },
|
||||
{ key: 'upcoming', label: 'Upcoming', color: '#475569', bg: '#f1f5f9' },
|
||||
{ key: 'expired', label: 'Expired Follow-ups', color: '#dc2626', bg: '#fee2e2' },
|
||||
];
|
||||
|
||||
const startOfDay = (d) => { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; };
|
||||
const endOfDay = (d) => { const x = new Date(d); x.setHours(23, 59, 59, 999); return x; };
|
||||
|
||||
const categorizeActivities = (followups) => {
|
||||
const now = new Date();
|
||||
const todayStart = startOfDay(now);
|
||||
const todayEnd = endOfDay(now);
|
||||
|
||||
const tomorrow = new Date(now);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const tomorrowStart = startOfDay(tomorrow);
|
||||
const tomorrowEnd = endOfDay(tomorrow);
|
||||
|
||||
// End of current ISO week (Sunday)
|
||||
const weekEnd = new Date(todayStart);
|
||||
const day = weekEnd.getDay(); // 0=Sun..6=Sat
|
||||
const daysToSunday = day === 0 ? 0 : 7 - day;
|
||||
weekEnd.setDate(weekEnd.getDate() + daysToSunday);
|
||||
weekEnd.setHours(23, 59, 59, 999);
|
||||
|
||||
const result = { today: [], tomorrow: [], thisWeek: [], upcoming: [], expired: [] };
|
||||
|
||||
followups.forEach(f => {
|
||||
if (f.status === 'DONE' || f.status === 'EXPIRED') return;
|
||||
const d = new Date(f.date);
|
||||
if (d < todayStart) result.expired.push(f);
|
||||
else if (d <= todayEnd) result.today.push(f);
|
||||
else if (d <= tomorrowEnd) result.tomorrow.push(f);
|
||||
else if (d <= weekEnd) result.thisWeek.push(f);
|
||||
else result.upcoming.push(f);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const TasksScreen = ({ navigation }) => {
|
||||
const { userInfo } = useContext(AuthContext);
|
||||
const insets = useSafeAreaInsets();
|
||||
const [sections, setSections] = useState([]);
|
||||
|
||||
const [allActivities, setAllActivities] = useState([]);
|
||||
const [categorized, setCategorized] = useState({ today: [], tomorrow: [], thisWeek: [], upcoming: [], expired: [] });
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [activeFilter, setActiveFilter] = useState('PENDING'); // ALL, PENDING, DONE
|
||||
|
||||
const groupByDay = (followups) => {
|
||||
const map = {};
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const tomorrow = new Date(today.getTime() + 86400000);
|
||||
|
||||
followups.forEach(f => {
|
||||
const d = new Date(f.date);
|
||||
d.setHours(0, 0, 0, 0);
|
||||
let label;
|
||||
if (d.getTime() === today.getTime()) label = 'Today';
|
||||
else if (d.getTime() < today.getTime()) label = `Overdue — ${d.toLocaleDateString('en-IN', { day: 'numeric', month: 'short' })}`;
|
||||
else if (d.getTime() === tomorrow.getTime()) label = 'Tomorrow';
|
||||
else label = d.toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'short' });
|
||||
|
||||
if (!map[label]) map[label] = { title: label, data: [], ts: d.getTime() };
|
||||
map[label].data.push(f);
|
||||
});
|
||||
|
||||
return Object.values(map).sort((a, b) => a.ts - b.ts);
|
||||
};
|
||||
const [activeCategory, setActiveCategory] = useState('today');
|
||||
const [inlineRemarks, setInlineRemarks] = useState({});
|
||||
const [submitting, setSubmitting] = useState(null); // holds activity id being submitted
|
||||
|
||||
const fetchTasks = async () => {
|
||||
try {
|
||||
const params = new URLSearchParams({ userId: userInfo.id });
|
||||
if (activeFilter !== 'ALL') params.append('status', activeFilter);
|
||||
const params = new URLSearchParams({ userId: userInfo.id, status: 'PENDING' });
|
||||
const res = await api.get(`/followups?${params.toString()}`);
|
||||
setSections(groupByDay(res.data));
|
||||
setAllActivities(res.data);
|
||||
setCategorized(categorizeActivities(res.data));
|
||||
} catch (e) {
|
||||
console.error('TasksScreen fetch error', e);
|
||||
} finally {
|
||||
|
|
@ -81,74 +108,111 @@ const TasksScreen = ({ navigation }) => {
|
|||
}
|
||||
};
|
||||
|
||||
useFocusEffect(useCallback(() => { fetchTasks(); }, [activeFilter]));
|
||||
|
||||
const handleMarkDone = async (item) => {
|
||||
const isMandatory = ['DEMO_COMPLETED', 'VISIT_COMPLETED', 'DEMO'].includes(item.type);
|
||||
|
||||
if (isMandatory) {
|
||||
navigation.navigate('Feedback', { activity: item });
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert('Complete Activity?', 'This will mark the activity as done and remove it from pending.', [
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Complete ✓', onPress: async () => {
|
||||
try {
|
||||
await api.patch(`/followups/${item.id}`, { status: 'DONE' });
|
||||
fetchTasks();
|
||||
} catch (e) {
|
||||
Alert.alert('Error', 'Could not update activity.');
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
};
|
||||
useFocusEffect(useCallback(() => { fetchTasks(); }, []));
|
||||
|
||||
const handleCall = (phone) => {
|
||||
if (!phone) return;
|
||||
Linking.openURL(`tel:${phone}`);
|
||||
};
|
||||
|
||||
const renderTask = ({ item }) => {
|
||||
const isPending = item.status === 'PENDING';
|
||||
const isOverdue = isPending && new Date(item.date) < new Date();
|
||||
const handleInlineMarkDone = async (item) => {
|
||||
if (MANDATORY_TYPES.includes(item.type)) {
|
||||
navigation.navigate('Feedback', { activity: item });
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(item.id);
|
||||
try {
|
||||
const payload = { status: 'DONE' };
|
||||
const remarks = inlineRemarks[item.id];
|
||||
if (remarks && remarks.trim()) payload.notes = remarks.trim();
|
||||
|
||||
await api.patch(`/followups/${item.id}`, payload);
|
||||
|
||||
setInlineRemarks(prev => {
|
||||
const updated = { ...prev };
|
||||
delete updated[item.id];
|
||||
return updated;
|
||||
});
|
||||
fetchTasks();
|
||||
} catch (e) {
|
||||
Alert.alert('Error', 'Could not update activity.');
|
||||
} finally {
|
||||
setSubmitting(null);
|
||||
}
|
||||
};
|
||||
|
||||
const currentCat = CATEGORIES.find(c => c.key === activeCategory);
|
||||
const currentItems = categorized[activeCategory] || [];
|
||||
|
||||
const renderItem = ({ item }) => {
|
||||
const type = item.type || 'FOLLOWUP';
|
||||
|
||||
const isMandatory = MANDATORY_TYPES.includes(type);
|
||||
|
||||
return (
|
||||
<View style={[styles.card, isOverdue && styles.cardOverdue, !isPending && styles.cardDone]}>
|
||||
<View style={[styles.typeIconBadge, { backgroundColor: TYPE_COLORS[type] + '20' }]}>
|
||||
<Text style={styles.typeIconText}>{TYPE_ICONS[type]}</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={styles.clientName}>{item.client?.companyName || item.client?.name || 'Unknown Client'}</Text>
|
||||
{item.client?.phone && (
|
||||
<TouchableOpacity onPress={() => handleCall(item.client.phone)} style={styles.callCircle}>
|
||||
<Text style={styles.callIcon}>📞</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={[styles.card, { borderLeftColor: currentCat.color }]}>
|
||||
{/* Card Header */}
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={[styles.typeIconBadge, { backgroundColor: (TYPE_COLORS[type] || '#64748b') + '20' }]}>
|
||||
<Text style={styles.typeIconText}>{TYPE_ICONS[type] || '📌'}</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.clientName} numberOfLines={1}>
|
||||
{item.client?.companyName || item.client?.name || 'Unknown Client'}
|
||||
</Text>
|
||||
<View style={styles.typeBadgeRow}>
|
||||
<View style={[styles.typeBadge, { backgroundColor: (TYPE_COLORS[type] || '#64748b') + '15' }]}>
|
||||
<Text style={[styles.typeText, { color: TYPE_COLORS[type] || '#64748b' }]}>
|
||||
{type.replace(/_/g, ' ')}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.timeText}>
|
||||
{new Date(item.date).toLocaleDateString('en-IN', { day: 'numeric', month: 'short' })}
|
||||
{' '}
|
||||
{new Date(item.date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</Text>
|
||||
</View>
|
||||
{item.opportunity && (
|
||||
<Text style={styles.oppText} numberOfLines={1}>💼 {item.opportunity.title}</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.typeBadge}>
|
||||
<Text style={[styles.typeText, { color: TYPE_COLORS[type] || '#64748b' }]}>{type.replace('_', ' ')}</Text>
|
||||
</View>
|
||||
<Text style={styles.notes} numberOfLines={2}>{item.notes}</Text>
|
||||
<Text style={styles.time}>
|
||||
{new Date(item.date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
{item.user?.name ? ` • Assigned by ${item.user.name}` : ''}
|
||||
</Text>
|
||||
{item.client?.phone && (
|
||||
<TouchableOpacity onPress={() => handleCall(item.client.phone)} style={styles.callCircle}>
|
||||
<Text style={styles.callIcon}>📞</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
{isPending && (
|
||||
<TouchableOpacity style={[styles.doneBtn, { backgroundColor: TYPE_COLORS[type] }]} onPress={() => handleMarkDone(item.id)}>
|
||||
<Text style={styles.doneBtnText}>Done</Text>
|
||||
|
||||
{/* Notes */}
|
||||
{!!item.notes && (
|
||||
<Text style={styles.notes} numberOfLines={2}>"{item.notes}"</Text>
|
||||
)}
|
||||
|
||||
{/* Assigned user */}
|
||||
<Text style={styles.assignedText}>
|
||||
👤 {item.user?.name || 'Unassigned'}
|
||||
</Text>
|
||||
|
||||
{/* Inline Remarks + Mark Done */}
|
||||
<View style={styles.inlineRow}>
|
||||
<TextInput
|
||||
style={[styles.remarksInput, isMandatory && styles.remarksInputDisabled]}
|
||||
placeholder={isMandatory ? 'Needs feedback modal →' : 'Add remarks...'}
|
||||
placeholderTextColor={isMandatory ? '#cbd5e1' : '#94a3b8'}
|
||||
value={inlineRemarks[item.id] || ''}
|
||||
onChangeText={v => setInlineRemarks(prev => ({ ...prev, [item.id]: v }))}
|
||||
editable={!isMandatory}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={[styles.doneBtn, { backgroundColor: currentCat.color }, submitting === item.id && { opacity: 0.6 }]}
|
||||
onPress={() => handleInlineMarkDone(item)}
|
||||
disabled={submitting === item.id}
|
||||
>
|
||||
<Text style={styles.doneBtnText}>
|
||||
{isMandatory ? '📋 Open' : submitting === item.id ? '...' : '✓ Done'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{!isPending && (
|
||||
<View style={styles.completedBadge}>
|
||||
<Text style={styles.completedText}>✓</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
@ -156,49 +220,88 @@ const TasksScreen = ({ navigation }) => {
|
|||
return (
|
||||
<View style={styles.container}>
|
||||
<StatusBar backgroundColor={Colors.primary} barStyle="light-content" />
|
||||
|
||||
{/* Header */}
|
||||
<View style={[styles.header, { paddingTop: insets.top + 16 }]}>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<View style={styles.headerTop}>
|
||||
<View>
|
||||
<Text style={styles.headerTitle}>Activities</Text>
|
||||
<Text style={styles.headerSub}>Manage your schedule</Text>
|
||||
<Text style={styles.headerSub}>Daily follow-up engine</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={styles.addBtn}
|
||||
onPress={() => navigation.navigate('LogActivity', { tab: 'followup' })}
|
||||
>
|
||||
<Text style={styles.addBtnText}>+ New</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.filterRow}>
|
||||
{['PENDING', 'DONE', 'ALL'].map(f => (
|
||||
<TouchableOpacity
|
||||
key={f}
|
||||
style={[styles.filterBtn, activeFilter === f && styles.filterBtnActive]}
|
||||
onPress={() => setActiveFilter(f)}
|
||||
>
|
||||
<Text style={[styles.filterText, activeFilter === f && styles.filterTextActive]}>{f}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* Category Picker */}
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={styles.categoryScroll}
|
||||
contentContainerStyle={styles.categoryScrollContent}
|
||||
>
|
||||
{CATEGORIES.map(cat => {
|
||||
const count = categorized[cat.key]?.length || 0;
|
||||
const isActive = activeCategory === cat.key;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={cat.key}
|
||||
style={[
|
||||
styles.catBtn,
|
||||
isActive && { backgroundColor: 'white' },
|
||||
]}
|
||||
onPress={() => setActiveCategory(cat.key)}
|
||||
>
|
||||
<Text style={[styles.catBtnText, isActive && { color: cat.color }]}>
|
||||
{cat.label}
|
||||
</Text>
|
||||
<View style={[styles.catCount, { backgroundColor: isActive ? cat.color : 'rgba(255,255,255,0.25)' }]}>
|
||||
<Text style={styles.catCountText}>{count}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
<SectionList
|
||||
sections={sections}
|
||||
{/* Section Header */}
|
||||
<View style={[styles.sectionBanner, { backgroundColor: currentCat.bg, borderLeftColor: currentCat.color }]}>
|
||||
<Text style={[styles.sectionBannerTitle, { color: currentCat.color }]}>
|
||||
{currentCat.label}
|
||||
</Text>
|
||||
<Text style={[styles.sectionBannerCount, { color: currentCat.color }]}>
|
||||
{currentItems.length} {currentItems.length === 1 ? 'activity' : 'activities'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={currentItems}
|
||||
keyExtractor={item => item.id}
|
||||
renderItem={renderTask}
|
||||
renderSectionHeader={({ section }) => (
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>{section.title}</Text>
|
||||
<Text style={styles.sectionCount}>{section.data.length} item{section.data.length !== 1 ? 's' : ''}</Text>
|
||||
</View>
|
||||
)}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={() => { setRefreshing(true); fetchTasks(); }} colors={[Colors.primary]} />}
|
||||
contentContainerStyle={{ paddingBottom: 40 }}
|
||||
renderItem={renderItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={() => { setRefreshing(true); fetchTasks(); }}
|
||||
colors={[Colors.primary]}
|
||||
/>
|
||||
}
|
||||
contentContainerStyle={{ paddingHorizontal: 16, paddingTop: 12, paddingBottom: 40 }}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.empty}>
|
||||
<Text style={styles.emptyIcon}>✨</Text>
|
||||
<Text style={styles.emptyTitle}>All Caught Up!</Text>
|
||||
<Text style={styles.emptySub}>No activities scheduled here.</Text>
|
||||
<Text style={styles.emptyIcon}>
|
||||
{activeCategory === 'expired' ? '🎉' : '✨'}
|
||||
</Text>
|
||||
<Text style={styles.emptyTitle}>
|
||||
{activeCategory === 'expired' ? 'No expired follow-ups!' : 'All clear here!'}
|
||||
</Text>
|
||||
<Text style={styles.emptySub}>
|
||||
{activeCategory === 'expired'
|
||||
? 'You\'re on top of everything.'
|
||||
: 'No activities in this category.'}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
|
|
@ -208,39 +311,82 @@ const TasksScreen = ({ navigation }) => {
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#f8fafc' },
|
||||
header: { backgroundColor: Colors.primary, paddingHorizontal: 20, paddingBottom: 20 },
|
||||
headerTitle: { color: 'white', fontSize: 28, fontWeight: '900' },
|
||||
headerSub: { color: 'rgba(255,255,255,0.7)', fontSize: 13, marginTop: 2, marginBottom: 16 },
|
||||
filterRow: { flexDirection: 'row', gap: 8 },
|
||||
filterBtn: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.2)' },
|
||||
filterBtnActive: { backgroundColor: 'white' },
|
||||
filterText: { color: 'rgba(255,255,255,0.8)', fontSize: 12, fontWeight: '800' },
|
||||
filterTextActive: { color: Colors.primary },
|
||||
addBtn: { backgroundColor: 'rgba(255,255,255,0.25)', paddingHorizontal: 14, paddingVertical: 8, borderRadius: 12 },
|
||||
|
||||
// Header
|
||||
header: { backgroundColor: Colors.primary, paddingHorizontal: 20, paddingBottom: 12 },
|
||||
headerTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 },
|
||||
headerTitle: { color: 'white', fontSize: 26, fontWeight: '900' },
|
||||
headerSub: { color: 'rgba(255,255,255,0.7)', fontSize: 12, marginTop: 2 },
|
||||
addBtn: { backgroundColor: 'rgba(255,255,255,0.2)', paddingHorizontal: 14, paddingVertical: 8, borderRadius: 12 },
|
||||
addBtnText: { color: 'white', fontWeight: '900', fontSize: 13 },
|
||||
sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingTop: 24, paddingBottom: 10 },
|
||||
sectionTitle: { fontSize: 12, fontWeight: '900', color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 },
|
||||
sectionCount: { fontSize: 11, color: '#94a3b8', fontWeight: '700' },
|
||||
card: { backgroundColor: 'white', marginHorizontal: 16, marginBottom: 10, borderRadius: 18, padding: 16, flexDirection: 'row', alignItems: 'center', elevation: 3, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 8 },
|
||||
cardOverdue: { borderLeftWidth: 5, borderLeftColor: '#ef4444' },
|
||||
cardDone: { opacity: 0.7 },
|
||||
typeIconBadge: { width: 44, height: 44, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginRight: 14 },
|
||||
typeIconText: { fontSize: 20 },
|
||||
clientName: { fontSize: 15, fontWeight: '800', color: '#1e293b', marginBottom: 2, flex: 1 },
|
||||
callCircle: { width: 32, height: 32, borderRadius: 16, backgroundColor: '#f1f5f9', alignItems: 'center', justifyContent: 'center' },
|
||||
callIcon: { fontSize: 14 },
|
||||
typeBadge: { alignSelf: 'flex-start', paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6, backgroundColor: '#f8fafc', marginBottom: 6 },
|
||||
|
||||
// Category picker
|
||||
categoryScroll: { marginBottom: 4 },
|
||||
categoryScrollContent: { gap: 8, paddingRight: 4 },
|
||||
catBtn: {
|
||||
flexDirection: 'row', alignItems: 'center', gap: 6,
|
||||
paddingHorizontal: 12, paddingVertical: 7,
|
||||
borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.18)',
|
||||
},
|
||||
catBtnText: { fontSize: 11, fontWeight: '800', color: 'rgba(255,255,255,0.85)' },
|
||||
catCount: { minWidth: 18, height: 18, borderRadius: 9, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 4 },
|
||||
catCountText: { color: 'white', fontSize: 10, fontWeight: '900' },
|
||||
|
||||
// Section Banner
|
||||
sectionBanner: {
|
||||
flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center',
|
||||
marginHorizontal: 16, marginTop: 12, marginBottom: 4,
|
||||
paddingHorizontal: 14, paddingVertical: 10,
|
||||
borderRadius: 10, borderLeftWidth: 4,
|
||||
},
|
||||
sectionBannerTitle: { fontSize: 12, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 0.8 },
|
||||
sectionBannerCount: { fontSize: 11, fontWeight: '700' },
|
||||
|
||||
// Cards
|
||||
card: {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 14, marginBottom: 12,
|
||||
padding: 14,
|
||||
borderLeftWidth: 4,
|
||||
elevation: 2,
|
||||
shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.06, shadowRadius: 6,
|
||||
},
|
||||
cardHeader: { flexDirection: 'row', alignItems: 'flex-start', gap: 10, marginBottom: 8 },
|
||||
typeIconBadge: { width: 40, height: 40, borderRadius: 12, alignItems: 'center', justifyContent: 'center', marginTop: 2 },
|
||||
typeIconText: { fontSize: 18 },
|
||||
clientName: { fontSize: 14, fontWeight: '800', color: '#1e293b', marginBottom: 4 },
|
||||
typeBadgeRow: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
||||
typeBadge: { paddingHorizontal: 7, paddingVertical: 2, borderRadius: 6 },
|
||||
typeText: { fontSize: 10, fontWeight: '900', textTransform: 'uppercase' },
|
||||
notes: { fontSize: 13, color: '#475569', lineHeight: 18, marginBottom: 8 },
|
||||
time: { fontSize: 11, color: '#94a3b8', fontWeight: '600' },
|
||||
doneBtn: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 12, marginLeft: 12 },
|
||||
doneBtnText: { color: 'white', fontSize: 12, fontWeight: '900' },
|
||||
completedBadge: { width: 32, height: 32, borderRadius: 16, backgroundColor: '#dcfce7', justifyContent: 'center', alignItems: 'center', marginLeft: 12 },
|
||||
completedText: { color: '#16a34a', fontWeight: '900', fontSize: 16 },
|
||||
empty: { alignItems: 'center', paddingTop: 100 },
|
||||
emptyIcon: { fontSize: 56, marginBottom: 16 },
|
||||
emptyTitle: { fontSize: 20, fontWeight: '900', color: '#1e293b' },
|
||||
emptySub: { fontSize: 14, color: '#64748b', marginTop: 8 },
|
||||
timeText: { fontSize: 10, color: '#94a3b8', fontWeight: '600' },
|
||||
oppText: { fontSize: 11, color: '#64748b', marginTop: 4, fontStyle: 'italic' },
|
||||
callCircle: { width: 34, height: 34, borderRadius: 17, backgroundColor: '#f1f5f9', alignItems: 'center', justifyContent: 'center', marginLeft: 4 },
|
||||
callIcon: { fontSize: 15 },
|
||||
|
||||
notes: { fontSize: 12, color: '#475569', fontStyle: 'italic', marginBottom: 6, lineHeight: 17 },
|
||||
assignedText: { fontSize: 11, color: '#94a3b8', fontWeight: '600', marginBottom: 10 },
|
||||
|
||||
// Inline action row
|
||||
inlineRow: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
||||
remarksInput: {
|
||||
flex: 1, height: 36,
|
||||
borderWidth: 1, borderColor: '#e2e8f0',
|
||||
borderRadius: 8, paddingHorizontal: 10,
|
||||
fontSize: 12, color: '#1e293b',
|
||||
backgroundColor: '#f8fafc',
|
||||
},
|
||||
remarksInputDisabled: { backgroundColor: '#f1f5f9', color: '#94a3b8' },
|
||||
doneBtn: {
|
||||
paddingHorizontal: 12, paddingVertical: 8,
|
||||
borderRadius: 8,
|
||||
},
|
||||
doneBtnText: { color: 'white', fontSize: 11, fontWeight: '900' },
|
||||
|
||||
// Empty
|
||||
empty: { alignItems: 'center', paddingTop: 80 },
|
||||
emptyIcon: { fontSize: 52, marginBottom: 14 },
|
||||
emptyTitle: { fontSize: 18, fontWeight: '900', color: '#1e293b' },
|
||||
emptySub: { fontSize: 13, color: '#64748b', marginTop: 6, textAlign: 'center' },
|
||||
});
|
||||
|
||||
export default TasksScreen;
|
||||
|
|
|
|||
Loading…
Reference in New Issue