sistemato deeplinking alla serviceformscreen, aggiunto logout e sistemate altre cose

This commit is contained in:
2026-04-19 10:57:55 +02:00
parent e9f3327f31
commit 023665ae58
17 changed files with 742 additions and 198 deletions

View File

@@ -199,22 +199,24 @@ class ServicesCubit extends Cubit<ServicesState> {
// --- PERSISTENZA ---
Future<void> saveCurrentService() async {
Future<void> saveCurrentService({required bool isBozza}) async {
if (state.currentService == null) return;
emit(state.copyWith(status: ServicesStatus.saving, errorMessage: null));
try {
// Usiamo il repository corazzato che abbiamo scritto prima
await _repository.saveFullService(state.currentService!);
// 1. Aggiorniamo il flag bozza in base a quale pulsante ha premuto l'utente
final serviceToSave = state.currentService!.copyWith(isBozza: isBozza);
await loadServices(refresh: true);
// Reset della bozza e ricaricamento lista
// 2. Salvataggio corazzato
await _repository.saveFullService(serviceToSave);
// 3. Reset e ricaricamento
emit(state.copyWith(status: ServicesStatus.saved, currentService: null));
await loadServices(refresh: true);
} catch (e) {
emit(
state.copyWith(
status: ServicesStatus.failure,
errorMessage: e.toString(),
),
);

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/service_model.dart';
@@ -187,4 +188,28 @@ class ServicesRepository {
]; // Fallback se non c'è ancora storia
}
}
Future<void> uploadAttachment({
required String serviceId,
required String fileName,
required Uint8List fileBytes,
}) async {
try {
// 1. Upload fisico nel bucket 'service_documents'
final path = '$serviceId/$fileName';
await _supabase.storage
.from('service_documents')
.uploadBinary(path, fileBytes);
// 2. Registriamo l'esistenza del file nel database
await _supabase.from('service_attachment').insert({
'service_id': serviceId,
'file_path': path,
'file_name': fileName,
'created_at': DateTime.now().toIso8601String(),
});
} catch (e) {
throw "Errore upload: $e";
}
}
}

View File

@@ -142,6 +142,7 @@ class _EntertainmentList extends StatelessWidget {
telefoniaFissa: false,
telefoniaMobile: false,
assicurazioni: false,
finanziamenti: false,
altro: false,
intrattenimento: false,
),

View File

@@ -171,6 +171,7 @@ class _FinanceList extends StatelessWidget {
assicurazioni: false,
altro: false,
intrattenimento: false,
finanziamenti: false,
),
)
.nome;
@@ -292,12 +293,13 @@ class _FinanceFormState extends State<_FinanceForm> {
// 1. SCELTA ISTITUTO (Solo attivi)
BlocBuilder<ProvidersCubit, ProvidersState>(
builder: (context, state) {
final finProviders = state
.activeProviders; // Già filtrati dal caricamento della dialog
final finProviders = state.activeProviders
.where((p) => p.finanziamenti)
.toList(); // Già filtrati dal caricamento della dialog
return DropdownButtonFormField<String>(
initialValue: _selectedProviderId,
decoration: const InputDecoration(
labelText: "Istituto di Credito",
labelText: "Gestore",
border: OutlineInputBorder(),
),
items: finProviders

View File

@@ -1,16 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/services/blocs/services_cubit.dart';
import 'package:flux/features/services/models/service_model.dart';
import 'package:flux/features/services/ui/service_form_screen/customer_section.dart';
import 'package:flux/features/services/ui/service_form_screen/general_info_section.dart';
import 'package:flux/features/services/ui/service_form_screen/services_grid.dart';
class ServiceFormScreen extends StatelessWidget {
const ServiceFormScreen({super.key});
class ServiceFormScreen extends StatefulWidget {
final String? serviceId;
final ServiceModel? existingService; // <-- AGGIUNTO
const ServiceFormScreen({
super.key,
this.serviceId,
this.existingService, // <-- AGGIUNTO
});
@override
State<ServiceFormScreen> createState() => _ServiceFormScreenState();
}
class _ServiceFormScreenState extends State<ServiceFormScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// Diamo in pasto al Cubit tutto quello che abbiamo!
context.read<ServicesCubit>().initServiceForm(
existingService: widget.existingService,
serviceId: widget.serviceId,
);
});
}
void _performSave(BuildContext context, {required bool isBozza}) {
FocusScope.of(context).unfocus();
context.read<ServicesCubit>().saveCurrentService(isBozza: isBozza);
}
@override
Widget build(BuildContext context) {
return BlocListener<ServicesCubit, ServicesState>(
return BlocConsumer<ServicesCubit, ServicesState>(
listener: (context, state) {
if (state.status == ServicesStatus.saved) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -19,91 +49,123 @@ class ServiceFormScreen extends StatelessWidget {
backgroundColor: Colors.green,
),
);
Navigator.pop(context); // Torna alla lista di pratiche
Navigator.pop(context);
} else if (state.status == ServicesStatus.failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Si è verificato un errore ${state.errorMessage ?? ''}",
),
content: Text("Errore: ${state.errorMessage ?? ''}"),
backgroundColor: Colors.red,
),
);
}
},
child: Scaffold(
appBar: AppBar(
title: const Text("Nuova Pratica"),
actions: [
_SaveButton(), // Tasto salva intelligente
],
),
body: BlocBuilder<ServicesCubit, ServicesState>(
builder: (context, state) {
final service = state.currentService;
// Se la bozza non è ancora inizializzata, mostriamo un loader
if (service == null) {
return const Center(child: CircularProgressIndicator());
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// SEZIONE 1: CLIENTE
CustomerSection(service: service),
const SizedBox(height: 24),
// SEZIONE 2: INFO GENERALI
GeneralInfoSection(service: service),
const SizedBox(height: 24),
// SEZIONE 3: I MODULI
ServicesGrid(service: service),
const SizedBox(height: 32),
// TODO SEZIONE 4: ALLEGATI (Da fare)
// const _AttachmentsSection(),
],
),
);
},
),
),
);
}
}
// --- COMPONENTI DELLA PAGINA ---
class _SaveButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<ServicesCubit, ServicesState>(
builder: (context, state) {
if (state.status == ServicesStatus.saving) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
),
);
}
return IconButton(
icon: const Icon(Icons.save),
tooltip: "Salva Pratica",
onPressed: () {
context.read<ServicesCubit>().saveCurrentService();
},
final service = state.currentService;
final isSaving = state.status == ServicesStatus.saving;
final isEditMode = widget.serviceId != null;
return Scaffold(
appBar: AppBar(
title: Text(isEditMode ? "Modifica Pratica" : "Nuova Pratica"),
actions: [
if (isSaving)
const Padding(
padding: EdgeInsets.only(right: 20.0),
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
)
else if (service != null) ...[
IconButton(
icon: const Icon(Icons.edit_note),
tooltip: "Salva come Bozza",
onPressed: () => _performSave(context, isBozza: true),
),
IconButton(
icon: const Icon(
Icons.check_circle_outline,
color: Colors.green,
),
tooltip: "Conferma Pratica",
onPressed: () => _performSave(context, isBozza: false),
),
const SizedBox(width: 8),
],
],
),
body: (service == null)
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomerSection(service: service),
const SizedBox(height: 24),
GeneralInfoSection(service: service),
const SizedBox(height: 24),
ServicesGrid(service: service),
const SizedBox(height: 32),
// TODO: _AttachmentsSection(),
_buildBottomActionButtons(context, isSaving: isSaving),
const SizedBox(height: 32),
],
),
),
);
},
);
}
Widget _buildBottomActionButtons(
BuildContext context, {
required bool isSaving,
}) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
flex: 1,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
icon: const Icon(Icons.edit_note),
label: const Text("Salva in Bozza"),
onPressed: isSaving
? null
: () => _performSave(context, isBozza: true),
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green.shade600,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
icon: const Icon(Icons.check_circle_outline),
label: const Text(
"CONFERMA PRATICA",
style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1),
),
onPressed: isSaving
? null
: () => _performSave(context, isBozza: false),
),
),
],
);
}
}

View File

@@ -177,7 +177,9 @@ class _ServicesScreenState extends State<ServicesScreen> {
trailing: const Icon(Icons.chevron_right),
onTap: () => context.pushNamed(
'service-form',
queryParameters: {'serviceId': service.id},
extra: service, // <-- LA MAGIA È QUI: Passa l'oggetto intero!
// Teniamo anche il parametro URL per coerenza di routing
queryParameters: service.id != null ? {'serviceId': service.id!} : {},
),
),
);