Refactor service management: streamline service form, enhance state management, and improve loading logic

This commit is contained in:
2026-04-17 19:19:01 +02:00
parent 667bbf6404
commit a06be4bf7a
13 changed files with 715 additions and 261 deletions

View File

@@ -0,0 +1,111 @@
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';
class GeneralInfoSection extends StatelessWidget {
final ServiceModel service;
const GeneralInfoSection({super.key, required this.service});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.info_outline,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
"Info Generali",
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 16),
// Numero di Riferimento / Telefono
TextFormField(
initialValue: service.number,
keyboardType: TextInputType
.phone, // Fa aprire il tastierino numerico su mobile
decoration: const InputDecoration(
labelText: "Numero di Telefono / Riferimento",
hintText: "Es. 3331234567",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.phone),
),
onChanged: (val) {
context.read<ServicesCubit>().updateField(number: val);
},
),
const SizedBox(height: 16),
// I due Switch affiancati (Bozza e A buon fine)
Row(
children: [
Expanded(
child: SwitchListTile(
title: const Text("Bozza"),
subtitle: const Text(
"Pratica in lavorazione",
style: TextStyle(fontSize: 12),
),
value: service.isBozza,
activeThumbColor: Colors.orange,
contentPadding: EdgeInsets.zero,
onChanged: (val) {
context.read<ServicesCubit>().updateField(isBozza: val);
},
),
),
const SizedBox(width: 16),
Expanded(
child: SwitchListTile(
title: const Text("A buon fine"),
subtitle: const Text(
"Esito positivo",
style: TextStyle(fontSize: 12),
),
value: service.resultOk,
activeThumbColor: Colors.green,
contentPadding: EdgeInsets.zero,
onChanged: (val) {
context.read<ServicesCubit>().updateField(resultOk: val);
},
),
),
],
),
const SizedBox(height: 16),
// Campo Note
TextFormField(
initialValue: service.note,
maxLines: 4,
minLines: 2,
decoration: const InputDecoration(
labelText: "Note Operazione",
hintText:
"Scrivi qui eventuali dettagli o richieste del cliente...",
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
onChanged: (val) {
context.read<ServicesCubit>().updateField(note: val);
},
),
],
),
),
);
}
}

View File

@@ -0,0 +1,177 @@
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/ui/service_form_screen/general_info_section.dart';
class ServiceFormScreen extends StatelessWidget {
const ServiceFormScreen({super.key});
@override
Widget build(BuildContext context) {
return 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
const _CustomerSection(),
const SizedBox(height: 24),
// SEZIONE 2: INFO GENERALI (Da fare)
GeneralInfoSection(service: service),
const SizedBox(height: 24),
// SEZIONE 3: I MODULI (Da fare)
Text(
"Servizi e Accessori",
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
// const _ServicesGrid(),
const SizedBox(height: 32),
// 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.isSaving) {
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: () {
// TODO: Aggiungere una validazione prima di salvare!
context.read<ServicesCubit>().saveCurrentService();
},
);
},
);
}
}
class _CustomerSection extends StatelessWidget {
const _CustomerSection();
@override
Widget build(BuildContext context) {
return BlocBuilder<ServicesCubit, ServicesState>(
builder: (context, state) {
final service = state.currentService!;
final hasCustomer = service.customerId != null;
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.person,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
"Dati Cliente",
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 16),
// Se non c'è il cliente, mostriamo il tastone per cercarlo
if (!hasCustomer)
Center(
child: ElevatedButton.icon(
onPressed: () {
// TODO: Aprire modale/dialog per ricerca clienti
print("Apro ricerca clienti...");
},
icon: const Icon(Icons.search),
label: const Text("Seleziona o Crea Cliente"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
),
)
// Se c'è, mostriamo chi è e diamo la possibilità di cambiarlo
else
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
service.customerDisplayName ?? "Cliente Selezionato",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
TextButton.icon(
onPressed: () {
// TODO: Aprire modale/dialog per ricerca clienti
},
icon: const Icon(Icons.edit, size: 18),
label: const Text("Cambia"),
),
],
),
],
),
),
);
},
);
}
}