feat-insert-service #5
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -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",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 ---
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 +42,9 @@ class _FinanceServiceDialogState extends State<FinanceServiceDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return BlocProvider.value(
|
||||||
|
value: widget.productCubit,
|
||||||
|
child: AlertDialog(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
@@ -94,6 +98,7 @@ class _FinanceServiceDialogState extends State<FinanceServiceDialog> {
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,30 +95,7 @@ class _FluxAppState extends State<FluxApp> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return BlocBuilder<ThemeBloc, ThemeState>(
|
||||||
providers: [
|
|
||||||
BlocProvider<AuthBloc>(create: (_) => AuthBloc()),
|
|
||||||
BlocProvider<CompanyBloc>(create: (_) => CompanyBloc()),
|
|
||||||
BlocProvider<StoreCubit>(
|
|
||||||
create: (_) => StoreCubit(context.read<SessionBloc>())..loadStores(),
|
|
||||||
),
|
|
||||||
BlocProvider<CustomerCubit>(create: (_) => CustomerCubit()),
|
|
||||||
BlocProvider<ProductCubit>(
|
|
||||||
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) {
|
builder: (context, state) {
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
title: 'FLUX Gestionale',
|
title: 'FLUX Gestionale',
|
||||||
@@ -120,7 +106,6 @@ class _FluxAppState extends State<FluxApp> {
|
|||||||
routerConfig: _router, // Usa l'istanza mantenuta nello stato
|
routerConfig: _router, // Usa l'istanza mantenuta nello stato
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user