feature aggiunta

This commit is contained in:
2026-04-20 11:18:22 +02:00
parent 023665ae58
commit 78012fdbf3
13 changed files with 631 additions and 80 deletions

View File

@@ -1,18 +1,19 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flux/core/blocs/session/session_bloc.dart';
import 'package:flux/core/utils/string_extensions.dart';
import 'package:flux/features/customers/models/customer_file_model.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/customer_model.dart';
class CustomerRepository {
final SupabaseClient _client = GetIt.I<SupabaseClient>();
final SupabaseClient _supabase = GetIt.I<SupabaseClient>();
final String companyId = GetIt.I.get<SessionBloc>().state.company!.id;
// Crea un nuovo cliente
Future<CustomerModel> saveCustomer(CustomerModel customer) async {
try {
final response = await _client
final response = await _supabase
.from('customer')
.upsert(customer.toJson())
.select()
@@ -25,7 +26,7 @@ class CustomerRepository {
Future<CustomerModel> updateCustomer(CustomerModel customer) async {
try {
final response = await _client
final response = await _supabase
.from('customer')
.update(customer.toJson())
.eq('id', customer.id!)
@@ -40,7 +41,7 @@ class CustomerRepository {
// Recupera tutti i clienti dell'azienda
Future<List<CustomerModel>> getCustomers(String companyId) async {
try {
final response = await _client
final response = await _supabase
.from('customer')
.select('*, customer_file(count)')
.eq('company_id', companyId)
@@ -59,7 +60,7 @@ class CustomerRepository {
String query,
) async {
try {
final response = await _client
final response = await _supabase
.from('customer')
.select()
.eq('company_id', companyId)
@@ -75,13 +76,13 @@ class CustomerRepository {
/// Recupera i file di un cliente specifico
Future<List<CustomerFileModel>> getCustomerFiles(String customerId) async {
try {
final response = await _client
final response = await _supabase
.from('customer_file')
.select()
.eq('customer_id', customerId);
return (response as List)
.map((f) => CustomerFileModel.fromJson(f))
.map((f) => CustomerFileModel.fromMap(f))
.toList();
} catch (e) {
throw 'Errore recupero file: $e';
@@ -90,7 +91,7 @@ class CustomerRepository {
/// Salva il riferimento del file nel DB
Future<void> saveFileReference(CustomerFileModel file) async {
await _client.from('customer_file').insert(file.toJson());
await _supabase.from('customer_file').insert(file.toMap());
}
/// Carica un file e salva il riferimento nel database
@@ -98,15 +99,24 @@ class CustomerRepository {
required String customerId,
required PlatformFile pickedFile,
}) async {
final cleanFileName = pickedFile.name.replaceAll(
RegExp(r'[^a-zA-Z0-9\.\-]'),
'_',
);
final storagePath =
'$companyId/customers/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
final int fileSize = pickedFile.size;
final fileToSave = CustomerFileModel(
customerId: customerId,
name: cleanFileName.fileNameWithoutExtension(),
extension: cleanFileName.fileExtension(),
url: '',
fileSize: fileSize,
);
final String mimeType = fileToSave.extension.toLowerCase() == 'pdf'
? 'application/pdf'
: 'image/${fileToSave.extension}';
try {
final user = _client.auth.currentUser;
if (user == null) throw 'Utente non autenticato';
final fileName = pickedFile.name;
final extension = pickedFile.extension ?? '';
final path =
'${user.id}/$customerId/${DateTime.now().millisecondsSinceEpoch}_$fileName';
// Usiamo bytes invece del path per massima compatibilità
if (pickedFile.bytes == null && pickedFile.path == null) {
throw 'Impossibile leggere il contenuto del file';
@@ -114,32 +124,26 @@ class CustomerRepository {
// Se siamo su desktop/mobile abbiamo il path, su web abbiamo i bytes
if (pickedFile.bytes != null) {
await _client.storage
await _supabase.storage
.from('documents')
.uploadBinary(path, pickedFile.bytes!);
} else {
final file = File(pickedFile.path!);
await _client.storage.from('documents').upload(path, file);
.uploadBinary(
storagePath,
pickedFile.bytes!,
fileOptions: FileOptions(contentType: mimeType, upsert: true),
);
}
final String publicUrl = _client.storage
final String publicUrl = _supabase.storage
.from('documents')
.getPublicUrl(path);
.getPublicUrl(storagePath);
final fileRecord = CustomerFileModel(
customerId: customerId,
name: fileName,
url: publicUrl,
extension: extension,
);
final response = await _client
final response = await _supabase
.from('customer_file')
.insert(fileRecord.toJson())
.insert(fileToSave.copyWith(url: publicUrl).toMap())
.select()
.single();
return CustomerFileModel.fromJson(response);
return CustomerFileModel.fromMap(response);
} catch (e) {
throw 'Errore durante l\'upload: $e';
}
@@ -147,13 +151,16 @@ class CustomerRepository {
/// Aggiorna la lista degli URL nel database
Future<void> updateCustomerDocuments(int id, List<String> urls) async {
await _client.from('customer').update({'document_urls': urls}).eq('id', id);
await _supabase
.from('customer')
.update({'document_urls': urls})
.eq('id', id);
}
/// Elimina un file dallo storage
Future<void> deleteDocument(String fullPath) async {
// Il path dovrebbe essere ricavato dall'URL
final path = fullPath.split('documents/').last;
await _client.storage.from('documents').remove([path]);
await _supabase.storage.from('documents').remove([path]);
}
}

View File

@@ -7,6 +7,7 @@ class CustomerFileModel extends Equatable {
final String url;
final String extension;
final DateTime? createdAt;
final int fileSize;
const CustomerFileModel({
this.id,
@@ -15,31 +16,76 @@ class CustomerFileModel extends Equatable {
required this.url,
required this.extension,
this.createdAt,
required this.fileSize,
});
factory CustomerFileModel.fromJson(Map<String, dynamic> json) {
// Trasforma i byte in qualcosa di leggibile (KB, MB, GB)
String get sizeFormatted {
if (fileSize <= 0) return "0 B";
const suffixes = ["B", "KB", "MB", "GB", "TB"];
var i = (fileSize.toString().length - 1) ~/ 3;
if (i >= suffixes.length) i = suffixes.length - 1;
double num = fileSize / (1 << (i * 10));
return "${num.toStringAsFixed(i == 0 ? 0 : 1)} ${suffixes[i]}";
}
bool get isPdf => extension.toLowerCase().replaceAll('.', '') == 'pdf';
CustomerFileModel copyWith({
String? id,
String? customerId,
String? name,
String? url,
String? extension,
DateTime? createdAt,
int? fileSize,
}) {
return CustomerFileModel(
id: json['id'] as String,
customerId: json['customer_id'],
name: json['name'],
url: json['url'],
extension: json['extension'] ?? '',
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'])
: null,
id: id ?? this.id,
customerId: customerId ?? this.customerId,
name: name ?? this.name,
url: url ?? this.url,
extension: extension ?? this.extension,
createdAt: createdAt ?? this.createdAt,
fileSize: fileSize ?? this.fileSize,
);
}
Map<String, dynamic> toJson() {
factory CustomerFileModel.fromMap(Map<String, dynamic> map) {
return CustomerFileModel(
id: map['id'] as String,
customerId: map['customer_id'],
name: map['name'],
url: map['url'],
extension: map['extension'] ?? '',
createdAt: map['created_at'] != null
? DateTime.parse(map['created_at'])
: null,
fileSize: map['file_size'] is int
? map['file_size']
: int.tryParse(map['file_size']?.toString() ?? '0') ?? 0,
);
}
Map<String, dynamic> toMap() {
return {
if (id != null) 'id': id,
'customer_id': customerId,
'name': name,
'url': url,
'extension': extension,
'file_size': fileSize,
};
}
@override
List<Object?> get props => [id, customerId, name, url, extension, createdAt];
List<Object?> get props => [
id,
customerId,
name,
url,
extension,
createdAt,
fileSize,
];
}