Commit 101cfa5c authored by aleenaasghar's avatar aleenaasghar

feat: Implement CSV address upload functionality

parent 7ee35231
import 'dart:convert';
import 'package:csv/csv.dart';
import 'package:file_picker/file_picker.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
...@@ -46,6 +49,74 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> { ...@@ -46,6 +49,74 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
); );
} }
Future<void> _showUploadCsvDialog() async {
try {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['csv'],
);
if (result == null || !mounted) return;
final file = result.files.first;
final bytes = file.bytes;
if (bytes == null) return;
final content = utf8.decode(bytes);
final list = const CsvToListConverter().convert(content);
if (list.isNotEmpty) {
list.removeAt(0); // remove header
}
final addresses = list.map((row) {
try {
return DeliveryAddress(
streetAddress: row[0].toString(),
city: row[1].toString(),
state: row[2].toString(),
zipCode: row[3].toString(),
notes: row.length > 4 ? row[4].toString() : null,
);
} catch (e) {
print('Error parsing row: $row, error: $e');
return null;
}
}).where((address) => address != null).cast<DeliveryAddress>().toList();
if (!mounted) return; // Check if the widget is still in the tree
if (addresses.isNotEmpty) {
try {
await _firestoreService.saveAddressesFromCsv(addresses);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Addresses uploaded successfully!')),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error uploading addresses: $e')),
);
}
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No valid addresses found in the CSV file.')),
);
}
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error picking file: $e')),
);
}
}
}
void _deleteAddress(String addressId) { void _deleteAddress(String addressId) {
_firestoreService.deleteAddress(addressId); _firestoreService.deleteAddress(addressId);
} }
...@@ -61,6 +132,7 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> { ...@@ -61,6 +132,7 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
password: _passwordController.text.trim(), password: _passwordController.text.trim(),
); );
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Login Failed: ${e.message}")), SnackBar(content: Text("Login Failed: ${e.message}")),
); );
...@@ -139,7 +211,7 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> { ...@@ -139,7 +211,7 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
OutlinedButton.icon( OutlinedButton.icon(
onPressed: null, // Disabled for now onPressed: _showUploadCsvDialog,
icon: const Icon(Icons.upload_file), icon: const Icon(Icons.upload_file),
label: const Text('Upload CSV'), label: const Text('Upload CSV'),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
......
import 'dart:typed_data';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';
import '../colors.dart'; import '../colors.dart';
import '../services/google_auth_service.dart'; import '../services/google_auth_service.dart';
...@@ -18,15 +19,16 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -18,15 +19,16 @@ class _ProfileScreenState extends State<ProfileScreen> {
User? _user; User? _user;
bool _isLoading = false; bool _isLoading = false;
bool _isEditing = false; bool _isEditing = false;
// Profile editing controllers // Profile editing controllers
final TextEditingController _nameController = TextEditingController(); final TextEditingController _nameController = TextEditingController();
final TextEditingController _phoneController = TextEditingController(); final TextEditingController _phoneController = TextEditingController();
final TextEditingController _bioController = TextEditingController(); final TextEditingController _bioController = TextEditingController();
final TextEditingController _companyController = TextEditingController(); final TextEditingController _companyController = TextEditingController();
String? _profileImageUrl; String? _profileImageUrl;
File? _selectedImage; XFile? _selectedImage;
Uint8List? _selectedImageBytes;
// GraphGo specific stats // GraphGo specific stats
int _totalRoutes = 0; int _totalRoutes = 0;
...@@ -80,9 +82,11 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -80,9 +82,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
} catch (e) { } catch (e) {
print('Error loading user data: $e'); print('Error loading user data: $e');
} finally { } finally {
setState(() { if (mounted) {
_isLoading = false; setState(() {
}); _isLoading = false;
});
}
} }
} }
...@@ -126,12 +130,14 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -126,12 +130,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
totalEfficiency += (routeData['efficiency'] ?? 0.0).toDouble(); totalEfficiency += (routeData['efficiency'] ?? 0.0).toDouble();
} }
setState(() { if (mounted) {
_totalRoutes = totalRoutes; setState(() {
_totalDeliveries = totalDeliveries; _totalRoutes = totalRoutes;
_totalDistance = totalDistance; _totalDeliveries = totalDeliveries;
_averageEfficiency = totalRoutes > 0 ? totalEfficiency / totalRoutes : 0.0; _totalDistance = totalDistance;
}); _averageEfficiency = totalRoutes > 0 ? totalEfficiency / totalRoutes : 0.0;
});
}
} }
} catch (e) { } catch (e) {
print('Error loading user stats: $e'); print('Error loading user stats: $e');
...@@ -149,12 +155,15 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -149,12 +155,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
); );
if (image != null) { if (image != null) {
final bytes = await image.readAsBytes();
setState(() { setState(() {
_selectedImage = File(image.path); _selectedImage = image;
_selectedImageBytes = bytes;
}); });
} }
} catch (e) { } catch (e) {
print('Error picking image: $e'); print('Error picking image: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error selecting image: $e')), SnackBar(content: Text('Error selecting image: $e')),
); );
...@@ -162,7 +171,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -162,7 +171,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
} }
Future<void> _uploadProfileImage() async { Future<void> _uploadProfileImage() async {
if (_selectedImage == null) return; if (_selectedImage == null || _selectedImageBytes == null) return;
try { try {
setState(() { setState(() {
...@@ -175,24 +184,29 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -175,24 +184,29 @@ class _ProfileScreenState extends State<ProfileScreen> {
.child('profile_images') .child('profile_images')
.child(fileName); .child(fileName);
UploadTask uploadTask = storageRef.putFile(_selectedImage!); UploadTask uploadTask = storageRef.putData(_selectedImageBytes!);
TaskSnapshot snapshot = await uploadTask; TaskSnapshot snapshot = await uploadTask;
String downloadUrl = await snapshot.ref.getDownloadURL(); String downloadUrl = await snapshot.ref.getDownloadURL();
setState(() { if (mounted) {
_profileImageUrl = downloadUrl; setState(() {
}); _profileImageUrl = downloadUrl;
});
}
print('✅ Profile image uploaded: $downloadUrl'); print('✅ Profile image uploaded: $downloadUrl');
} catch (e) { } catch (e) {
print('Error uploading profile image: $e'); print('Error uploading profile image: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error uploading image: $e')), SnackBar(content: Text('Error uploading image: $e')),
); );
} finally { } finally {
setState(() { if (mounted) {
_isLoading = false; setState(() {
}); _isLoading = false;
});
}
} }
} }
...@@ -203,7 +217,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -203,7 +217,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}); });
// Upload profile image if selected // Upload profile image if selected
if (_selectedImage != null) { if (_selectedImageBytes != null) {
await _uploadProfileImage(); await _uploadProfileImage();
} }
...@@ -220,25 +234,30 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -220,25 +234,30 @@ class _ProfileScreenState extends State<ProfileScreen> {
'updatedAt': FieldValue.serverTimestamp(), 'updatedAt': FieldValue.serverTimestamp(),
}); });
setState(() { if (mounted) {
_isEditing = false; setState(() {
}); _isEditing = false;
});
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('✅ Profile updated successfully!'), content: Text('✅ Profile updated successfully!'),
backgroundColor: Colors.green, backgroundColor: Colors.green,
), ),
); );
}
} catch (e) { } catch (e) {
print('Error saving profile: $e'); print('Error saving profile: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error saving profile: $e')), SnackBar(content: Text('Error saving profile: $e')),
); );
} finally { } finally {
setState(() { if (mounted) {
_isLoading = false; setState(() {
}); _isLoading = false;
});
}
} }
} }
...@@ -280,7 +299,6 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -280,7 +299,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_isLoading) { if (_isLoading) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
...@@ -320,7 +338,8 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -320,7 +338,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
setState(() { setState(() {
_isEditing = !_isEditing; _isEditing = !_isEditing;
if (!_isEditing) { if (!_isEditing) {
// Reset to original values _selectedImage = null;
_selectedImageBytes = null;
_loadUserData(); _loadUserData();
} }
}); });
...@@ -345,12 +364,12 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -345,12 +364,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 60, radius: 60,
backgroundImage: _selectedImage != null backgroundImage: _selectedImageBytes != null
? FileImage(_selectedImage!) ? MemoryImage(_selectedImageBytes!)
: (_profileImageUrl != null : (_profileImageUrl != null
? NetworkImage(_profileImageUrl!) as ImageProvider ? NetworkImage(_profileImageUrl!) as ImageProvider
: null), : null),
child: _selectedImage == null && _profileImageUrl == null child: _selectedImageBytes == null && _profileImageUrl == null
? const Icon(Icons.person, size: 60) ? const Icon(Icons.person, size: 60)
: null, : null,
), ),
...@@ -375,7 +394,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -375,7 +394,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// User Name // User Name
if (_isEditing) if (_isEditing)
TextField( TextField(
...@@ -398,7 +417,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -398,7 +417,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
// User Email // User Email
Text( Text(
_user?.email ?? 'No email', _user?.email ?? 'No email',
...@@ -410,9 +429,9 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -410,9 +429,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// GraphGo Statistics // GraphGo Statistics
Card( Card(
child: Padding( child: Padding(
...@@ -434,7 +453,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -434,7 +453,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [
_buildStatCard( _buildStatCard(
...@@ -467,9 +486,9 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -467,9 +486,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Profile Details // Profile Details
Card( Card(
child: Padding( child: Padding(
...@@ -485,7 +504,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -485,7 +504,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Company // Company
Row( Row(
children: [ children: [
...@@ -513,9 +532,9 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -513,9 +532,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Phone Number // Phone Number
Row( Row(
children: [ children: [
...@@ -543,9 +562,9 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -543,9 +562,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Bio // Bio
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
...@@ -579,16 +598,15 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -579,16 +598,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
), ),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Action Buttons // Action Buttons
Row( Row(
children: [ children: [
Expanded( Expanded(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
// Navigate to route history
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Route history coming soon!')), const SnackBar(content: Text('Route history coming soon!')),
); );
......
...@@ -16,6 +16,16 @@ class FirestoreService { ...@@ -16,6 +16,16 @@ class FirestoreService {
return _db.collection(_collectionPath).doc(address.id).set(address.toJson()); return _db.collection(_collectionPath).doc(address.id).set(address.toJson());
} }
// Save a list of addresses from a CSV
Future<void> saveAddressesFromCsv(List<DeliveryAddress> addresses) async {
final batch = _db.batch();
for (final address in addresses) {
final docRef = _db.collection(_collectionPath).doc(address.id);
batch.set(docRef, address.toJson());
}
await batch.commit();
}
// Delete an address // Delete an address
Future<void> deleteAddress(String addressId) { Future<void> deleteAddress(String addressId) {
return _db.collection(_collectionPath).doc(addressId).delete(); return _db.collection(_collectionPath).doc(addressId).delete();
......
...@@ -193,6 +193,14 @@ packages: ...@@ -193,6 +193,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
csv:
dependency: "direct main"
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
...@@ -233,6 +241,14 @@ packages: ...@@ -233,6 +241,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4"
url: "https://pub.dev"
source: hosted
version: "6.2.1"
file_selector_linux: file_selector_linux:
dependency: transitive dependency: transitive
description: description:
...@@ -1156,6 +1172,14 @@ packages: ...@@ -1156,6 +1172,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
win32:
dependency: transitive
description:
name: win32
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
url: "https://pub.dev"
source: hosted
version: "5.14.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
......
...@@ -36,6 +36,8 @@ dependencies: ...@@ -36,6 +36,8 @@ dependencies:
http: ^1.1.0 http: ^1.1.0
uuid: ^4.2.1 uuid: ^4.2.1
image_picker: ^1.0.4 image_picker: ^1.0.4
file_picker: ^6.2.0
csv: ^6.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment