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';
......@@ -26,7 +27,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
final TextEditingController _companyController = TextEditingController();
String? _profileImageUrl;
File? _selectedImage;
XFile? _selectedImage;
Uint8List? _selectedImageBytes;
// GraphGo specific stats
int _totalRoutes = 0;
......@@ -80,11 +82,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
} catch (e) {
print('Error loading user data: $e');
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _createUserProfile() async {
try {
......@@ -126,6 +130,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
totalEfficiency += (routeData['efficiency'] ?? 0.0).toDouble();
}
if (mounted) {
setState(() {
_totalRoutes = totalRoutes;
_totalDeliveries = totalDeliveries;
......@@ -133,6 +138,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
_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,26 +184,31 @@ 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();
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 {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _saveProfile() async {
try {
......@@ -203,7 +217,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
});
// Upload profile image if selected
if (_selectedImage != null) {
if (_selectedImageBytes != null) {
await _uploadProfileImage();
}
......@@ -220,6 +234,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
'updatedAt': FieldValue.serverTimestamp(),
});
if (mounted) {
setState(() {
_isEditing = false;
});
......@@ -230,17 +245,21 @@ class _ProfileScreenState extends State<ProfileScreen> {
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 {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Expanded(
......@@ -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,
),
......@@ -588,7 +607,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
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