From de97592ded7cd8c94f1ef7501b241bc889fedef2 Mon Sep 17 00:00:00 2001 From: Manu Date: Sat, 9 May 2026 15:22:21 +0530 Subject: [PATCH] changes till 09/05/2026 New clients creation from opportunities, client conversion%, time taken for conversion, close the modal when touched outside it Client and company name separate, Demo becomes a separate activity, all changes done in mobile app as well 2) transfer of clients, demos followups negotiation etc scheduling, quote opportunity in place of enquiry, In opportunity new product add, existing dropdown, added option for adding documents on client creation and showing it --- package-lock.json | 38 ++ package.json | 2 + src/navigation/AppNav.js | 8 +- src/screens/AddClientScreen.js | 242 ++++++--- src/screens/AddOpportunityScreen.js | 392 ++++++++++++++ src/screens/ClientDetailsScreen.js | 137 ++++- src/screens/ClientListScreen.js | 19 +- src/screens/EditClientScreen.js | 351 ++++++------ src/screens/HomeScreen.js | 6 +- src/screens/LogActivityScreen.js | 358 ++++++++++--- src/screens/PipelineScreen.js | 797 +++++++++++++--------------- src/screens/TasksScreen.js | 113 ++-- 12 files changed, 1676 insertions(+), 787 deletions(-) create mode 100644 src/screens/AddOpportunityScreen.js diff --git a/package-lock.json b/package-lock.json index 06c04c7..404dccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.1", "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-community/datetimepicker": "^9.1.0", + "@react-native-documents/picker": "^12.0.1", "@react-native/new-app-screen": "0.83.1", "@react-navigation/bottom-tabs": "^7.15.9", "@react-navigation/native": "^7.1.26", @@ -2976,6 +2978,42 @@ "node": ">=10" } }, + "node_modules/@react-native-community/datetimepicker": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-9.1.0.tgz", + "integrity": "sha512-eadbnk+I2vxvW30iTAsm/qlCnMMAadkifIMYNEB2lzhxN/SvlKc7S2V4k5DyrwjdCbqdcMk3t9K6fnUMcAV34w==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "expo": ">=52.0.0", + "react": "*", + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "react-native-windows": { + "optional": true + } + } + }, + "node_modules/@react-native-documents/picker": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-12.0.1.tgz", + "integrity": "sha512-vpJKb4t/5bnxe9+gQl+plJfKrrIsmYwANGhNH2B9E1dS1+6FDBzg4Dwmcq4ueaGfkRKEPJ606mJttVEH1ZKZaA==", + "license": "MIT", + "funding": { + "url": "https://github.com/react-native-documents/document-picker?sponsor=1" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.79.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.83.1", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.1.tgz", diff --git a/package.json b/package.json index 2f05667..cabeacf 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-community/datetimepicker": "^9.1.0", + "@react-native-documents/picker": "^12.0.1", "@react-native/new-app-screen": "0.83.1", "@react-navigation/bottom-tabs": "^7.15.9", "@react-navigation/native": "^7.1.26", diff --git a/src/navigation/AppNav.js b/src/navigation/AppNav.js index 32f9592..7769427 100644 --- a/src/navigation/AppNav.js +++ b/src/navigation/AppNav.js @@ -16,8 +16,7 @@ import ClientDetailsScreen from '../screens/ClientDetailsScreen'; import EditClientScreen from '../screens/EditClientScreen'; import PipelineScreen from '../screens/PipelineScreen'; -import EnquiryScreen from '../screens/EnquiryScreen'; -import EnquiryListScreen from '../screens/EnquiryListScreen'; +import AddOpportunityScreen from '../screens/AddOpportunityScreen'; import ExpenseScreen from '../screens/ExpenseScreen'; import IncentiveScreen from '../screens/IncentiveScreen'; import LogActivityScreen from '../screens/LogActivityScreen'; @@ -52,7 +51,7 @@ const TabNavigator = () => ( - + ); @@ -77,8 +76,7 @@ const AppNav = () => { - - + diff --git a/src/screens/AddClientScreen.js b/src/screens/AddClientScreen.js index 1a8b8dd..dd4c26e 100644 --- a/src/screens/AddClientScreen.js +++ b/src/screens/AddClientScreen.js @@ -1,18 +1,39 @@ -import React, { useState } from 'react'; -import { View, Text, TextInput, Button, StyleSheet, Alert, ScrollView, Platform, PermissionsAndroid, ActivityIndicator } from 'react-native'; +import React, { useState, useEffect, useContext } from 'react'; +import { + View, Text, TextInput, Button, StyleSheet, Alert, ScrollView, + Platform, PermissionsAndroid, ActivityIndicator, TouchableOpacity, Modal, FlatList +} from 'react-native'; import Geolocation from 'react-native-geolocation-service'; +import { pick } from '@react-native-documents/picker'; import api from '../services/api'; import Colors from '../constants/Colors'; +import { AuthContext } from '../context/AuthContext'; const AddClientScreen = ({ navigation }) => { - const [name, setName] = useState(''); + const { userInfo } = useContext(AuthContext); + const [companyName, setCompanyName] = useState(''); + const [contactName, setContactName] = useState(''); const [phone, setPhone] = useState(''); const [email, setEmail] = useState(''); const [address, setAddress] = useState(''); const [landmark, setLandmark] = useState(''); + const [closingProbability, setClosingProbability] = useState(''); + const [expectedClosingTimeframe, setExpectedClosingTimeframe] = useState(''); + const [isDemoDone, setIsDemoDone] = useState(false); const [location, setLocation] = useState(null); const [loading, setLoading] = useState(false); const [locating, setLocating] = useState(false); + const [selectedFiles, setSelectedFiles] = useState([]); + + // Assignment state + const [users, setUsers] = useState([]); + const [assignedUser, setAssignedUser] = useState(null); + const [userModal, setUserModal] = useState(false); + + useEffect(() => { + setAssignedUser({ id: userInfo?.id, name: 'Myself' }); + api.get('/users').then(r => setUsers(r.data)).catch(() => {}); + }, [userInfo]); const requestLocationPermission = async () => { if (Platform.OS === 'android') { @@ -43,13 +64,11 @@ const AddClientScreen = ({ navigation }) => { setLocating(true); Geolocation.getCurrentPosition( (position) => { - console.log('Location success:', position); setLocation(position.coords); setLocating(false); Alert.alert("Success", "Location Captured!"); }, (error) => { - console.log('Location error:', error); setLocating(false); Alert.alert("Location Error", error.message); }, @@ -57,30 +76,71 @@ const AddClientScreen = ({ navigation }) => { ); }; + const pickFiles = async () => { + try { + const results = await pick({ + multiple: true, + }); + + const uploadedFiles = []; + for (const res of results) { + const formData = new FormData(); + formData.append('file', { + uri: Platform.OS === 'ios' ? res.uri.replace('file://', '') : res.uri, + type: res.type || 'application/octet-stream', + name: res.name || 'file', + }); + + try { + const uploadRes = await api.post('/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + uploadedFiles.push({ + name: res.name, + type: res.type, + size: res.size, + url: uploadRes.data.url + }); + } catch (err) { + console.error('Upload failed', err); + Alert.alert('Upload Failed', `Could not upload ${res.name}`); + } + } + setSelectedFiles([...selectedFiles, ...uploadedFiles]); + } catch (err) { + if (!DocumentPicker.isCancel(err)) { + console.error(err); + } + } + }; + + const removeFile = (index) => { + setSelectedFiles(selectedFiles.filter((_, i) => i !== index)); + }; + const handleSubmit = async () => { - if (!name || !phone) { - Alert.alert("Error", "Name and Phone are required"); + if (!contactName || !phone) { + Alert.alert("Error", "Contact Name and Phone are required"); return; } - console.log('Current Location Check Before Submit:', location); - if (!location) { - Alert.alert("Debug", "Location state is null! Did you click capture?"); - } - const payload = { - name, + name: companyName || contactName, + companyName, + contactName, phone, status: 'LEAD', + assignedTo: assignedUser?.id || userInfo?.id, + closingProbability: closingProbability ? parseInt(closingProbability) : 0, + expectedClosingTimeframe, + isDemoDone, + files: selectedFiles, ...(email ? { email } : {}), ...(address ? { address } : {}), ...(landmark ? { landmark } : {}), ...(location ? { lat: location.latitude, lng: location.longitude } : {}) }; - console.log('Submitting Payload:', JSON.stringify(payload, null, 2)); - - setLoading(true); try { await api.post('/clients', payload); @@ -97,76 +157,122 @@ const AddClientScreen = ({ navigation }) => { return ( - Name * - + Company Name + + + Contact Name * + Phone * - + + + Assigned To + setUserModal(true)}> + {assignedUser?.name || 'Myself'} + + Email - + Address - + Landmark - + - -