ellamadonna

This commit is contained in:
2026-04-18 01:50:14 +02:00
parent 3eba1a32ec
commit a3509bca91
18 changed files with 108 additions and 810 deletions

View File

@@ -10,9 +10,9 @@ part 'product_state.dart';
class ProductCubit extends Cubit<ProductState> { class ProductCubit extends Cubit<ProductState> {
final ProductRepository _repository = GetIt.I<ProductRepository>(); final ProductRepository _repository = GetIt.I<ProductRepository>();
final SessionBloc _sessionBloc; final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
ProductCubit(this._sessionBloc) : super(const ProductState()); ProductCubit() : super(const ProductState());
// Caricamento iniziale dei Brand // Caricamento iniziale dei Brand
Future<void> loadBrands() async { Future<void> loadBrands() async {

View File

@@ -52,9 +52,9 @@ class ProvidersState extends Equatable {
class ProvidersCubit extends Cubit<ProvidersState> { class ProvidersCubit extends Cubit<ProvidersState> {
final ProviderRepository _repository = GetIt.I<ProviderRepository>(); final ProviderRepository _repository = GetIt.I<ProviderRepository>();
final SessionBloc _sessionBloc; final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
ProvidersCubit(this._sessionBloc) : super(const ProvidersState()); ProvidersCubit() : super(const ProvidersState());
// Carica i provider della company e quelli associati a uno store specifico // Carica i provider della company e quelli associati a uno store specifico
Future<void> loadProviders(StoreModel? store) async { Future<void> loadProviders(StoreModel? store) async {

View File

@@ -10,9 +10,9 @@ part 'staff_state.dart';
class StaffCubit extends Cubit<StaffState> { class StaffCubit extends Cubit<StaffState> {
final StaffRepository _repository = GetIt.I.get<StaffRepository>(); final StaffRepository _repository = GetIt.I.get<StaffRepository>();
final SessionBloc _sessionBloc; final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
StaffCubit(this._sessionBloc) : super(const StaffState()); StaffCubit() : super(const StaffState());
// Carica tutto lo staff della compagnia // Carica tutto lo staff della compagnia
Future<void> loadAllStaff() async { Future<void> loadAllStaff() async {

View File

@@ -13,9 +13,9 @@ part 'store_state.dart';
class StoreCubit extends Cubit<StoreState> { class StoreCubit extends Cubit<StoreState> {
final StoreRepository _repository = GetIt.I<StoreRepository>(); final StoreRepository _repository = GetIt.I<StoreRepository>();
final StaffRepository _staffRepository = GetIt.I<StaffRepository>(); final StaffRepository _staffRepository = GetIt.I<StaffRepository>();
final SessionBloc _sessionBloc; final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
StoreCubit(this._sessionBloc) : super(const StoreState(stores: [])); StoreCubit() : super(const StoreState(stores: []));
Future<void> createStore(final StoreModel store) async { Future<void> createStore(final StoreModel store) async {
emit(state.copyWith(status: StoreStatus.loading)); emit(state.copyWith(status: StoreStatus.loading));

View File

@@ -1,105 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_bloc.dart';
import 'package:flux/features/products/data/product_repository.dart';
import 'package:flux/features/products/models/brand_model.dart';
import 'package:flux/features/products/models/model_model.dart';
import 'package:get_it/get_it.dart';
part 'product_state.dart';
class ProductCubit extends Cubit<ProductState> {
final ProductRepository _repository = GetIt.I<ProductRepository>();
final SessionBloc _sessionBloc;
ProductCubit(this._sessionBloc) : super(const ProductState());
// Caricamento iniziale dei Brand
Future<void> loadBrands() async {
emit(state.copyWith(status: ProductStatus.loading));
try {
final brands = await _repository.getBrands(
_sessionBloc.state.company!.id,
);
emit(state.copyWith(status: ProductStatus.success, brands: brands));
} catch (e) {
emit(
state.copyWith(status: ProductStatus.error, errorMessage: e.toString()),
);
}
}
// Selezione Brand e caricamento Modelli
Future<void> selectBrand(BrandModel? brand) async {
if (brand == null) {
emit(state.copyWith(selectedBrand: null, models: []));
return;
}
emit(state.copyWith(status: ProductStatus.loading, selectedBrand: brand));
try {
final models = await _repository.getModelsByBrand(brand.id!);
emit(state.copyWith(status: ProductStatus.success, models: models));
} catch (e) {
emit(
state.copyWith(status: ProductStatus.error, errorMessage: e.toString()),
);
}
}
// Aggiungi/Modifica Brand
Future<void> saveBrand(String name, {String? id}) async {
try {
final brand = BrandModel(
id: id,
name: name,
companyId: _sessionBloc.state.company!.id,
);
final newBrand = await _repository.upsertBrand(brand);
await loadBrands(); // Ricarichiamo la lista aggiornata
selectBrand(newBrand);
} catch (e) {
emit(
state.copyWith(status: ProductStatus.error, errorMessage: e.toString()),
);
}
}
// Aggiungi/Modifica Modello
Future<void> saveModel(String name, {String? id}) async {
if (state.selectedBrand == null) return;
try {
final model = ModelModel(
id: id,
name: name,
brandId: state.selectedBrand!.id!,
nameWithBrand: '', // Gestito dal trigger SQL
);
await _repository.upsertModel(model);
await selectBrand(
state.selectedBrand,
); // Ricarichiamo i modelli del brand
} catch (e) {
emit(
state.copyWith(status: ProductStatus.error, errorMessage: e.toString()),
);
}
}
// Disattivazione (Soft Delete)
Future<void> toggleStatus(String table, String id, bool currentStatus) async {
try {
await _repository.toggleActiveStatus(table, id, !currentStatus);
if (table == 'brand') {
await loadBrands();
} else {
await selectBrand(state.selectedBrand);
}
} catch (e) {
emit(
state.copyWith(status: ProductStatus.error, errorMessage: e.toString()),
);
}
}
}

View File

@@ -1,44 +0,0 @@
part of 'product_cubit.dart';
enum ProductStatus { initial, loading, success, error }
class ProductState extends Equatable {
final ProductStatus status;
final List<BrandModel> brands;
final List<ModelModel> models;
final BrandModel? selectedBrand; // Il brand attualmente selezionato
final String? errorMessage;
const ProductState({
this.status = ProductStatus.initial,
this.brands = const [],
this.models = const [],
this.selectedBrand,
this.errorMessage,
});
ProductState copyWith({
ProductStatus? status,
List<BrandModel>? brands,
List<ModelModel>? models,
BrandModel? selectedBrand,
String? errorMessage,
}) {
return ProductState(
status: status ?? this.status,
brands: brands ?? this.brands,
models: models ?? this.models,
selectedBrand: selectedBrand ?? this.selectedBrand,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [
status,
brands,
models,
selectedBrand,
errorMessage,
];
}

View File

@@ -1,86 +0,0 @@
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/brand_model.dart';
import '../models/model_model.dart';
class ProductRepository {
final SupabaseClient _client = GetIt.I<SupabaseClient>();
// --- BRAND ---
/// Recupera tutti i brand dell'azienda
Future<List<BrandModel>> getBrands(String companyId) async {
try {
final response = await _client
.from('brand')
.select()
.eq('company_id', companyId)
.eq('is_active', true)
.order('name');
return (response as List).map((b) => BrandModel.fromJson(b)).toList();
} catch (e) {
throw 'Errore nel recupero dei Brand';
}
}
/// Crea o aggiorna un brand
Future<BrandModel> upsertBrand(BrandModel brand) async {
try {
final response = await _client
.from('brand')
.upsert(brand.toJson())
.select()
.single();
return BrandModel.fromJson(response);
} catch (e) {
throw 'Errore nel salvataggio del Brand';
}
}
// --- MODEL ---
/// Recupera i modelli di un brand specifico
Future<List<ModelModel>> getModelsByBrand(String brandId) async {
try {
final response = await _client
.from('model')
.select()
.eq('brand_id', brandId)
.eq('is_active', true)
.order('name');
return (response as List).map((m) => ModelModel.fromJson(m)).toList();
} catch (e) {
throw 'Errore nel recupero dei modelli';
}
}
/// Crea o aggiorna un modello
/// NOTA: name_with_brand verrà gestito dal trigger SQL che hai lanciato!
Future<ModelModel> upsertModel(ModelModel model) async {
try {
final response = await _client
.from('model')
.upsert(model.toJson())
.select()
.single();
return ModelModel.fromJson(response);
} catch (e) {
throw 'Errore nel salvataggio del modello';
}
}
// --- DELETE (LOGICA) ---
/// Disattiva un brand o un modello (Soft Delete per non rompere le FK delle operazioni passate)
Future<void> toggleActiveStatus(String table, String id, bool status) async {
try {
await _client.from(table).update({'is_active': status}).eq('id', id);
} catch (e) {
throw 'Errore durante la modifica dello stato';
}
}
}

View File

@@ -1,58 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:flux/core/utils/string_extensions.dart';
class BrandModel extends Equatable {
final String? id;
final String name;
final String companyId;
final bool isActive;
final DateTime? createdAt;
const BrandModel({
this.id,
required this.name,
required this.companyId,
this.isActive = true,
this.createdAt,
});
factory BrandModel.fromJson(Map<String, dynamic> json) {
return BrandModel(
id: json['id'] as String,
name: (json['name'] as String).myFormat(),
companyId: json['company_id'] as String,
isActive: json['is_active'] as bool? ?? true,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
if (id != null) 'id': id,
'name': name.toLowerCase().trim(),
'company_id': companyId,
'is_active': isActive,
};
}
BrandModel copyWith({
String? id,
String? name,
String? companyId,
bool? isActive,
DateTime? createdAt,
}) {
return BrandModel(
id: id ?? this.id,
name: name ?? this.name,
companyId: companyId ?? this.companyId,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt,
);
}
@override
List<Object?> get props => [id, name, companyId, isActive, createdAt];
}

View File

@@ -1,70 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:flux/core/utils/string_extensions.dart';
class ModelModel extends Equatable {
final String? id;
final String name;
final String nameWithBrand;
final String brandId;
final bool isActive;
final DateTime? createdAt;
const ModelModel({
this.id,
required this.name,
required this.nameWithBrand,
required this.brandId,
this.isActive = true,
this.createdAt,
});
factory ModelModel.fromJson(Map<String, dynamic> json) {
return ModelModel(
id: json['id'] as String,
name: (json['name'] as String).myFormat(),
nameWithBrand: (json['name_with_brand'] as String).myFormat(),
brandId: json['brand_id'] as String,
isActive: json['is_active'] as bool? ?? true,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
if (id != null) 'id': id,
'name': name.toLowerCase().trim(),
'brand_id': brandId,
'is_active': isActive,
};
}
ModelModel copyWith({
String? id,
String? name,
String? nameWithBrand,
String? brandId,
bool? isActive,
DateTime? createdAt,
}) {
return ModelModel(
id: id ?? this.id,
name: name ?? this.name,
nameWithBrand: nameWithBrand ?? this.nameWithBrand,
brandId: brandId ?? this.brandId,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt,
);
}
@override
List<Object?> get props => [
id,
name,
nameWithBrand,
brandId,
isActive,
createdAt,
];
}

View File

@@ -1,58 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/theme/theme.dart';
import 'package:flux/features/products/blocs/product_cubit.dart';
import 'package:flux/features/products/models/brand_model.dart';
import 'package:flux/features/products/ui/product_dialogs.dart';
import 'package:flux/features/products/ui/round_action_button.dart';
class BrandSelector extends StatelessWidget {
final ProductState state;
const BrandSelector(this.state, {super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: context.background,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: context.accent.withValues(alpha: 0.1)),
),
child: Row(
children: [
Expanded(
child: DropdownButtonFormField<BrandModel>(
initialValue: state.selectedBrand,
//value: state.selectedBrand,
decoration: const InputDecoration(
labelText: "Seleziona Brand",
prefixIcon: Icon(Icons.branding_watermark_outlined),
),
items: state.brands.map((brand) {
return DropdownMenuItem(value: brand, child: Text(brand.name));
}).toList(),
onChanged: (brand) =>
context.read<ProductCubit>().selectBrand(brand),
),
),
const SizedBox(width: 16),
// Pulsanti rapidi Brand
RoundActionButton(
icon: Icons.add,
onTap: () => showBrandDialog(context),
tooltip: "Nuovo Brand",
),
if (state.selectedBrand != null) ...[
const SizedBox(width: 8),
RoundActionButton(
icon: Icons.edit_outlined,
onTap: () => showBrandDialog(context, brand: state.selectedBrand),
tooltip: "Modifica Brand",
),
],
],
),
);
}
}

View File

@@ -1,79 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/theme/theme.dart';
import 'package:flux/features/products/blocs/product_cubit.dart';
import 'package:flux/features/products/ui/product_dialogs.dart';
class ModelsList extends StatelessWidget {
final ProductState state;
const ModelsList(this.state, {super.key});
@override
Widget build(BuildContext context) {
if (state.selectedBrand == null) {
return const Center(
child: Text("Seleziona un brand per gestire i modelli"),
);
}
if (state.status == ProductStatus.loading && state.models.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Modelli di ${state.selectedBrand!.name}",
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
ElevatedButton.icon(
onPressed: () => showModelDialog(context),
icon: const Icon(Icons.add),
label: const Text("NUOVO MODELLO"),
),
],
),
const SizedBox(height: 16),
Expanded(
child: ListView.separated(
itemCount: state.models.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final model = state.models[index];
return ListTile(
title: Text(model.name),
subtitle: Text(
model.nameWithBrand,
), // Quello gestito dal trigger!
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit_outlined),
onPressed: () => showModelDialog(context, model: model),
),
IconButton(
icon: Icon(
model.isActive
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
color: model.isActive ? context.accent : Colors.grey,
),
onPressed: () => context
.read<ProductCubit>()
.toggleStatus('model', model.id!, model.isActive),
),
],
),
);
},
),
),
],
);
}
}

View File

@@ -1,110 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/products/blocs/product_cubit.dart';
import 'package:flux/features/products/models/brand_model.dart';
import 'package:flux/features/products/models/model_model.dart';
void showBrandDialog(BuildContext context, {BrandModel? brand}) {
final controller = TextEditingController(text: brand?.name);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(brand == null ? "Nuovo Brand" : "Modifica Brand"),
content: TextField(
controller: controller,
autofocus: true,
decoration: const InputDecoration(
labelText: "Nome Brand",
hintText: "es. Apple, Samsung...",
),
onSubmitted: (_) => _submitBrand(controller, context, brand),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Annulla"),
),
ElevatedButton(
onPressed: () => _submitBrand(controller, context, brand),
child: const Text("Salva"),
),
],
),
);
}
void _submitBrand(
TextEditingController controller,
BuildContext context,
BrandModel? brand,
) {
if (controller.text.trim().isNotEmpty) {
context.read<ProductCubit>().saveBrand(controller.text, id: brand?.id);
Navigator.pop(context);
}
}
void showModelDialog(BuildContext context, {ModelModel? model}) {
final controller = TextEditingController(text: model?.name);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(model == null ? "Nuovo Modello" : "Modifica Modello"),
content: TextField(
controller: controller,
autofocus: true,
decoration: const InputDecoration(
labelText: "Nome Modello",
hintText: "es. iPhone 15, Galaxy S24...",
),
onSubmitted: (_) => _submitModel(controller, context, model),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Annulla"),
),
ElevatedButton(
onPressed: () => _submitModel(controller, context, model),
child: const Text("Salva"),
),
],
),
);
}
void _submitModel(
TextEditingController controller,
BuildContext context,
ModelModel? model,
) {
if (controller.text.isNotEmpty) {
context.read<ProductCubit>().saveModel(controller.text, id: model?.id);
Navigator.pop(context);
}
}
void confirmToggle(BuildContext context, String title, VoidCallback onConfirm) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Sei sicuro?"),
content: Text("Stai per cambiare lo stato di: $title"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Annulla"),
),
TextButton(
onPressed: () {
onConfirm();
Navigator.pop(context);
},
child: const Text("Conferma", style: TextStyle(color: Colors.red)),
),
],
),
);
}

View File

@@ -1,74 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/theme/theme.dart';
import 'package:flux/features/products/blocs/product_cubit.dart';
import 'package:flux/features/products/ui/brand_selector.dart';
import 'package:flux/features/products/ui/models_list.dart';
import 'package:go_router/go_router.dart';
class ProductsScreen extends StatelessWidget {
const ProductsScreen({super.key});
@override
Widget build(BuildContext context) {
// Carichiamo i brand appena la pagina viene creata
context.read<ProductCubit>().loadBrands();
return Scaffold(
backgroundColor: context.background,
appBar: AppBar(
backgroundColor: context.background,
elevation: 0,
centerTitle: false,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.pop(), // Torna alla Dashboard
),
title: Text(
"Anagrafica Prodotti",
style: TextStyle(
color: context.primaryText,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
body: BlocConsumer<ProductCubit, ProductState>(
listener: (context, state) {
if (state.status == ProductStatus.error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Errore')),
);
}
},
builder: (context, state) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Marche e Modelli",
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: context.accent,
),
),
const SizedBox(height: 24),
// SEZIONE BRAND
BrandSelector(state),
const SizedBox(height: 32),
// SEZIONE MODELLI
Expanded(child: ModelsList(state)),
],
),
);
},
),
);
}
}

View File

@@ -1,23 +0,0 @@
import 'package:flutter/material.dart';
class RoundActionButton extends StatelessWidget {
final IconData icon;
final VoidCallback onTap;
final String tooltip;
const RoundActionButton({
super.key,
required this.icon,
required this.onTap,
required this.tooltip,
});
@override
Widget build(BuildContext context) {
return IconButton.filledTonal(
onPressed: onTap,
icon: Icon(icon),
tooltip: tooltip,
);
}
}

View File

@@ -12,9 +12,9 @@ part 'services_state.dart';
class ServicesCubit extends Cubit<ServicesState> { class ServicesCubit extends Cubit<ServicesState> {
final ServicesRepository _repository = GetIt.I<ServicesRepository>(); final ServicesRepository _repository = GetIt.I<ServicesRepository>();
final SessionBloc _sessionBloc; final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
ServicesCubit(this._sessionBloc) : super(const ServicesState()); ServicesCubit() : super(const ServicesState());
// --- CARICAMENTO E PAGINAZIONE --- // --- CARICAMENTO E PAGINAZIONE ---

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/master_data/products/blocs/product_cubit.dart';
import 'package:flux/features/master_data/products/models/model_model.dart';
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart'; import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
import 'package:flux/features/products/blocs/product_cubit.dart';
import 'package:flux/features/services/models/fin_service_model.dart'; import 'package:flux/features/services/models/fin_service_model.dart';
import 'package:flux/features/master_data/providers/models/provider_model.dart'; import 'package:flux/features/master_data/providers/models/provider_model.dart';
import 'package:flux/features/products/models/model_model.dart';
// =========================================================================== // ===========================================================================
// DIALOG PRINCIPALE // DIALOG PRINCIPALE
@@ -12,11 +12,13 @@ import 'package:flux/features/products/models/model_model.dart';
class FinanceServiceDialog extends StatefulWidget { class FinanceServiceDialog extends StatefulWidget {
final List<FinServiceModel> initialServices; final List<FinServiceModel> initialServices;
final String currentStoreId; final String currentStoreId;
final ProductCubit productCubit;
const FinanceServiceDialog({ const FinanceServiceDialog({
super.key, super.key,
required this.initialServices, required this.initialServices,
required this.currentStoreId, required this.currentStoreId,
required this.productCubit,
}); });
@override @override
@@ -40,60 +42,63 @@ class _FinanceServiceDialogState extends State<FinanceServiceDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return BlocProvider.value(
title: Row( value: widget.productCubit,
children: [ child: AlertDialog(
Icon( title: Row(
Icons.payments_outlined, children: [
color: Theme.of(context).colorScheme.primary, Icon(
), Icons.payments_outlined,
const SizedBox(width: 8), color: Theme.of(context).colorScheme.primary,
Text(_isAddingNew ? "Dettagli Finanziamento" : "Finanziamenti"), ),
], const SizedBox(width: 8),
), Text(_isAddingNew ? "Dettagli Finanziamento" : "Finanziamenti"),
content: AnimatedSize( ],
duration: const Duration(milliseconds: 300),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: _isAddingNew
? _FinanceForm(
onSave: (newFin) => setState(() {
_tempList.add(newFin);
_isAddingNew = false;
}),
onCancel: () => setState(() => _isAddingNew = false),
)
: BlocBuilder<ProvidersCubit, ProvidersState>(
builder: (context, provState) {
return BlocBuilder<ProductCubit, ProductState>(
builder: (context, prodState) {
return _FinanceList(
services: _tempList,
allProviders:
provState.allProviders, // Per vedere lo storico
allModels: prodState.models,
onDelete: (index) =>
setState(() => _tempList.removeAt(index)),
onAddTap: () => setState(() => _isAddingNew = true),
);
},
);
},
),
), ),
content: AnimatedSize(
duration: const Duration(milliseconds: 300),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: _isAddingNew
? _FinanceForm(
onSave: (newFin) => setState(() {
_tempList.add(newFin);
_isAddingNew = false;
}),
onCancel: () => setState(() => _isAddingNew = false),
)
: BlocBuilder<ProvidersCubit, ProvidersState>(
builder: (context, provState) {
return BlocBuilder<ProductCubit, ProductState>(
builder: (context, prodState) {
return _FinanceList(
services: _tempList,
allProviders:
provState.allProviders, // Per vedere lo storico
allModels: prodState.models,
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"),
),
]
: null,
), ),
actions: !_isAddingNew
? [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Annulla"),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, _tempList),
child: const Text("Conferma"),
),
]
: null,
); );
} }
} }

View File

@@ -1,10 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; 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/blocs/services_cubit.dart';
import 'package:flux/features/services/models/energy_service_model.dart'; import 'package:flux/features/services/models/energy_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/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/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/energy_service_dialog.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 import 'package:flux/features/services/ui/service_form_screen/int_dialogs.dart'; // Assicurati di importare il modello
class ServicesGrid extends StatelessWidget { class ServicesGrid extends StatelessWidget {
@@ -142,8 +145,20 @@ class ServicesGrid extends StatelessWidget {
count: service.finServices.length, count: service.finServices.length,
icon: Icons.euro_symbol, icon: Icons.euro_symbol,
color: Colors.teal, color: Colors.teal,
onTap: () { onTap: () async {
// TODO: Aprire la Dialog Finanziamenti complessa final result = await showDialog<List<FinServiceModel>>(
context: context,
builder: (context) => FinanceServiceDialog(
productCubit: context.read<ProductCubit>(),
currentStoreId: service.storeId,
initialServices:
service.finServices, // Passiamo la lista attuale
),
);
if (result != null && context.mounted) {
context.read<ServicesCubit>().updateFinServices(result);
}
}, },
), ),
ActionCard( ActionCard(

View File

@@ -37,8 +37,16 @@ void main() async {
BlocProvider<ThemeBloc>( BlocProvider<ThemeBloc>(
create: (context) => ThemeBloc()..add(LoadThemeEvent()), create: (context) => ThemeBloc()..add(LoadThemeEvent()),
), ),
BlocProvider<SessionBloc>( BlocProvider<SessionBloc>(create: (_) => GetIt.I<SessionBloc>()),
create: (context) => SessionBloc()..add(AppStarted()), BlocProvider<AuthBloc>(create: (_) => AuthBloc()),
BlocProvider<CompanyBloc>(create: (_) => CompanyBloc()),
BlocProvider<StoreCubit>(create: (_) => StoreCubit()..loadStores()),
BlocProvider<CustomerCubit>(create: (_) => CustomerCubit()),
BlocProvider<ProductCubit>(create: (_) => ProductCubit()),
BlocProvider<StaffCubit>(create: (_) => StaffCubit()..loadAllStaff()),
BlocProvider<ServicesCubit>(create: (_) => ServicesCubit()),
BlocProvider<ProvidersCubit>(
create: (_) => ProvidersCubit()..loadProviders(null),
), ),
], ],
child: const FluxApp(), child: const FluxApp(),
@@ -65,6 +73,7 @@ Future<void> setupLocator() async {
getIt.registerLazySingleton<StaffRepository>(() => StaffRepository()); getIt.registerLazySingleton<StaffRepository>(() => StaffRepository());
getIt.registerLazySingleton<ServicesRepository>(() => ServicesRepository()); getIt.registerLazySingleton<ServicesRepository>(() => ServicesRepository());
getIt.registerLazySingleton<ProviderRepository>(() => ProviderRepository()); getIt.registerLazySingleton<ProviderRepository>(() => ProviderRepository());
getIt.registerSingleton<SessionBloc>(SessionBloc()..add(AppStarted()));
} }
class FluxApp extends StatefulWidget { class FluxApp extends StatefulWidget {
@@ -86,41 +95,17 @@ class _FluxAppState extends State<FluxApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return BlocBuilder<ThemeBloc, ThemeState>(
providers: [ builder: (context, state) {
BlocProvider<AuthBloc>(create: (_) => AuthBloc()), return MaterialApp.router(
BlocProvider<CompanyBloc>(create: (_) => CompanyBloc()), title: 'FLUX Gestionale',
BlocProvider<StoreCubit>( debugShowCheckedModeBanner: false,
create: (_) => StoreCubit(context.read<SessionBloc>())..loadStores(), theme: fluxLightTheme,
), darkTheme: fluxDarkTheme,
BlocProvider<CustomerCubit>(create: (_) => CustomerCubit()), themeMode: state.currentTheme.themeMode,
BlocProvider<ProductCubit>( routerConfig: _router, // Usa l'istanza mantenuta nello stato
create: (context) => ProductCubit(context.read<SessionBloc>()), );
), },
BlocProvider<StaffCubit>(
create: (_) =>
StaffCubit(context.read<SessionBloc>())..loadAllStaff(),
),
BlocProvider<ServicesCubit>(
create: (_) => ServicesCubit(context.read<SessionBloc>()),
),
BlocProvider<ProvidersCubit>(
create: (_) =>
ProvidersCubit(context.read<SessionBloc>())..loadProviders(null),
),
],
child: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state) {
return MaterialApp.router(
title: 'FLUX Gestionale',
debugShowCheckedModeBanner: false,
theme: fluxLightTheme,
darkTheme: fluxDarkTheme,
themeMode: state.currentTheme.themeMode,
routerConfig: _router, // Usa l'istanza mantenuta nello stato
);
},
),
); );
} }
} }