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:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
......@@ -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) {
_firestoreService.deleteAddress(addressId);
}
......@@ -61,6 +132,7 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
password: _passwordController.text.trim(),
);
} on FirebaseAuthException catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Login Failed: ${e.message}")),
);
......@@ -139,7 +211,7 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
),
const SizedBox(width: 16),
OutlinedButton.icon(
onPressed: null, // Disabled for now
onPressed: _showUploadCsvDialog,
icon: const Icon(Icons.upload_file),
label: const Text('Upload CSV'),
style: OutlinedButton.styleFrom(
......
import 'dart:typed_data';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';
import '../colors.dart';
import '../services/google_auth_service.dart';
......@@ -18,15 +19,16 @@ class _ProfileScreenState extends State<ProfileScreen> {
User? _user;
bool _isLoading = false;
bool _isEditing = false;
// Profile editing controllers
final TextEditingController _nameController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _bioController = TextEditingController();
final TextEditingController _companyController = TextEditingController();
String? _profileImageUrl;
File? _selectedImage;
XFile? _selectedImage;
Uint8List? _selectedImageBytes;
// GraphGo specific stats
int _totalRoutes = 0;
......@@ -80,9 +82,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
} catch (e) {
print('Error loading user data: $e');
} finally {
setState(() {
_isLoading = false;
});
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
......@@ -126,12 +130,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
totalEfficiency += (routeData['efficiency'] ?? 0.0).toDouble();
}
setState(() {
_totalRoutes = totalRoutes;
_totalDeliveries = totalDeliveries;
_totalDistance = totalDistance;
_averageEfficiency = totalRoutes > 0 ? totalEfficiency / totalRoutes : 0.0;
});
if (mounted) {
setState(() {
_totalRoutes = totalRoutes;
_totalDeliveries = totalDeliveries;
_totalDistance = totalDistance;
_averageEfficiency = totalRoutes > 0 ? totalEfficiency / totalRoutes : 0.0;
});
}
}
} catch (e) {
print('Error loading user stats: $e');
......@@ -149,12 +155,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
if (image != null) {
final bytes = await image.readAsBytes();
setState(() {
_selectedImage = File(image.path);
_selectedImage = image;
_selectedImageBytes = bytes;
});
}
} catch (e) {
print('Error picking image: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error selecting image: $e')),
);
......@@ -162,7 +171,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
Future<void> _uploadProfileImage() async {
if (_selectedImage == null) return;
if (_selectedImage == null || _selectedImageBytes == null) return;
try {
setState(() {
......@@ -175,24 +184,29 @@ class _ProfileScreenState extends State<ProfileScreen> {
.child('profile_images')
.child(fileName);
UploadTask uploadTask = storageRef.putFile(_selectedImage!);
UploadTask uploadTask = storageRef.putData(_selectedImageBytes!);
TaskSnapshot snapshot = await uploadTask;
String downloadUrl = await snapshot.ref.getDownloadURL();
setState(() {
_profileImageUrl = downloadUrl;
});
if (mounted) {
setState(() {
_profileImageUrl = downloadUrl;
});
}
print('✅ Profile image uploaded: $downloadUrl');
} catch (e) {
print('Error uploading profile image: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error uploading image: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
......@@ -203,7 +217,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
});
// Upload profile image if selected
if (_selectedImage != null) {
if (_selectedImageBytes != null) {
await _uploadProfileImage();
}
......@@ -220,25 +234,30 @@ class _ProfileScreenState extends State<ProfileScreen> {
'updatedAt': FieldValue.serverTimestamp(),
});
setState(() {
_isEditing = false;
});
if (mounted) {
setState(() {
_isEditing = false;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('✅ Profile updated successfully!'),
backgroundColor: Colors.green,
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('✅ Profile updated successfully!'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
print('Error saving profile: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error saving profile: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
......@@ -280,7 +299,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
@override
Widget build(BuildContext context) {
if (_isLoading) {
return Scaffold(
appBar: AppBar(
......@@ -320,7 +338,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
setState(() {
_isEditing = !_isEditing;
if (!_isEditing) {
// Reset to original values
_selectedImage = null;
_selectedImageBytes = null;
_loadUserData();
}
});
......@@ -345,12 +364,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
children: [
CircleAvatar(
radius: 60,
backgroundImage: _selectedImage != null
? FileImage(_selectedImage!)
backgroundImage: _selectedImageBytes != null
? MemoryImage(_selectedImageBytes!)
: (_profileImageUrl != null
? NetworkImage(_profileImageUrl!) as ImageProvider
: null),
child: _selectedImage == null && _profileImageUrl == null
child: _selectedImageBytes == null && _profileImageUrl == null
? const Icon(Icons.person, size: 60)
: null,
),
......@@ -375,7 +394,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
),
const SizedBox(height: 16),
// User Name
if (_isEditing)
TextField(
......@@ -398,7 +417,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
fontWeight: FontWeight.bold,
),
),
// User Email
Text(
_user?.email ?? 'No email',
......@@ -410,9 +429,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
],
),
),
const SizedBox(height: 24),
// GraphGo Statistics
Card(
child: Padding(
......@@ -434,7 +453,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
],
),
const SizedBox(height: 16),
Row(
children: [
_buildStatCard(
......@@ -467,9 +486,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
),
),
const SizedBox(height: 16),
// Profile Details
Card(
child: Padding(
......@@ -485,7 +504,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
),
const SizedBox(height: 16),
// Company
Row(
children: [
......@@ -513,9 +532,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
],
),
const SizedBox(height: 16),
// Phone Number
Row(
children: [
......@@ -543,9 +562,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
],
),
const SizedBox(height: 16),
// Bio
Row(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -579,16 +598,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
),
),
const SizedBox(height: 24),
// Action Buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
// Navigate to route history
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Route history coming soon!')),
);
......
......@@ -16,6 +16,16 @@ class FirestoreService {
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
Future<void> deleteAddress(String addressId) {
return _db.collection(_collectionPath).doc(addressId).delete();
......
......@@ -193,6 +193,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
......@@ -233,6 +241,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
......@@ -1156,6 +1172,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
win32:
dependency: transitive
description:
name: win32
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
url: "https://pub.dev"
source: hosted
version: "5.14.0"
xdg_directories:
dependency: transitive
description:
......
......@@ -36,6 +36,8 @@ dependencies:
http: ^1.1.0
uuid: ^4.2.1
image_picker: ^1.0.4
file_picker: ^6.2.0
csv: ^6.0.0
dev_dependencies:
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