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';
...@@ -26,7 +27,8 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -26,7 +27,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
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,11 +82,13 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -80,11 +82,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
} catch (e) { } catch (e) {
print('Error loading user data: $e'); print('Error loading user data: $e');
} finally { } finally {
if (mounted) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
}
Future<void> _createUserProfile() async { Future<void> _createUserProfile() async {
try { try {
...@@ -126,6 +130,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -126,6 +130,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
totalEfficiency += (routeData['efficiency'] ?? 0.0).toDouble(); totalEfficiency += (routeData['efficiency'] ?? 0.0).toDouble();
} }
if (mounted) {
setState(() { setState(() {
_totalRoutes = totalRoutes; _totalRoutes = totalRoutes;
_totalDeliveries = totalDeliveries; _totalDeliveries = totalDeliveries;
...@@ -133,6 +138,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -133,6 +138,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
_averageEfficiency = totalRoutes > 0 ? totalEfficiency / totalRoutes : 0.0; _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,26 +184,31 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -175,26 +184,31 @@ 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();
if (mounted) {
setState(() { setState(() {
_profileImageUrl = downloadUrl; _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 {
if (mounted) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
}
Future<void> _saveProfile() async { Future<void> _saveProfile() async {
try { try {
...@@ -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,6 +234,7 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -220,6 +234,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
'updatedAt': FieldValue.serverTimestamp(), 'updatedAt': FieldValue.serverTimestamp(),
}); });
if (mounted) {
setState(() { setState(() {
_isEditing = false; _isEditing = false;
}); });
...@@ -230,17 +245,21 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -230,17 +245,21 @@ class _ProfileScreenState extends State<ProfileScreen> {
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 {
if (mounted) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
}
Widget _buildStatCard(String title, String value, IconData icon, Color color) { Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Expanded( return Expanded(
...@@ -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,
), ),
...@@ -588,7 +607,6 @@ class _ProfileScreenState extends State<ProfileScreen> { ...@@ -588,7 +607,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
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