ottimo punto sembra funzionare tutto, devo solo aggiungere l'aggiunta di un cliente volante, di un modello volante e gestire i file allegati

This commit is contained in:
2026-04-18 19:03:49 +02:00
parent bbb9729ca4
commit e9f3327f31
16 changed files with 665 additions and 96 deletions

View File

@@ -8,26 +8,27 @@ import 'package:flux/features/services/models/entertainment_service_model.dart';
import 'package:flux/features/services/models/fin_service_model.dart';
import 'package:flux/features/services/models/service_model.dart';
import 'package:get_it/get_it.dart';
import 'package:collection/collection.dart';
part 'services_state.dart';
class ServicesCubit extends Cubit<ServicesState> {
final ServicesRepository _repository = GetIt.I<ServicesRepository>();
final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
ServicesCubit() : super(const ServicesState());
ServicesCubit() : super(const ServicesState(status: ServicesStatus.initial));
// --- CARICAMENTO E PAGINAZIONE ---
Future<void> loadServices({bool refresh = false}) async {
// Se stiamo già caricando, evitiamo chiamate doppie
if (state.isLoading) return;
if (state.status == ServicesStatus.loading) return;
// Se non è un refresh e abbiamo già raggiunto la fine dei dati, ci fermiamo
if (!refresh && state.hasReachedMax) return;
emit(
state.copyWith(
isLoading: true,
status: ServicesStatus.loading,
errorMessage: null,
// Se è un refresh, svuotiamo la lista attuale per mostrare lo shimmer/loading
allServices: refresh ? [] : state.allServices,
@@ -56,7 +57,7 @@ class ServicesCubit extends Cubit<ServicesState> {
emit(
state.copyWith(
isLoading: false,
status: ServicesStatus.ready,
allServices: refresh
? newServices
: [...state.allServices, ...newServices],
@@ -66,7 +67,7 @@ class ServicesCubit extends Cubit<ServicesState> {
} catch (e) {
emit(
state.copyWith(
isLoading: false,
status: ServicesStatus.failure,
errorMessage: "Errore nel caricamento servizi: $e",
),
);
@@ -95,9 +96,28 @@ class ServicesCubit extends Cubit<ServicesState> {
// --- GESTIONE BOZZA (DRAFT) ---
/// Inizializza un nuovo servizio o ne carica uno esistente per la modifica
void initServiceForm(ServiceModel? existingService) {
void initServiceForm({
ServiceModel? existingService,
String? serviceId,
}) async {
if (existingService != null) {
emit(state.copyWith(currentService: existingService));
emit(
state.copyWith(
currentService: existingService,
status: ServicesStatus.ready,
),
);
} else if (serviceId != null) {
ServiceModel? serviceModel = state.allServices.firstWhereOrNull(
(s) => s.id == serviceId,
);
serviceModel ??= await _repository.fetchServiceById(serviceId);
emit(
state.copyWith(
currentService: serviceModel,
status: ServicesStatus.ready,
),
);
} else {
// Crea un template vuoto con lo store di default (se disponibile)
emit(
@@ -106,7 +126,9 @@ class ServicesCubit extends Cubit<ServicesState> {
storeId: _sessionBloc.state.selectedStore?.id ?? '',
number: '', // Sarà compilato dall'utente
createdAt: DateTime.now(),
companyId: _sessionBloc.state.company!.id,
),
status: ServicesStatus.ready,
),
);
}
@@ -180,16 +202,22 @@ class ServicesCubit extends Cubit<ServicesState> {
Future<void> saveCurrentService() async {
if (state.currentService == null) return;
emit(state.copyWith(isSaving: true, errorMessage: null));
emit(state.copyWith(status: ServicesStatus.saving, errorMessage: null));
try {
// Usiamo il repository corazzato che abbiamo scritto prima
await _repository.saveFullService(state.currentService!);
// Reset della bozza e ricaricamento lista
emit(state.copyWith(isSaving: false, currentService: null));
await loadServices(refresh: true);
// Reset della bozza e ricaricamento lista
emit(state.copyWith(status: ServicesStatus.saved, currentService: null));
} catch (e) {
emit(state.copyWith(isSaving: false, errorMessage: e.toString()));
emit(
state.copyWith(
status: ServicesStatus.failure,
errorMessage: e.toString(),
),
);
}
}
}

View File

@@ -1,20 +1,20 @@
part of 'services_cubit.dart';
enum ServicesStatus { initial, loading, ready, saving, saved, success, failure }
class ServicesState extends Equatable {
final ServicesStatus status;
final List<ServiceModel> allServices;
final ServiceModel? currentService; // La bozza che stiamo editando
final bool isLoading;
final bool isSaving; // Per mostrare il caricamento solo sul tasto salva
final String? errorMessage;
final String query;
final DateTimeRange? dateRange;
final bool hasReachedMax;
const ServicesState({
required this.status,
this.allServices = const [],
this.currentService,
this.isLoading = false,
this.isSaving = false,
this.errorMessage,
this.query = '',
this.dateRange,
@@ -22,20 +22,18 @@ class ServicesState extends Equatable {
});
ServicesState copyWith({
ServicesStatus? status,
List<ServiceModel>? allServices,
ServiceModel? currentService,
bool? isLoading,
bool? isSaving,
String? errorMessage,
String? query,
DateTimeRange? dateRange,
bool? hasReachedMax,
}) {
return ServicesState(
status: status ?? this.status,
allServices: allServices ?? this.allServices,
currentService: currentService ?? this.currentService,
isLoading: isLoading ?? this.isLoading,
isSaving: isSaving ?? this.isSaving,
errorMessage: errorMessage,
query: query ?? this.query,
dateRange: dateRange ?? this.dateRange,
@@ -45,10 +43,9 @@ class ServicesState extends Equatable {
@override
List<Object?> get props => [
status,
allServices,
currentService,
isLoading,
isSaving,
errorMessage,
query,
dateRange,

View File

@@ -5,6 +5,27 @@ import '../models/service_model.dart';
class ServicesRepository {
final _supabase = Supabase.instance.client;
// --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO ---
Future<ServiceModel> fetchServiceById(String id) async {
try {
final response = await _supabase
.from('service')
.select('''
*,
customer(nome),
energy_service(*),
fin_service(*),
entertainment_service(*)
''')
.eq('id', id)
.single();
return ServiceModel.fromMap(response);
} catch (e) {
throw Exception('Errore nel caricamento del servizio: $e');
}
}
// --- RECUPERO PAGINATO CON FILTRI E JOIN ---
Future<List<ServiceModel>> fetchServices({
required String companyId,
@@ -19,7 +40,7 @@ class ServicesRepository {
.from('service')
.select('''
*,
customer(name, surname),
customer(nome),
energy_service(*),
fin_service(*),
entertainment_service(*)
@@ -36,7 +57,7 @@ class ServicesRepository {
if (searchTerm != null && searchTerm.isNotEmpty) {
// Filtra sui campi della tabella principale O su quelli della tabella joinata
query = query.or(
'number.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.name.ilike.%$searchTerm%,customer.surname.ilike.%$searchTerm%',
'number.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.nome.ilike.%$searchTerm%',
);
}
@@ -134,4 +155,36 @@ class ServicesRepository {
throw Exception('Errore durante l\'eliminazione: $e');
}
}
// --- RECUPERO TIPI CONTENUTI PIÙ FREQUENTI PER AUTOCOMPLETE ---
Future<List<String>> fetchTopEntertainmentTypes(String companyId) async {
try {
// Cerchiamo i tipi più frequenti associati ai servizi di questa company
// Nota: dobbiamo passare attraverso la tabella 'service' per filtrare per company_id
final response = await _supabase
.from('entertainment_service')
.select('type, service!inner(store!inner(company_id))')
.eq('service.store.company_id', companyId)
.limit(100); // Prendiamo un campione
// Logica rapida per contare le occorrenze e prendere i primi 5
final Map<String, int> counts = {};
for (var item in (response as List)) {
final type = item['type'] as String;
counts[type] = (counts[type] ?? 0) + 1;
}
var sortedKeys = counts.keys.toList()
..sort((a, b) => counts[b]!.compareTo(counts[a]!));
return sortedKeys.take(5).toList();
} catch (e) {
return [
"Netflix",
"DAZN",
"Disney+",
"Sky",
]; // Fallback se non c'è ancora storia
}
}
}

View File

@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:flux/core/utils/string_extensions.dart';
import 'package:flux/features/services/models/energy_service_model.dart';
import 'package:flux/features/services/models/entertainment_service_model.dart';
import 'package:flux/features/services/models/fin_service_model.dart';
@@ -14,6 +15,7 @@ class ServiceModel extends Equatable {
final String note;
final bool resultOk;
final String? customerDisplayName;
final String companyId;
// Telefonia
final int al;
@@ -46,6 +48,7 @@ class ServiceModel extends Equatable {
this.finServices = const [],
this.entertainmentServices = const [],
this.customerDisplayName,
required this.companyId,
});
ServiceModel copyWith({
@@ -67,6 +70,7 @@ class ServiceModel extends Equatable {
List<FinServiceModel>? finServices,
List<EntertainmentServiceModel>? entertainmentServices,
String? customerDisplayName,
String? companyId,
}) {
return ServiceModel(
id: id ?? this.id,
@@ -88,6 +92,7 @@ class ServiceModel extends Equatable {
entertainmentServices:
entertainmentServices ?? this.entertainmentServices,
customerDisplayName: customerDisplayName ?? this.customerDisplayName,
companyId: companyId ?? this.companyId,
);
}
@@ -111,6 +116,7 @@ class ServiceModel extends Equatable {
finServices,
entertainmentServices,
customerDisplayName,
companyId,
];
factory ServiceModel.fromMap(Map<String, dynamic> map) {
@@ -151,9 +157,9 @@ class ServiceModel extends Equatable {
// Display name del cliente con fallback
customerDisplayName: map['customer'] != null
? "${map['customer']['name'] ?? ''} ${map['customer']['surname'] ?? ''}"
.trim()
? "${map['customer']['nome'] ?? ''}".myFormat()
: "Cliente non assegnato",
companyId: map['company_id'] as String,
);
}
@@ -172,6 +178,7 @@ class ServiceModel extends Equatable {
'nip': nip,
'unica': unica,
'telepass': telepass,
'company_id': companyId,
// Le liste non le mettiamo qui perché vanno in tabelle diverse!
};
}

View File

@@ -0,0 +1,392 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_bloc.dart';
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
import 'package:flux/features/master_data/providers/models/provider_model.dart';
import 'package:flux/features/services/data/services_repository.dart';
import 'package:flux/features/services/models/entertainment_service_model.dart';
import 'package:get_it/get_it.dart';
class EntertainmentServiceDialog extends StatefulWidget {
final List<EntertainmentServiceModel> initialServices;
final String currentStoreId;
const EntertainmentServiceDialog({
super.key,
required this.initialServices,
required this.currentStoreId,
});
@override
State<EntertainmentServiceDialog> createState() =>
_EntertainmentServiceDialogState();
}
class _EntertainmentServiceDialogState
extends State<EntertainmentServiceDialog> {
late List<EntertainmentServiceModel> _tempList;
bool _isAddingNew = false;
@override
void initState() {
super.initState();
_tempList = List.from(widget.initialServices);
// Carichiamo i provider attivi per lo store corrente
context.read<ProvidersCubit>().loadActiveProvidersForStore(
widget.currentStoreId,
);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Row(
children: [
Icon(
Icons.movie_filter_outlined,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(_isAddingNew ? "Nuovo Servizio" : "Servizi Intrattenimento"),
],
),
content: AnimatedSize(
duration: const Duration(milliseconds: 300),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: _isAddingNew
? _EntertainmentForm(
// Il form che abbiamo creato prima
onSave: (newService) => setState(() {
_tempList.add(newService);
_isAddingNew = false;
}),
onCancel: () => setState(() => _isAddingNew = false),
)
: BlocBuilder<ProvidersCubit, ProvidersState>(
builder: (context, state) {
// Passiamo allProviders per garantire la visione dello storico
return _EntertainmentList(
services: _tempList,
allProviders: state.allProviders,
onDelete: (index) =>
setState(() => _tempList.removeAt(index)),
onAddTap: () => setState(() => _isAddingNew = true),
);
},
),
),
),
actions: !_isAddingNew
? [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Annulla"),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, _tempList),
child: const Text("Conferma Tutti"),
),
]
: null, // I pulsanti del form sono interni al form stesso
);
}
}
class _EntertainmentList extends StatelessWidget {
final List<EntertainmentServiceModel> services;
final List<ProviderModel> allProviders;
final Function(int) onDelete;
final VoidCallback onAddTap;
const _EntertainmentList({
required this.services,
required this.allProviders,
required this.onDelete,
required this.onAddTap,
});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (services.isEmpty)
const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Text(
"Nessun servizio intrattenimento.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
)
else
Flexible(
child: ListView.separated(
shrinkWrap: true,
itemCount: services.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final s = services[index];
final providerName = allProviders
.firstWhere(
(p) => p.id == s.providerId,
orElse: () => ProviderModel(
id: '',
nome: 'Fornitore Storico',
companyId: '',
isActive: false,
energia: false,
telefoniaFissa: false,
telefoniaMobile: false,
assicurazioni: false,
altro: false,
intrattenimento: false,
),
)
.nome;
return ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
backgroundColor: Colors.purple.shade100,
child: const Icon(
Icons.movie_creation_outlined,
color: Colors.purple,
),
),
title: Text(
"${s.type}$providerName",
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
s.constrained
? "Vincolo fino al: ${s.constrainExpiration.day}/${s.constrainExpiration.month}/${s.constrainExpiration.year}"
: "Senza vincoli",
style: TextStyle(
color: s.constrained
? Colors.red.shade700
: Colors.green.shade700,
),
),
trailing: IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red),
onPressed: () => onDelete(index),
),
);
},
),
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: onAddTap,
icon: const Icon(Icons.add),
label: const Text("Aggiungi Servizio"),
),
],
);
}
}
// ---ENTERTAINMENT FORM (MODALE)---
class _EntertainmentForm extends StatefulWidget {
final Function(EntertainmentServiceModel) onSave;
final VoidCallback onCancel;
const _EntertainmentForm({required this.onSave, required this.onCancel});
@override
State<_EntertainmentForm> createState() => _EntertainmentFormState();
}
class _EntertainmentFormState extends State<_EntertainmentForm> {
String? _selectedProviderId;
final TextEditingController _typeController = TextEditingController();
bool _isConstrained = false;
DateTime _expirationDate = DateTime.now().add(
const Duration(days: 365),
); // Default 12 mesi
// Preset rapidi per il vincolo (es: 12, 24 mesi)
int? _selectedPresetMonths;
void _applyPreset(int months) {
setState(() {
_selectedPresetMonths = months;
_isConstrained = true;
final now = DateTime.now();
_expirationDate = DateTime(now.year, now.month + months, now.day);
});
}
Future<void> _pickDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _expirationDate,
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365 * 10)),
);
if (picked != null) {
setState(() {
_expirationDate = picked;
_selectedPresetMonths = null;
_isConstrained = true;
});
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. GESTORE (Filtro intrattenimento)
BlocBuilder<ProvidersCubit, ProvidersState>(
builder: (context, state) {
final filtered = state.activeProviders
.where((p) => p.intrattenimento)
.toList();
return DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: "Fornitore (es: Sky, TIM)",
border: OutlineInputBorder(),
),
items: filtered
.map(
(p) => DropdownMenuItem(value: p.id, child: Text(p.nome)),
)
.toList(),
onChanged: (val) => setState(() => _selectedProviderId = val),
);
},
),
const SizedBox(height: 16),
// 2. TIPO SERVIZIO (TextField con suggerimenti rapidi sotto)
TextFormField(
controller: _typeController,
decoration: const InputDecoration(
labelText: "Servizio",
hintText: "es: Netflix, DAZN, Disney+",
border: OutlineInputBorder(),
),
onChanged: (val) => setState(() {}),
),
const SizedBox(height: 8),
// Suggerimenti rapidi (Chip)
FutureBuilder<List<String>>(
future: GetIt.I<ServicesRepository>().fetchTopEntertainmentTypes(
GetIt.I<SessionBloc>().state.company!.id,
),
builder: (context, snapshot) {
final suggestions = snapshot.data ?? ["Netflix", "DAZN", "Sky"];
return Wrap(
spacing: 8,
children: suggestions.map((s) {
return ActionChip(
label: Text(s, style: const TextStyle(fontSize: 12)),
onPressed: () => setState(() => _typeController.text = s),
);
}).toList(),
);
},
),
const SizedBox(height: 16),
// 3. VINCOLO CONTRATTUALE
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Vincolo di permanenza",
style: TextStyle(fontWeight: FontWeight.bold),
),
Switch(
value: _isConstrained,
onChanged: (val) => setState(() {
_isConstrained = val;
if (!val) _selectedPresetMonths = null;
}),
),
],
),
if (_isConstrained) ...[
const SizedBox(height: 8),
SegmentedButton<int?>(
segments: const [
ButtonSegment(value: 12, label: Text("12m")),
ButtonSegment(value: 24, label: Text("24m")),
ButtonSegment(
value: null,
label: Icon(Icons.calendar_month, size: 20),
),
],
selected: {_selectedPresetMonths},
onSelectionChanged: (val) {
if (val.first == null) {
_pickDate();
} else {
_applyPreset(val.first!);
}
},
),
const SizedBox(height: 12),
// Box data scadenza vincolo
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.event_busy, size: 18, color: Colors.redAccent),
const SizedBox(width: 8),
Text(
"Scadenza vincolo: ${_expirationDate.day.toString().padLeft(2, '0')}/${_expirationDate.month.toString().padLeft(2, '0')}/${_expirationDate.year}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
],
const SizedBox(height: 24),
// PULSANTI
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: widget.onCancel,
child: const Text("Annulla"),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed:
(_selectedProviderId == null || _typeController.text.isEmpty)
? null
: () => widget.onSave(
EntertainmentServiceModel(
providerId: _selectedProviderId!,
type: _typeController.text,
constrained: _isConstrained,
constrainExpiration: _expirationDate,
),
),
child: const Text("Aggiungi"),
),
],
),
],
);
}
}

View File

@@ -10,45 +10,67 @@ class ServiceFormScreen extends StatelessWidget {
@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
CustomerSection(service: service),
const SizedBox(height: 24),
// SEZIONE 2: INFO GENERALI (Da fare)
GeneralInfoSection(service: service),
const SizedBox(height: 24),
// SEZIONE 3: I MODULI (Da fare)
ServicesGrid(service: service),
const SizedBox(height: 32),
// SEZIONE 4: ALLEGATI (Da fare)
// const _AttachmentsSection(),
],
return BlocListener<ServicesCubit, ServicesState>(
listener: (context, state) {
if (state.status == ServicesStatus.saved) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Pratica salvata con successo!"),
backgroundColor: Colors.green,
),
);
},
Navigator.pop(context); // Torna alla lista di pratiche
} else if (state.status == ServicesStatus.failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Si è verificato un 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(),
],
),
);
},
),
),
);
}
@@ -61,7 +83,7 @@ class _SaveButton extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<ServicesCubit, ServicesState>(
builder: (context, state) {
if (state.isSaving) {
if (state.status == ServicesStatus.saving) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: SizedBox(
@@ -78,7 +100,6 @@ class _SaveButton extends StatelessWidget {
icon: const Icon(Icons.save),
tooltip: "Salva Pratica",
onPressed: () {
// TODO: Aggiungere una validazione prima di salvare!
context.read<ServicesCubit>().saveCurrentService();
},
);

View File

@@ -3,10 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/master_data/products/blocs/product_cubit.dart';
import 'package:flux/features/services/blocs/services_cubit.dart';
import 'package:flux/features/services/models/energy_service_model.dart';
import 'package:flux/features/services/models/entertainment_service_model.dart';
import 'package:flux/features/services/models/fin_service_model.dart';
import 'package:flux/features/services/models/service_model.dart';
import 'package:flux/features/services/ui/service_form_screen/action_card.dart';
import 'package:flux/features/services/ui/service_form_screen/energy_service_dialog.dart';
import 'package:flux/features/services/ui/service_form_screen/entertainment_service_card.dart';
import 'package:flux/features/services/ui/service_form_screen/finance_service_dialog.dart';
import 'package:flux/features/services/ui/service_form_screen/int_dialogs.dart'; // Assicurati di importare il modello
@@ -162,12 +164,25 @@ class ServicesGrid extends StatelessWidget {
},
),
ActionCard(
label: "Contenuti",
label: "Intratten.",
count: service.entertainmentServices.length,
icon: Icons.tv,
color: Colors.redAccent,
onTap: () {
// TODO: Aprire la Dialog Contenuti complessa
icon: Icons.movie_filter_outlined,
color: Colors.purple,
onTap: () async {
final result =
await showDialog<List<EntertainmentServiceModel>>(
context: context,
builder: (context) => EntertainmentServiceDialog(
initialServices: service.entertainmentServices,
currentStoreId: service.storeId,
),
);
if (result != null && context.mounted) {
context
.read<ServicesCubit>()
.updateEntertainmentServices(result);
}
},
),
],

View File

@@ -21,6 +21,8 @@ class _ServicesScreenState extends State<ServicesScreen> {
super.initState();
// Agganciamo il listener per la paginazione (Scroll Infinito)
_scrollController.addListener(_onScroll);
// Carichiamo i servizi iniziali
context.read<ServicesCubit>().loadServices();
}
void _onScroll() {
@@ -61,7 +63,8 @@ class _ServicesScreenState extends State<ServicesScreen> {
body: BlocBuilder<ServicesCubit, ServicesState>(
builder: (context, state) {
// 1. Stato di caricamento iniziale
if (state.isLoading && state.allServices.isEmpty) {
if (state.status == ServicesStatus.loading &&
state.allServices.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
@@ -172,7 +175,10 @@ class _ServicesScreenState extends State<ServicesScreen> {
],
),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.pushNamed('service-form', extra: service),
onTap: () => context.pushNamed(
'service-form',
queryParameters: {'serviceId': service.id},
),
),
);
}

View File

@@ -54,11 +54,12 @@ void startNewService(BuildContext context) {
onTap: () {
// 1. Inizializza il form nel Cubit
context.read<ServicesCubit>().initServiceForm(
ServiceModel(
existingService: ServiceModel(
storeId: currentStoreId,
employeeId: member.id,
number: '',
createdAt: DateTime.now(),
companyId: session.company!.id,
),
);