update till 22/05/2026

update till 22/05/2026
main
Manu Krishna 2026-05-22 14:47:03 +05:30
parent de97592ded
commit 69f1ee64fb
9 changed files with 332 additions and 26 deletions

View File

@ -1,7 +1,7 @@
// Environment Configuration // Environment Configuration
const ENV = { const ENV = {
dev: { dev: {
API_URL: 'http://192.168.29.100:3000', // Local Dev IP API_URL: 'http://192.168.65.3:3000', // Local Dev IP
}, },
prod: { prod: {
API_URL: 'https://crmapi.ignosimoney.in', // Change this to your public IP/Domain API_URL: 'https://crmapi.ignosimoney.in', // Change this to your public IP/Domain

View File

@ -24,6 +24,7 @@ import MyTargetScreen from '../screens/MyTargetScreen';
import TasksScreen from '../screens/TasksScreen'; import TasksScreen from '../screens/TasksScreen';
import CallLogsScreen from '../screens/CallLogsScreen'; import CallLogsScreen from '../screens/CallLogsScreen';
import ChangePasswordScreen from '../screens/ChangePasswordScreen'; import ChangePasswordScreen from '../screens/ChangePasswordScreen';
import FeedbackScreen from '../screens/FeedbackScreen';
const Stack = createNativeStackNavigator(); const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator(); const Tab = createBottomTabNavigator();
@ -84,6 +85,7 @@ const AppNav = () => {
<Stack.Screen name="TasksDetail" component={TasksScreen} options={{ headerShown: false }} /> <Stack.Screen name="TasksDetail" component={TasksScreen} options={{ headerShown: false }} />
<Stack.Screen name="CallLogs" component={CallLogsScreen} options={{ headerShown: false }} /> <Stack.Screen name="CallLogs" component={CallLogsScreen} options={{ headerShown: false }} />
<Stack.Screen name="ChangePassword" component={ChangePasswordScreen} options={{ headerShown: false }} /> <Stack.Screen name="ChangePassword" component={ChangePasswordScreen} options={{ headerShown: false }} />
<Stack.Screen name="Feedback" component={FeedbackScreen} options={{ headerShown: false }} />
</> </>
) : ( ) : (
<Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} /> <Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} />

View File

@ -10,10 +10,10 @@ import { AuthContext } from '../context/AuthContext';
import Colors from '../constants/Colors'; import Colors from '../constants/Colors';
const STAGES = [ const STAGES = [
{ id: 'NEW', label: 'New', color: '#6366f1' }, { id: 'LEAD', label: 'Lead', color: '#6366f1' },
{ id: 'QUALIFIED', label: 'Qualified', color: '#3b82f6' }, { id: 'QUALIFIED', label: 'Qualified', color: '#3b82f6' },
{ id: 'PROPOSITION', label: 'Proposition', color: '#f59e0b' }, { id: 'POTENTIAL', label: 'Potential', color: '#f59e0b' },
{ id: 'WON', label: 'Won', color: '#10b981' }, { id: 'SALES', label: 'Sales', color: '#10b981' },
{ id: 'LOST', label: 'Lost', color: '#ef4444' }, { id: 'LOST', label: 'Lost', color: '#ef4444' },
]; ];
@ -37,7 +37,7 @@ const AddOpportunityScreen = ({ navigation, route }) => {
const [form, setForm] = useState({ const [form, setForm] = useState({
title: '', title: '',
value: '', value: '',
stage: 'NEW', stage: 'LEAD',
expectedClosingDate: '', expectedClosingDate: '',
notes: '', notes: '',
}); });
@ -102,7 +102,7 @@ const AddOpportunityScreen = ({ navigation, route }) => {
notes: form.notes || null, notes: form.notes || null,
}); });
Alert.alert('✅ Deal Created!', `"${form.title}" has been added to your pipeline.`, [ Alert.alert('✅ Deal Created!', `"${form.title}" has been added to your pipeline.`, [
{ text: 'Add Another', onPress: () => { setForm({ title: '', value: '', stage: 'NEW', expectedClosingDate: '', notes: '' }); setSelectedClient(null); } }, { text: 'Add Another', onPress: () => { setForm({ title: '', value: '', stage: 'LEAD', expectedClosingDate: '', notes: '' }); setSelectedClient(null); } },
{ text: 'View Pipeline', onPress: () => navigation.navigate('Pipeline') }, { text: 'View Pipeline', onPress: () => navigation.navigate('Pipeline') },
]); ]);
} catch (e) { } catch (e) {

View File

@ -72,7 +72,7 @@ const CallLogsScreen = ({ navigation }) => {
); );
const STATUS_MAP = { const STATUS_MAP = {
QUALITY: { label: 'Quality Lead', color: '#16a34a', bg: '#dcfce7' }, QUALIFIED: { label: 'Qualified Lead', color: '#16a34a', bg: '#dcfce7' },
POTENTIAL: { label: 'Potential', color: '#eab308', bg: '#fef9c3' }, POTENTIAL: { label: 'Potential', color: '#eab308', bg: '#fef9c3' },
DEMO: { label: 'Demo', color: '#a855f7', bg: '#f3e8ff' }, DEMO: { label: 'Demo', color: '#a855f7', bg: '#f3e8ff' },
SALES: { label: 'Sales', color: '#0ea5e9', bg: '#e0f2fe' }, SALES: { label: 'Sales', color: '#0ea5e9', bg: '#e0f2fe' },

View File

@ -1,8 +1,10 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState, useContext, useEffect } from 'react';
import { View, Text, TextInput, Button, StyleSheet, Alert, ScrollView } from 'react-native'; import { View, Text, TextInput, Button, StyleSheet, Alert, ScrollView, TouchableOpacity, ActivityIndicator, Image, Platform } from 'react-native';
import { pick } from '@react-native-documents/picker';
import api from '../services/api'; import api from '../services/api';
import { AuthContext } from '../context/AuthContext'; import { AuthContext } from '../context/AuthContext';
import Colors from '../constants/Colors'; import Colors from '../constants/Colors';
import { Camera, Image as ImageIcon, X } from 'lucide-react-native';
const ExpenseScreen = ({ navigation }) => { const ExpenseScreen = ({ navigation }) => {
const { userInfo } = useContext(AuthContext); const { userInfo } = useContext(AuthContext);
@ -11,6 +13,8 @@ const ExpenseScreen = ({ navigation }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [expenses, setExpenses] = useState([]); const [expenses, setExpenses] = useState([]);
const [fetching, setFetching] = useState(true); const [fetching, setFetching] = useState(true);
const [selectedImage, setSelectedImage] = useState(null);
const [uploading, setUploading] = useState(false);
useEffect(() => { useEffect(() => {
fetchExpenses(); fetchExpenses();
@ -28,6 +32,40 @@ const ExpenseScreen = ({ navigation }) => {
} }
}; };
const handlePickImage = async () => {
try {
const results = await pick({
multiple: false,
});
if (results && results.length > 0) {
setSelectedImage(results[0]);
}
} catch (err) {
console.error(err);
}
};
const uploadImage = async () => {
if (!selectedImage) return null;
const formData = new FormData();
formData.append('file', {
uri: Platform.OS === 'ios' ? selectedImage.uri.replace('file://', '') : selectedImage.uri,
type: selectedImage.type || 'image/jpeg',
name: selectedImage.name || 'bill.jpg',
});
try {
const response = await api.post('/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return response.data.url;
} catch (error) {
console.error('Upload failed', error);
throw new Error('Failed to upload bill image');
}
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (!amount || !description) { if (!amount || !description) {
Alert.alert("Error", "Amount and Description are required"); Alert.alert("Error", "Amount and Description are required");
@ -42,21 +80,31 @@ const ExpenseScreen = ({ navigation }) => {
setLoading(true); setLoading(true);
try { try {
let imageUrl = null;
if (selectedImage) {
setUploading(true);
imageUrl = await uploadImage();
setUploading(false);
}
await api.post('/expenses', { await api.post('/expenses', {
userId: userInfo.id, userId: userInfo.id,
amount: parsedAmount, amount: parsedAmount,
description, description,
imageUrl,
status: 'PENDING' status: 'PENDING'
}); });
Alert.alert("Success", "Expense Submitted Successfully"); Alert.alert("Success", "Expense Submitted Successfully");
setAmount(''); setAmount('');
setDescription(''); setDescription('');
setSelectedImage(null);
fetchExpenses(); fetchExpenses();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
Alert.alert("Error", "Failed to submit expense"); Alert.alert("Error", error.message || "Failed to submit expense");
} finally { } finally {
setLoading(false); setLoading(false);
setUploading(false);
} }
}; };
@ -84,7 +132,38 @@ const ExpenseScreen = ({ navigation }) => {
placeholder="Lunch, Travel, etc." placeholder="Lunch, Travel, etc."
/> />
<Button title={loading ? "Submitting..." : "Submit Expense"} onPress={handleSubmit} disabled={loading} color={Colors.primary} /> <Text style={styles.label}>Attach Bill (Optional)</Text>
<TouchableOpacity
style={[styles.imagePicker, selectedImage && styles.imagePickerSelected]}
onPress={handlePickImage}
>
{selectedImage ? (
<View style={styles.selectedImageInfo}>
<ImageIcon size={20} color={Colors.primary} />
<Text style={styles.imageName} numberOfLines={1}>{selectedImage.name}</Text>
<TouchableOpacity onPress={() => setSelectedImage(null)} style={styles.removeBtn}>
<X size={16} color="#ef4444" />
</TouchableOpacity>
</View>
) : (
<View style={styles.pickerPlaceholder}>
<Camera size={24} color="#94a3b8" />
<Text style={styles.pickerText}>Tap to add bill image</Text>
</View>
)}
</TouchableOpacity>
<TouchableOpacity
style={[styles.submitBtn, (loading || uploading) && styles.disabledBtn]}
onPress={handleSubmit}
disabled={loading || uploading}
>
{loading || uploading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.submitBtnText}>Submit Expense</Text>
)}
</TouchableOpacity>
</View> </View>
<Text style={styles.subTitle}>My Expenses</Text> <Text style={styles.subTitle}>My Expenses</Text>
@ -129,7 +208,64 @@ const styles = StyleSheet.create({
status: { fontSize: 12, fontWeight: 'bold', paddingHorizontal: 8, paddingVertical: 2, borderRadius: 4, overflow: 'hidden' }, status: { fontSize: 12, fontWeight: 'bold', paddingHorizontal: 8, paddingVertical: 2, borderRadius: 4, overflow: 'hidden' },
approved: { backgroundColor: Colors.accent, color: Colors.secondary }, approved: { backgroundColor: Colors.accent, color: Colors.secondary },
rejected: { backgroundColor: Colors.backgroundSecondary, color: Colors.textMuted }, rejected: { backgroundColor: Colors.backgroundSecondary, color: Colors.textMuted },
pending: { backgroundColor: Colors.backgroundSecondary, color: Colors.textMuted } pending: { backgroundColor: Colors.backgroundSecondary, color: Colors.textMuted },
imagePicker: {
borderWidth: 1.5,
borderColor: '#e2e8f0',
borderStyle: 'dashed',
borderRadius: 12,
padding: 15,
marginBottom: 20,
backgroundColor: '#f8fafc',
alignItems: 'center',
justifyContent: 'center',
},
imagePickerSelected: {
borderStyle: 'solid',
borderColor: Colors.primary + '40',
backgroundColor: Colors.primary + '05',
},
pickerPlaceholder: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
},
pickerText: {
color: '#94a3b8',
fontSize: 14,
fontWeight: '500',
},
selectedImageInfo: {
flexDirection: 'row',
alignItems: 'center',
width: '100%',
},
imageName: {
flex: 1,
marginLeft: 10,
fontSize: 14,
color: '#1e293b',
fontWeight: '600',
},
removeBtn: {
padding: 5,
},
submitBtn: {
backgroundColor: Colors.primary,
padding: 15,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
elevation: 2,
},
submitBtnText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
disabledBtn: {
opacity: 0.6,
}
}); });
export default ExpenseScreen; export default ExpenseScreen;

View File

@ -0,0 +1,140 @@
import React, { useState } from 'react';
import {
View, Text, StyleSheet, ScrollView, TextInput,
TouchableOpacity, ActivityIndicator, Alert, SafeAreaView
} from 'react-native';
import api from '../services/api';
import Colors from '../constants/Colors';
const FeedbackScreen = ({ navigation, route }) => {
const { activity } = route.params;
const [loading, setLoading] = useState(false);
const [feedback, setFeedback] = useState({
customerFeedback: '',
requirementDetails: '',
suggestions: '',
budget: '',
expectedClosingTimeline: '',
competitorInfo: '',
staffRemarks: '',
customerCommitments: '',
caCsDetails: '',
demoPersonName: '',
demoContactDetails: ''
});
const handleSubmit = async () => {
const requiredFields = [
'customerFeedback', 'requirementDetails', 'budget',
'expectedClosingTimeline', 'competitorInfo', 'staffRemarks',
'customerCommitments', 'caCsDetails'
];
const missing = requiredFields.filter(f => !feedback[f]);
if (missing.length > 0) {
Alert.alert('Incomplete Form', 'Please fill all mandatory fields to complete this activity.');
return;
}
setLoading(true);
try {
await api.patch(`/followups/${activity.id}`, {
status: 'DONE',
...feedback
});
Alert.alert('Success ✅', 'Activity marked as completed with feedback.', [
{ text: 'OK', onPress: () => navigation.goBack() }
]);
} catch (e) {
Alert.alert('Error', 'Failed to save feedback.');
} finally {
setLoading(false);
}
};
const InputField = ({ label, value, keyName, multiline = false, placeholder = '' }) => (
<View style={styles.inputGroup}>
<Text style={styles.label}>{label} *</Text>
<TextInput
style={[styles.input, multiline && styles.textArea]}
value={value}
onChangeText={(text) => setFeedback({ ...feedback, [keyName]: text })}
multiline={multiline}
placeholder={placeholder}
placeholderTextColor="#94a3b8"
/>
</View>
);
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backBtn}>
<Text style={styles.backBtnText}></Text>
</TouchableOpacity>
<Text style={styles.headerTitle}>Mandatory Feedback</Text>
<View style={{ width: 40 }} />
</View>
<ScrollView contentContainerStyle={styles.body} keyboardShouldPersistTaps="handled">
<View style={styles.infoBox}>
<Text style={styles.infoTitle}>{activity.type.replace('_', ' ')}</Text>
<Text style={styles.infoSub}>{activity.client?.companyName || activity.client?.name}</Text>
</View>
<InputField label="Customer Feedback" value={feedback.customerFeedback} keyName="customerFeedback" multiline placeholder="Reaction/Feedback from customer" />
<InputField label="Requirement Details" value={feedback.requirementDetails} keyName="requirementDetails" multiline placeholder="Specific needs identified" />
<View style={{ flexDirection: 'row', gap: 12 }}>
<View style={{ flex: 1 }}>
<InputField label="Budget" value={feedback.budget} keyName="budget" placeholder="e.g. 5L" />
</View>
<View style={{ flex: 1 }}>
<InputField label="Exp. Timeline" value={feedback.expectedClosingTimeline} keyName="expectedClosingTimeline" placeholder="e.g. 1 month" />
</View>
</View>
<InputField label="Competitor Info" value={feedback.competitorInfo} keyName="competitorInfo" placeholder="Other vendors considered" />
<InputField label="Staff Remarks" value={feedback.staffRemarks} keyName="staffRemarks" multiline placeholder="Internal observations" />
<InputField label="Customer Commitments" value={feedback.customerCommitments} keyName="customerCommitments" multiline placeholder="What did they promise?" />
<InputField label="CA / CS Details" value={feedback.caCsDetails} keyName="caCsDetails" placeholder="Chartered Accountant info" />
<View style={styles.divider} />
<Text style={styles.sectionHeader}>Initial Demo Info (Optional)</Text>
<InputField label="Person Met" value={feedback.demoPersonName} keyName="demoPersonName" placeholder="Name and Role" />
<InputField label="Contact Details" value={feedback.demoContactDetails} keyName="demoContactDetails" placeholder="Phone/Email" />
<TouchableOpacity
style={[styles.submitBtn, loading && { opacity: 0.7 }]}
onPress={handleSubmit}
disabled={loading}
>
{loading ? <ActivityIndicator color="white" /> : <Text style={styles.submitBtnText}>Complete Activity</Text>}
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f8fafc' },
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: 20, backgroundColor: Colors.primary },
headerTitle: { color: 'white', fontSize: 18, fontWeight: '900' },
backBtn: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' },
backBtnText: { color: 'white', fontSize: 20, fontWeight: 'bold' },
body: { padding: 20, paddingBottom: 60 },
infoBox: { backgroundColor: 'white', padding: 16, borderRadius: 16, marginBottom: 24, borderLeftWidth: 4, borderLeftColor: Colors.primary, elevation: 2 },
infoTitle: { fontSize: 12, fontWeight: '900', color: Colors.primary, textTransform: 'uppercase', letterSpacing: 1 },
infoSub: { fontSize: 16, fontWeight: '700', color: '#1e293b', marginTop: 4 },
inputGroup: { marginBottom: 20 },
label: { fontSize: 11, fontWeight: '900', color: '#64748b', textTransform: 'uppercase', marginBottom: 8, marginLeft: 4 },
input: { backgroundColor: 'white', borderRadius: 12, borderWidth: 1.5, borderColor: '#e2e8f0', padding: 14, fontSize: 15, color: '#1e293b' },
textArea: { minHeight: 80, textAlignVertical: 'top' },
divider: { height: 1, backgroundColor: '#e2e8f0', marginVertical: 20 },
sectionHeader: { fontSize: 12, fontWeight: '800', color: '#94a3b8', marginBottom: 20 },
submitBtn: { backgroundColor: Colors.primary, borderRadius: 14, padding: 18, alignItems: 'center', marginTop: 20, elevation: 4 },
submitBtnText: { color: 'white', fontSize: 16, fontWeight: '900' }
});
export default FeedbackScreen;

View File

@ -18,10 +18,16 @@ const STRATEGIC_TYPES = [
]; ];
const SCHEDULE_TYPES = [ const SCHEDULE_TYPES = [
{ id: 'FOLLOWUP', label: 'Follow-up', icon: '📅', color: '#6366f1' }, { id: 'CALL', label: 'Call', icon: '📞', color: '#10b981' },
{ id: 'DEMO', label: 'Demo', icon: '📽️', color: '#3b82f6' }, { id: 'MESSAGE', label: 'Message', icon: '💬', color: '#06b6d4' },
{ id: 'QUOTE', label: 'Quote', icon: '📝', color: '#a855f7' }, { id: 'DEMO_SCHEDULED', label: 'Demo Sch', icon: '📅', color: '#3b82f6' },
{ id: 'DEMO_COMPLETED', label: 'Demo Done', icon: '✅', color: '#10b981' },
{ id: 'QUOTE_REQUEST', label: 'Quote Req', icon: '📋', color: '#a855f7' },
{ id: 'QUOTE_SEND', label: 'Quote Send', icon: '📤', color: '#6366f1' },
{ id: 'VISIT_SCHEDULED', label: 'Visit Sch', icon: '📍', color: '#f97316' },
{ id: 'VISIT_COMPLETED', label: 'Visit Done', icon: '🏁', color: '#ef4444' },
{ id: 'NEGOTIATION', label: 'Negotiate', icon: '🤝', color: '#f59e0b' }, { id: 'NEGOTIATION', label: 'Negotiate', icon: '🤝', color: '#f59e0b' },
{ id: 'FOLLOWUP', label: 'Other', icon: '📅', color: '#64748b' },
]; ];
const TABS = [ const TABS = [
@ -55,7 +61,7 @@ const LogActivityScreen = ({ navigation, route }) => {
const STATUS_OPTIONS = [ const STATUS_OPTIONS = [
{ id: 'LEAD', label: 'Lead', color: '#6366f1', bg: '#eef2ff' }, { id: 'LEAD', label: 'Lead', color: '#6366f1', bg: '#eef2ff' },
{ id: 'QUALITY', label: 'Quality', color: '#16a34a', bg: '#dcfce7' }, { id: 'QUALIFIED', label: 'Qualified', color: '#16a34a', bg: '#dcfce7' },
{ id: 'POTENTIAL', label: 'Potential', color: '#eab308', bg: '#fef9c3' }, { id: 'POTENTIAL', label: 'Potential', color: '#eab308', bg: '#fef9c3' },
{ id: 'SALES', label: 'Sales', color: '#0ea5e9', bg: '#e0f2fe' }, { id: 'SALES', label: 'Sales', color: '#0ea5e9', bg: '#e0f2fe' },
{ id: 'CLOSED', label: 'Closed', color: '#ef4444', bg: '#fee2e2' } { id: 'CLOSED', label: 'Closed', color: '#ef4444', bg: '#fee2e2' }
@ -406,7 +412,7 @@ const LogActivityScreen = ({ navigation, route }) => {
))} ))}
</View> </View>
{fuType === 'QUOTE' ? ( {['QUOTE', 'QUOTE_REQUEST', 'QUOTE_SEND'].includes(fuType) ? (
<> <>
<Text style={styles.section}>Link to Opportunity *</Text> <Text style={styles.section}>Link to Opportunity *</Text>
<OpportunityPicker /> <OpportunityPicker />

View File

@ -19,9 +19,9 @@ const PipelineScreen = ({ navigation }) => {
const dealStages = [ const dealStages = [
{ id: 'LEAD', label: 'Lead' }, { id: 'LEAD', label: 'Lead' },
{ id: 'QUALIFIED', label: 'Qual' }, { id: 'QUALIFIED', label: 'Qualified' },
{ id: 'POTENTIAL', label: 'Poten' }, { id: 'POTENTIAL', label: 'Potential' },
{ id: 'WON', label: 'Won' }, { id: 'SALES', label: 'Sales' },
]; ];
const leadStages = [ const leadStages = [
@ -150,7 +150,6 @@ const PipelineScreen = ({ navigation }) => {
const renderItem = ({ item }) => { const renderItem = ({ item }) => {
const isLead = !!item.status; // Clients have status, opportunities have stages const isLead = !!item.status; // Clients have status, opportunities have stages
return ( return (
<TouchableOpacity <TouchableOpacity
style={styles.card} style={styles.card}

View File

@ -10,17 +10,33 @@ import api from '../services/api';
import Colors from '../constants/Colors'; import Colors from '../constants/Colors';
const TYPE_ICONS = { const TYPE_ICONS = {
'CALL': '📞',
'MESSAGE': '💬',
'DEMO_SCHEDULED': '📅',
'DEMO_COMPLETED': '✅',
'QUOTE_REQUEST': '📋',
'QUOTE_SEND': '📤',
'VISIT_SCHEDULED': '📍',
'VISIT_COMPLETED': '🏁',
'NEGOTIATION': '🤝',
'FOLLOWUP': '📅', 'FOLLOWUP': '📅',
'DEMO': '📽️', 'DEMO': '📽️',
'QUOTE': '📝', 'QUOTE': '📝',
'NEGOTIATION': '🤝',
}; };
const TYPE_COLORS = { const TYPE_COLORS = {
'FOLLOWUP': '#6366f1', 'CALL': '#10b981',
'MESSAGE': '#06b6d4',
'DEMO_SCHEDULED': '#3b82f6',
'DEMO_COMPLETED': '#10b981',
'QUOTE_REQUEST': '#a855f7',
'QUOTE_SEND': '#6366f1',
'VISIT_SCHEDULED': '#f97316',
'VISIT_COMPLETED': '#ef4444',
'NEGOTIATION': '#f59e0b',
'FOLLOWUP': '#64748b',
'DEMO': '#3b82f6', 'DEMO': '#3b82f6',
'QUOTE': '#a855f7', 'QUOTE': '#a855f7',
'NEGOTIATION': '#f59e0b',
}; };
const TasksScreen = ({ navigation }) => { const TasksScreen = ({ navigation }) => {
@ -67,13 +83,20 @@ const TasksScreen = ({ navigation }) => {
useFocusEffect(useCallback(() => { fetchTasks(); }, [activeFilter])); useFocusEffect(useCallback(() => { fetchTasks(); }, [activeFilter]));
const handleMarkDone = async (id) => { 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.', [ Alert.alert('Complete Activity?', 'This will mark the activity as done and remove it from pending.', [
{ text: 'Cancel', style: 'cancel' }, { text: 'Cancel', style: 'cancel' },
{ {
text: 'Complete ✓', onPress: async () => { text: 'Complete ✓', onPress: async () => {
try { try {
await api.patch(`/followups/${id}`, { status: 'DONE' }); await api.patch(`/followups/${item.id}`, { status: 'DONE' });
fetchTasks(); fetchTasks();
} catch (e) { } catch (e) {
Alert.alert('Error', 'Could not update activity.'); Alert.alert('Error', 'Could not update activity.');
@ -108,7 +131,7 @@ const TasksScreen = ({ navigation }) => {
)} )}
</View> </View>
<View style={styles.typeBadge}> <View style={styles.typeBadge}>
<Text style={[styles.typeText, { color: TYPE_COLORS[type] }]}>{type}</Text> <Text style={[styles.typeText, { color: TYPE_COLORS[type] || '#64748b' }]}>{type.replace('_', ' ')}</Text>
</View> </View>
<Text style={styles.notes} numberOfLines={2}>{item.notes}</Text> <Text style={styles.notes} numberOfLines={2}>{item.notes}</Text>
<Text style={styles.time}> <Text style={styles.time}>