asd
This commit is contained in:
@@ -12,7 +12,7 @@ import 'package:flux/features/customers/models/customer_model.dart';
|
|||||||
import 'package:flux/features/customers/ui/customer_detail_screen.dart';
|
import 'package:flux/features/customers/ui/customer_detail_screen.dart';
|
||||||
import 'package:flux/features/customers/ui/customer_mobile_upload_screen.dart';
|
import 'package:flux/features/customers/ui/customer_mobile_upload_screen.dart';
|
||||||
import 'package:flux/features/customers/ui/customers_content.dart';
|
import 'package:flux/features/customers/ui/customers_content.dart';
|
||||||
import 'package:flux/features/home/latest_store_services/bloc/latest_store_services_bloc.dart';
|
import 'package:flux/features/home/latest_store_operations/bloc/latest_store_operations_bloc.dart';
|
||||||
import 'package:flux/features/home/ui/home_screen.dart';
|
import 'package:flux/features/home/ui/home_screen.dart';
|
||||||
import 'package:flux/features/master_data/master_data_hub_content.dart';
|
import 'package:flux/features/master_data/master_data_hub_content.dart';
|
||||||
import 'package:flux/features/master_data/products/ui/products_screen.dart';
|
import 'package:flux/features/master_data/products/ui/products_screen.dart';
|
||||||
@@ -21,11 +21,11 @@ import 'package:flux/features/master_data/staff/ui/staff_screen.dart';
|
|||||||
import 'package:flux/features/master_data/store/ui/stores_screen.dart';
|
import 'package:flux/features/master_data/store/ui/stores_screen.dart';
|
||||||
import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart';
|
import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart';
|
||||||
import 'package:flux/features/onboarding/ui/onboarding_screen.dart';
|
import 'package:flux/features/onboarding/ui/onboarding_screen.dart';
|
||||||
import 'package:flux/features/operations/blocs/service_files_bloc.dart';
|
import 'package:flux/features/operations/blocs/operation_files_bloc.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/service_form_screen.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/operation_form_screen.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/service_mobile_upload_screen.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/operation_mobile_upload_screen.dart';
|
||||||
import 'package:flux/features/operations/ui/services_screen.dart';
|
import 'package:flux/features/operations/ui/operations_screen.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/operations',
|
path: '/operations',
|
||||||
builder: (context, state) => const ServicesScreen(),
|
builder: (context, state) => const OperationsScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/customers',
|
path: '/customers',
|
||||||
@@ -171,14 +171,15 @@ class AppRouter {
|
|||||||
path: '/operation-form',
|
path: '/operation-form',
|
||||||
name: 'operation-form',
|
name: 'operation-form',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final existingService = state.extra as ServiceModel?;
|
final existingOperation = state.extra as OperationModel?;
|
||||||
final serviceId = state.uri.queryParameters['serviceId'];
|
final operationId = state.uri.queryParameters['operationId'];
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => OperationFilesBloc(
|
||||||
ServiceFilesBloc(serviceId: serviceId ?? existingService?.id),
|
operationId: operationId ?? existingOperation?.id,
|
||||||
child: ServiceFormScreen(
|
),
|
||||||
serviceId: serviceId ?? existingService?.id,
|
child: OperationFormScreen(
|
||||||
existingService: existingService,
|
operationId: operationId ?? existingOperation?.id,
|
||||||
|
existingOperation: existingOperation,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -186,13 +187,14 @@ class AppRouter {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/operation/:id/upload',
|
path: '/operation/:id/upload',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final serviceId = state.pathParameters['id']!;
|
final operationId = state.pathParameters['id']!;
|
||||||
final serviceName = state.uri.queryParameters['name'] ?? 'Pratica';
|
final operationName =
|
||||||
|
state.uri.queryParameters['name'] ?? 'Pratica';
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => ServiceFilesBloc(serviceId: serviceId),
|
create: (context) => OperationFilesBloc(operationId: operationId),
|
||||||
child: ServiceMobileUploadScreen(
|
child: OperationMobileUploadScreen(
|
||||||
serviceId: serviceId,
|
operationId: operationId,
|
||||||
serviceName: serviceName,
|
operationName: operationName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// lib/ui/common/flux_text_field.dart
|
// lib/ui/common/flux_text_field.dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/operations.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flux/core/theme/theme.dart';
|
import 'package:flux/core/theme/theme.dart';
|
||||||
|
|
||||||
class FluxTextField extends StatefulWidget {
|
class FluxTextField extends StatefulWidget {
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ extension CompanyLimits on CompanyModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int get maxServicesPerMonth {
|
int get maxOperationsPerMonth {
|
||||||
switch (subscriptionTier) {
|
switch (subscriptionTier) {
|
||||||
case SubscriptionTier.free:
|
case SubscriptionTier.free:
|
||||||
return 50;
|
return 50;
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class _CustomerSearchSheetState extends State<CustomerSearchSheet> {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.person_add),
|
icon: const Icon(Icons.person_add),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final servicesCubit = context.read<ServicesCubit>();
|
final operationsCubit = context.read<OperationsCubit>();
|
||||||
// Apriamo la dialog passando la query attuale
|
// Apriamo la dialog passando la query attuale
|
||||||
final CustomerModel? nuovoCliente = await showDialog(
|
final CustomerModel? nuovoCliente = await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -101,7 +101,7 @@ class _CustomerSearchSheetState extends State<CustomerSearchSheet> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (nuovoCliente != null) {
|
if (nuovoCliente != null) {
|
||||||
servicesCubit.updateField(
|
operationsCubit.updateField(
|
||||||
customerId: nuovoCliente.id,
|
customerId: nuovoCliente.id,
|
||||||
customerDisplayName: nuovoCliente.nome,
|
customerDisplayName: nuovoCliente.nome,
|
||||||
);
|
);
|
||||||
@@ -180,7 +180,7 @@ class _CustomerSearchSheetState extends State<CustomerSearchSheet> {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Salviamo l'ID e il nome formattato nel form dei servizi
|
// Salviamo l'ID e il nome formattato nel form dei servizi
|
||||||
context.read<ServicesCubit>().updateField(
|
context.read<OperationsCubit>().updateField(
|
||||||
customerId: customer.id,
|
customerId: customer.id,
|
||||||
customerDisplayName: displayName,
|
customerDisplayName: displayName,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||||
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
part '../../latest_store_operations/bloc/latest_store_operations_events.dart';
|
||||||
|
part '../../latest_store_operations/bloc/latest_store_operations_state.dart';
|
||||||
|
|
||||||
|
class LatestStoreOperationsBloc
|
||||||
|
extends Bloc<LatestStoreOperationsEvent, LatestStoreOperationsState> {
|
||||||
|
final _repository = GetIt.I.get<OperationsRepository>();
|
||||||
|
|
||||||
|
LatestStoreOperationsBloc()
|
||||||
|
: super(
|
||||||
|
const LatestStoreOperationsState(
|
||||||
|
status: LatestStoreOperationsStatus.initial,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
on<InitLastStoreOperationsEvent>((event, emit) async {
|
||||||
|
emit(state.copyWith(status: LatestStoreOperationsStatus.loading));
|
||||||
|
try {
|
||||||
|
// 1. Creiamo uno stream "intermedio" che idrata i dati
|
||||||
|
final hydratedStream = _repository
|
||||||
|
.getLastStoreOperationsStream(storeId: event.storeId, limit: 5)
|
||||||
|
.asyncMap((List<OperationModel> rawOperations) async {
|
||||||
|
// Questo gira ad ogni "scatto" dello stream di Supabase
|
||||||
|
List<OperationModel> fullyHydratedOperations = [];
|
||||||
|
|
||||||
|
for (OperationModel operation in rawOperations) {
|
||||||
|
// Peschiamo i dati completi (incluso il cliente)
|
||||||
|
OperationModel fullOperation = await _repository
|
||||||
|
.fetchOperationById(operation.id!);
|
||||||
|
fullyHydratedOperations.add(fullOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passiamo la lista completa allo step successivo
|
||||||
|
return fullyHydratedOperations;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Ora passiamo lo stream idratato all'emit.forEach
|
||||||
|
await emit.forEach(
|
||||||
|
hydratedStream, // Usiamo lo stream modificato!
|
||||||
|
onData: (List<OperationModel> fullyHydratedOperations) {
|
||||||
|
// Qui ora è tutto sincrono e bellissimo
|
||||||
|
return state.copyWith(
|
||||||
|
operations: fullyHydratedOperations,
|
||||||
|
status: LatestStoreOperationsStatus.success,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError: (error, stackTrace) => state.copyWith(
|
||||||
|
status: LatestStoreOperationsStatus.failure,
|
||||||
|
error: error.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: LatestStoreOperationsStatus.failure,
|
||||||
|
error: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
part of 'latest_store_operations_bloc.dart';
|
||||||
|
|
||||||
|
sealed class LatestStoreOperationsEvent extends Equatable {
|
||||||
|
const LatestStoreOperationsEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class InitLastStoreOperationsEvent extends LatestStoreOperationsEvent {
|
||||||
|
final String storeId;
|
||||||
|
|
||||||
|
const InitLastStoreOperationsEvent(this.storeId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [storeId];
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
part of 'latest_store_operations_bloc.dart';
|
||||||
|
|
||||||
|
enum LatestStoreOperationsStatus { initial, loading, success, failure }
|
||||||
|
|
||||||
|
class LatestStoreOperationsState extends Equatable {
|
||||||
|
final LatestStoreOperationsStatus status;
|
||||||
|
final String? error;
|
||||||
|
final List<OperationModel> operations;
|
||||||
|
|
||||||
|
const LatestStoreOperationsState({
|
||||||
|
required this.status,
|
||||||
|
this.error,
|
||||||
|
this.operations = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, error, operations];
|
||||||
|
|
||||||
|
LatestStoreOperationsState copyWith({
|
||||||
|
LatestStoreOperationsStatus? status,
|
||||||
|
String? error,
|
||||||
|
List<OperationModel>? operations,
|
||||||
|
}) {
|
||||||
|
return LatestStoreOperationsState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
error: error,
|
||||||
|
operations: operations ?? this.operations,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
|
import 'package:flux/core/theme/theme.dart';
|
||||||
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
|
import 'package:flux/features/home/latest_store_operations/bloc/latest_store_operations_bloc.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
class LatestStoreOperationsCard extends StatelessWidget {
|
||||||
|
const LatestStoreOperationsCard({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final currentStoreId = context.read<SessionCubit>().state.currentStore?.id;
|
||||||
|
|
||||||
|
return BlocProvider(
|
||||||
|
// 1. Creiamo il Bloc e facciamo partire subito la query
|
||||||
|
create: (context) =>
|
||||||
|
LatestStoreOperationsBloc()
|
||||||
|
..add(InitLastStoreOperationsEvent(currentStoreId ?? '')),
|
||||||
|
child: BlocListener<SessionCubit, SessionState>(
|
||||||
|
// 2. MAGIA: Se l'utente cambia negozio dalla barra in alto, riavviamo lo stream!
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.currentStore?.id != current.currentStore?.id,
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.currentStore?.id != null) {
|
||||||
|
context.read<LatestStoreOperationsBloc>().add(
|
||||||
|
InitLastStoreOperationsEvent(state.currentStore!.id!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: _LatestOperationsCardContent(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LatestOperationsCardContent extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
const color = Colors.blue;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// --- HEADER DELLA CARD ---
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.design_services_outlined,
|
||||||
|
color: color,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => context.push('/operations'),
|
||||||
|
child: Text(
|
||||||
|
context.l10n.homeLatestOperations,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: context.primaryText,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// --- CORPO DELLA CARD (LA LISTA REAL-TIME) ---
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
BlocBuilder<
|
||||||
|
LatestStoreOperationsBloc,
|
||||||
|
LatestStoreOperationsState
|
||||||
|
>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.status == LatestStoreOperationsStatus.loading ||
|
||||||
|
state.status == LatestStoreOperationsStatus.initial) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.status == LatestStoreOperationsStatus.failure) {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
"Errore di caricamento",
|
||||||
|
style: TextStyle(color: theme.colorScheme.error),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.operations.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
"Nessun servizio recente.",
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.secondaryText,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: state.operations.length,
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
height: 1,
|
||||||
|
color: theme.dividerColor.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final operation = state.operations[index];
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => context.push(
|
||||||
|
'/operation-form',
|
||||||
|
extra: operation,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Text(
|
||||||
|
operation.customerDisplayName ??
|
||||||
|
'Cliente sconosciuto',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: context.primaryText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Text(
|
||||||
|
operation.number,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: context.primaryText,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${operation.createdAt?.day}/${operation.createdAt?.month}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.secondaryText,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flux/features/operations/data/services_repository.dart';
|
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
|
|
||||||
part 'latest_store_services_events.dart';
|
|
||||||
part 'latest_store_services_state.dart';
|
|
||||||
|
|
||||||
class LatestStoreServicesBloc
|
|
||||||
extends Bloc<LatestStoreServicesEvent, LatestStoreServicesState> {
|
|
||||||
final _repository = GetIt.I.get<ServicesRepository>();
|
|
||||||
|
|
||||||
LatestStoreServicesBloc()
|
|
||||||
: super(
|
|
||||||
const LatestStoreServicesState(
|
|
||||||
status: LatestStoreServicesStatus.initial,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
on<InitLastStoreServicesEvent>((event, emit) async {
|
|
||||||
emit(state.copyWith(status: LatestStoreServicesStatus.loading));
|
|
||||||
try {
|
|
||||||
// 1. Creiamo uno stream "intermedio" che idrata i dati
|
|
||||||
final hydratedStream = _repository
|
|
||||||
.getLastStoreServicesStream(storeId: event.storeId, limit: 5)
|
|
||||||
.asyncMap((List<ServiceModel> rawServices) async {
|
|
||||||
// Questo gira ad ogni "scatto" dello stream di Supabase
|
|
||||||
List<ServiceModel> fullyHydratedServices = [];
|
|
||||||
|
|
||||||
for (ServiceModel operation in rawServices) {
|
|
||||||
// Peschiamo i dati completi (incluso il cliente)
|
|
||||||
ServiceModel fullService = await _repository.fetchServiceById(
|
|
||||||
operation.id!,
|
|
||||||
);
|
|
||||||
fullyHydratedServices.add(fullService);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passiamo la lista completa allo step successivo
|
|
||||||
return fullyHydratedServices;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Ora passiamo lo stream idratato all'emit.forEach
|
|
||||||
await emit.forEach(
|
|
||||||
hydratedStream, // Usiamo lo stream modificato!
|
|
||||||
onData: (List<ServiceModel> fullyHydratedServices) {
|
|
||||||
// Qui ora è tutto sincrono e bellissimo
|
|
||||||
return state.copyWith(
|
|
||||||
operations: fullyHydratedServices,
|
|
||||||
status: LatestStoreServicesStatus.success,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onError: (error, stackTrace) => state.copyWith(
|
|
||||||
status: LatestStoreServicesStatus.failure,
|
|
||||||
error: error.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
status: LatestStoreServicesStatus.failure,
|
|
||||||
error: e.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
part of 'latest_store_services_bloc.dart';
|
|
||||||
|
|
||||||
sealed class LatestStoreServicesEvent extends Equatable {
|
|
||||||
const LatestStoreServicesEvent();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [];
|
|
||||||
}
|
|
||||||
|
|
||||||
class InitLastStoreServicesEvent extends LatestStoreServicesEvent {
|
|
||||||
final String storeId;
|
|
||||||
|
|
||||||
const InitLastStoreServicesEvent(this.storeId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [storeId];
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
part of 'latest_store_services_bloc.dart';
|
|
||||||
|
|
||||||
enum LatestStoreServicesStatus { initial, loading, success, failure }
|
|
||||||
|
|
||||||
class LatestStoreServicesState extends Equatable {
|
|
||||||
final LatestStoreServicesStatus status;
|
|
||||||
final String? error;
|
|
||||||
final List<ServiceModel> operations;
|
|
||||||
|
|
||||||
const LatestStoreServicesState({
|
|
||||||
required this.status,
|
|
||||||
this.error,
|
|
||||||
this.operations = const [],
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [status, error, operations];
|
|
||||||
|
|
||||||
LatestStoreServicesState copyWith({
|
|
||||||
LatestStoreServicesStatus? status,
|
|
||||||
String? error,
|
|
||||||
List<ServiceModel>? operations,
|
|
||||||
}) {
|
|
||||||
return LatestStoreServicesState(
|
|
||||||
status: status ?? this.status,
|
|
||||||
error: error,
|
|
||||||
operations: operations ?? this.operations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
|
||||||
import 'package:flux/core/theme/theme.dart';
|
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
|
||||||
import 'package:flux/features/home/latest_store_services/bloc/latest_store_services_bloc.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
class LatestStoreServicesCard extends StatelessWidget {
|
|
||||||
const LatestStoreServicesCard({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final currentStoreId = context.read<SessionCubit>().state.currentStore?.id;
|
|
||||||
|
|
||||||
return BlocProvider(
|
|
||||||
// 1. Creiamo il Bloc e facciamo partire subito la query
|
|
||||||
create: (context) =>
|
|
||||||
LatestStoreServicesBloc()
|
|
||||||
..add(InitLastStoreServicesEvent(currentStoreId ?? '')),
|
|
||||||
child: BlocListener<SessionCubit, SessionState>(
|
|
||||||
// 2. MAGIA: Se l'utente cambia negozio dalla barra in alto, riavviamo lo stream!
|
|
||||||
listenWhen: (previous, current) =>
|
|
||||||
previous.currentStore?.id != current.currentStore?.id,
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.currentStore?.id != null) {
|
|
||||||
context.read<LatestStoreServicesBloc>().add(
|
|
||||||
InitLastStoreServicesEvent(state.currentStore!.id!),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: _LatestServicesCardContent(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LatestServicesCardContent extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
const color = Colors.blue;
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
elevation: 0,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// --- HEADER DELLA CARD ---
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withValues(alpha: 0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.design_services_outlined,
|
|
||||||
color: color,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () => context.push('/operations'),
|
|
||||||
child: Text(
|
|
||||||
context.l10n.homeLatestServices,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
color: context.primaryText,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// --- CORPO DELLA CARD (LA LISTA REAL-TIME) ---
|
|
||||||
Expanded(
|
|
||||||
child: BlocBuilder<LatestStoreServicesBloc, LatestStoreServicesState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.status == LatestStoreServicesStatus.loading ||
|
|
||||||
state.status == LatestStoreServicesStatus.initial) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.status == LatestStoreServicesStatus.failure) {
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
"Errore di caricamento",
|
|
||||||
style: TextStyle(color: theme.colorScheme.error),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.operations.isEmpty) {
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
"Nessun servizio recente.",
|
|
||||||
style: TextStyle(
|
|
||||||
color: context.secondaryText,
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView.separated(
|
|
||||||
itemCount: state.operations.length,
|
|
||||||
separatorBuilder: (context, index) => Divider(
|
|
||||||
height: 1,
|
|
||||||
color: theme.dividerColor.withValues(alpha: 0.3),
|
|
||||||
),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final operation = state.operations[index];
|
|
||||||
return InkWell(
|
|
||||||
onTap: () =>
|
|
||||||
context.push('/operation-form', extra: operation),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 5,
|
|
||||||
child: Text(
|
|
||||||
operation.customerDisplayName ??
|
|
||||||
'Cliente sconosciuto',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: context.primaryText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 5,
|
|
||||||
child: Text(
|
|
||||||
operation.number,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.primaryText,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"${operation.createdAt?.day}/${operation.createdAt?.month}",
|
|
||||||
style: TextStyle(
|
|
||||||
color: context.secondaryText,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/core/theme/theme.dart';
|
import 'package:flux/core/theme/theme.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/features/home/latest_store_services/ui/latest_store_services_card.dart';
|
import 'package:flux/features/home/latest_store_operations/ui/latest_store_operations_card.dart';
|
||||||
import 'package:flux/features/home/ui/quick_actions_widget.dart';
|
import 'package:flux/features/home/ui/quick_actions_widget.dart';
|
||||||
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -76,10 +76,10 @@ class HomeScreen extends StatelessWidget {
|
|||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
context: context,
|
context: context,
|
||||||
),
|
),
|
||||||
LatestStoreServicesCard(),
|
LatestStoreOperationsCard(),
|
||||||
|
|
||||||
_buildDashboardWidget(
|
_buildDashboardWidget(
|
||||||
title: context.l10n.homeLatestServiceTickets,
|
title: context.l10n.homeLatestOperationTickets,
|
||||||
icon: Icons.support_agent_outlined,
|
icon: Icons.support_agent_outlined,
|
||||||
color: Colors.purple,
|
color: Colors.purple,
|
||||||
context: context,
|
context: context,
|
||||||
@@ -181,7 +181,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
QuickActionButton(
|
QuickActionButton(
|
||||||
icon: Icons.add,
|
icon: Icons.add,
|
||||||
label: context.l10n.commonService,
|
label: context.l10n.commonOperation,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Entriamo nel form! Nessun parametro extra = Nuovo Servizio
|
// Entriamo nel form! Nessun parametro extra = Nuovo Servizio
|
||||||
@@ -191,7 +191,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
QuickActionButton(
|
QuickActionButton(
|
||||||
icon: Icons.handyman,
|
icon: Icons.handyman,
|
||||||
label: context.l10n.homeNewServiceTicket,
|
label: context.l10n.homeNewOperationTicket,
|
||||||
color: Colors.redAccent,
|
color: Colors.redAccent,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: Quando avrai la rotta per la nuova assistenza
|
// TODO: Quando avrai la rotta per la nuova assistenza
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/operations.dart'; // <-- IMPORTANTE per i formatter
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flux/core/widgets/flux_text_field.dart';
|
import 'package:flux/core/widgets/flux_text_field.dart';
|
||||||
import 'package:flux/features/master_data/store/models/store_model.dart';
|
import 'package:flux/features/master_data/store/models/store_model.dart';
|
||||||
|
|||||||
242
lib/features/operations/blocs/operation_files_bloc.dart
Normal file
242
lib/features/operations/blocs/operation_files_bloc.dart
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
|
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||||
|
import 'package:flux/features/operations/models/operation_file_model.dart';
|
||||||
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
part 'operation_files_events.dart';
|
||||||
|
part 'operation_files_state.dart';
|
||||||
|
|
||||||
|
class OperationFilesBloc
|
||||||
|
extends Bloc<OperationFilesEvent, OperationFilesState> {
|
||||||
|
final _repository = GetIt.I.get<OperationsRepository>();
|
||||||
|
final String? operationId;
|
||||||
|
|
||||||
|
OperationFilesBloc({this.operationId})
|
||||||
|
: super(
|
||||||
|
OperationFilesState(
|
||||||
|
status: OperationFilesStatus.initial,
|
||||||
|
operationId: operationId,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
on<OperationsavedEvent>(_onOperationsaved);
|
||||||
|
on<LoadOperationFilesEvent>(_onLoadOperationFiles);
|
||||||
|
on<AddOperationFilesEvent>(_onAddOperationFiles);
|
||||||
|
on<UploadOperationFilesEvent>(_onUploadOperationFiles);
|
||||||
|
on<UploadMultipleOperationFilesEvent>(_onUploadMultipleOperationFiles);
|
||||||
|
on<DeleteOperationFilesEvent>(_onDeleteOperationFiles);
|
||||||
|
on<ToggleOperationFileSelectionEvent>(_onToggleOperationFileSelection);
|
||||||
|
// Se il BLoC nasce con un ID, accendiamo subito lo stream!
|
||||||
|
if (operationId != null) {
|
||||||
|
add(LoadOperationFilesEvent(operationId: operationId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onOperationsaved(
|
||||||
|
OperationsavedEvent event,
|
||||||
|
Emitter<OperationFilesState> emit,
|
||||||
|
) {
|
||||||
|
// 1. Aggiorniamo l'ID nello stato
|
||||||
|
// 2. PIALLIAMO i file locali: ormai sono partiti per Supabase!
|
||||||
|
// Così la UI si pulisce all'istante e aspetta quelli remoti.
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
operationId: event.operationId,
|
||||||
|
localFiles: [], // <-- LA MAGIA ANTI-DUPLICATI
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Lanciamo il caricamento
|
||||||
|
add(LoadOperationFilesEvent(operationId: event.operationId));
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onLoadOperationFiles(
|
||||||
|
LoadOperationFilesEvent event,
|
||||||
|
Emitter<OperationFilesState> emit,
|
||||||
|
) async {
|
||||||
|
// Usiamo l'ID dell'evento, e se non c'è usiamo quello dello stato
|
||||||
|
final currentId = event.operationId ?? state.operationId;
|
||||||
|
|
||||||
|
if (currentId != null) {
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.loading));
|
||||||
|
|
||||||
|
await emit.forEach(
|
||||||
|
_repository.getOperationFilesStream(
|
||||||
|
currentId,
|
||||||
|
), // <-- Usiamo l'ID corretto!
|
||||||
|
onData: (data) => state.copyWith(
|
||||||
|
status: OperationFilesStatus.success,
|
||||||
|
remoteFiles: data,
|
||||||
|
),
|
||||||
|
onError: (error, stackTrace) => state.copyWith(
|
||||||
|
status: OperationFilesStatus.failure,
|
||||||
|
error: error.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAddOperationFiles(
|
||||||
|
AddOperationFilesEvent event,
|
||||||
|
Emitter<OperationFilesState> emit,
|
||||||
|
) async {
|
||||||
|
final currentId = state.operationId;
|
||||||
|
// BIVIO 1: PRATICA NUOVA (Nessun ID)
|
||||||
|
if (currentId == null) {
|
||||||
|
// Mettiamo i file nel "parcheggio" locale dello State
|
||||||
|
final newLocalFiles = event.files.map((file) {
|
||||||
|
return OperationFileModel(
|
||||||
|
id: null,
|
||||||
|
operationId: operationId ?? '',
|
||||||
|
name: file.name.fileNameWithoutExtension(),
|
||||||
|
extension: file.name.fileExtension(),
|
||||||
|
storagePath: '',
|
||||||
|
fileSize: file.size,
|
||||||
|
localBytes: file.bytes,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
final List<OperationFileModel> updatedLocalFiles = [
|
||||||
|
...state.localFiles,
|
||||||
|
...newLocalFiles,
|
||||||
|
];
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
localFiles: updatedLocalFiles,
|
||||||
|
status: OperationFilesStatus.success,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID)
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.uploading));
|
||||||
|
try {
|
||||||
|
// Logica identica a quella che abbiamo fatto per i clienti
|
||||||
|
for (var file in event.files) {
|
||||||
|
await _repository.uploadAndRegisterOperationFile(
|
||||||
|
operationId: operationId!,
|
||||||
|
pickedFile: file,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.success));
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: OperationFilesStatus.failure,
|
||||||
|
error: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onUploadOperationFiles(
|
||||||
|
UploadOperationFilesEvent event,
|
||||||
|
Emitter<OperationFilesState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.pickedFiles == null && event.photos == null) return;
|
||||||
|
if (event.pickedFiles!.isEmpty && event.photos!.isEmpty) return;
|
||||||
|
|
||||||
|
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.uploading));
|
||||||
|
try {
|
||||||
|
// Logica identica a quella che abbiamo fatto per i clienti
|
||||||
|
if (event.pickedFiles != null && event.pickedFiles!.isNotEmpty) {
|
||||||
|
for (var file in event.pickedFiles!) {
|
||||||
|
await _repository.uploadAndRegisterOperationFile(
|
||||||
|
operationId: state.operationId!,
|
||||||
|
pickedFile: file,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.success));
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: OperationFilesStatus.failure,
|
||||||
|
error: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onUploadMultipleOperationFiles(
|
||||||
|
UploadMultipleOperationFilesEvent event,
|
||||||
|
Emitter<OperationFilesState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.files.isEmpty) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: OperationFilesStatus.failure,
|
||||||
|
error: "Nessun file selezionato",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.uploading, error: null));
|
||||||
|
try {
|
||||||
|
// 2. Creiamo una lista di "Promesse" (Futures) per il repository
|
||||||
|
final List<Future<void>> uploadTasks = [];
|
||||||
|
for (var file in event.files) {
|
||||||
|
// Aggiungiamo il task alla lista, ma NON usiamo await qui dentro!
|
||||||
|
uploadTasks.add(
|
||||||
|
_repository.uploadAndRegisterOperationFile(
|
||||||
|
operationId: state.operationId!,
|
||||||
|
pickedFile: file,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 3. ESECUZIONE PARALLELA!
|
||||||
|
// Aspettiamo che tutti i file siano caricati contemporaneamente.
|
||||||
|
await Future.wait(uploadTasks);
|
||||||
|
// 4. GRAN FINALE: Tutto caricato, emettiamo il success!
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.success));
|
||||||
|
} catch (e) {
|
||||||
|
// Se anche un solo file fallisce, catturiamo l'errore
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: OperationFilesStatus.failure,
|
||||||
|
error: "Errore durante l'upload multiplo: $e",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onDeleteOperationFiles(
|
||||||
|
DeleteOperationFilesEvent event,
|
||||||
|
Emitter<OperationFilesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: OperationFilesStatus.loading));
|
||||||
|
try {
|
||||||
|
await _repository.deleteOperationFiles(state.selectedFiles);
|
||||||
|
emit(
|
||||||
|
state.copyWith(status: OperationFilesStatus.success, selectedFiles: []),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: OperationFilesStatus.failure,
|
||||||
|
error: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onToggleOperationFileSelection(
|
||||||
|
ToggleOperationFileSelectionEvent event,
|
||||||
|
Emitter<OperationFilesState> emit,
|
||||||
|
) {
|
||||||
|
List<OperationFileModel> selectedFiles = List.from(state.selectedFiles);
|
||||||
|
if (selectedFiles.contains(event.file)) {
|
||||||
|
selectedFiles.remove(event.file);
|
||||||
|
} else {
|
||||||
|
selectedFiles.add(event.file);
|
||||||
|
}
|
||||||
|
emit(state.copyWith(selectedFiles: selectedFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/features/operations/blocs/operation_files_events.dart
Normal file
56
lib/features/operations/blocs/operation_files_events.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
part of 'operation_files_bloc.dart';
|
||||||
|
|
||||||
|
abstract class OperationFilesEvent extends Equatable {
|
||||||
|
const OperationFilesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OperationsavedEvent extends OperationFilesEvent {
|
||||||
|
final String operationId;
|
||||||
|
const OperationsavedEvent(this.operationId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [operationId];
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadOperationFilesEvent extends OperationFilesEvent {
|
||||||
|
final String? operationId;
|
||||||
|
final OperationModel? operation;
|
||||||
|
const LoadOperationFilesEvent({this.operationId, this.operation});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [operationId, operation];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddOperationFilesEvent extends OperationFilesEvent {
|
||||||
|
final List<PlatformFile> files;
|
||||||
|
const AddOperationFilesEvent(this.files);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [files];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadOperationFilesEvent extends OperationFilesEvent {
|
||||||
|
final List<PlatformFile>? pickedFiles;
|
||||||
|
final List<File>? photos;
|
||||||
|
const UploadOperationFilesEvent({this.pickedFiles, this.photos});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [pickedFiles, photos];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadMultipleOperationFilesEvent extends OperationFilesEvent {
|
||||||
|
final List<PlatformFile> files;
|
||||||
|
const UploadMultipleOperationFilesEvent(this.files);
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [files];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteOperationFilesEvent extends OperationFilesEvent {}
|
||||||
|
|
||||||
|
class ToggleOperationFileSelectionEvent extends OperationFilesEvent {
|
||||||
|
final OperationFileModel file;
|
||||||
|
const ToggleOperationFileSelectionEvent(this.file);
|
||||||
|
}
|
||||||
52
lib/features/operations/blocs/operation_files_state.dart
Normal file
52
lib/features/operations/blocs/operation_files_state.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
part of 'operation_files_bloc.dart';
|
||||||
|
|
||||||
|
enum OperationFilesStatus { initial, loading, uploading, success, failure }
|
||||||
|
|
||||||
|
class OperationFilesState extends Equatable {
|
||||||
|
const OperationFilesState({
|
||||||
|
this.operationId,
|
||||||
|
required this.status,
|
||||||
|
this.error,
|
||||||
|
this.localFiles = const [],
|
||||||
|
this.remoteFiles = const [],
|
||||||
|
this.selectedFiles = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? operationId;
|
||||||
|
final OperationFilesStatus status;
|
||||||
|
final String? error;
|
||||||
|
final List<OperationFileModel> localFiles;
|
||||||
|
final List<OperationFileModel> remoteFiles;
|
||||||
|
|
||||||
|
final List<OperationFileModel> selectedFiles;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
operationId,
|
||||||
|
status,
|
||||||
|
error,
|
||||||
|
localFiles,
|
||||||
|
remoteFiles,
|
||||||
|
selectedFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
List<OperationFileModel> get allFiles => [...remoteFiles, ...localFiles];
|
||||||
|
|
||||||
|
OperationFilesState copyWith({
|
||||||
|
String? operationId,
|
||||||
|
OperationFilesStatus? status,
|
||||||
|
String? error,
|
||||||
|
List<OperationFileModel>? localFiles,
|
||||||
|
List<OperationFileModel>? remoteFiles,
|
||||||
|
List<OperationFileModel>? selectedFiles,
|
||||||
|
}) {
|
||||||
|
return OperationFilesState(
|
||||||
|
operationId: operationId ?? this.operationId,
|
||||||
|
status: status ?? this.status,
|
||||||
|
error: error,
|
||||||
|
localFiles: localFiles ?? this.localFiles,
|
||||||
|
remoteFiles: remoteFiles ?? this.remoteFiles,
|
||||||
|
selectedFiles: selectedFiles ?? this.selectedFiles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,50 +4,51 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/features/operations/data/services_repository.dart';
|
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||||
import 'package:flux/features/operations/models/energy_service_model.dart';
|
import 'package:flux/features/operations/models/energy_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/entertainment_service_model.dart';
|
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/fin_service_model.dart';
|
import 'package:flux/features/operations/models/fin_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/service_file_model.dart';
|
import 'package:flux/features/operations/models/operation_file_model.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
part 'services_state.dart';
|
part 'operations_state.dart';
|
||||||
|
|
||||||
class ServicesCubit extends Cubit<ServicesState> {
|
class OperationsCubit extends Cubit<OperationsState> {
|
||||||
final ServicesRepository _repository = GetIt.I<ServicesRepository>();
|
final OperationsRepository _repository = GetIt.I<OperationsRepository>();
|
||||||
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
ServicesCubit() : super(const ServicesState(status: ServicesStatus.initial));
|
OperationsCubit()
|
||||||
|
: super(const OperationsState(status: OperationsStatus.initial));
|
||||||
|
|
||||||
// --- CARICAMENTO E PAGINAZIONE ---
|
// --- CARICAMENTO E PAGINAZIONE ---
|
||||||
|
|
||||||
Future<void> loadServices({bool refresh = false}) async {
|
Future<void> loadOperations({bool refresh = false}) async {
|
||||||
// Se stiamo già caricando, evitiamo chiamate doppie
|
// Se stiamo già caricando, evitiamo chiamate doppie
|
||||||
if (state.status == ServicesStatus.loading) return;
|
if (state.status == OperationsStatus.loading) return;
|
||||||
|
|
||||||
// Se non è un refresh e abbiamo già raggiunto la fine dei dati, ci fermiamo
|
// Se non è un refresh e abbiamo già raggiunto la fine dei dati, ci fermiamo
|
||||||
if (!refresh && state.hasReachedMax) return;
|
if (!refresh && state.hasReachedMax) return;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ServicesStatus.loading,
|
status: OperationsStatus.loading,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
// Se è un refresh, svuotiamo la lista attuale per mostrare lo shimmer/loading
|
// Se è un refresh, svuotiamo la lista attuale per mostrare lo shimmer/loading
|
||||||
allServices: refresh ? [] : state.allServices,
|
allOperations: refresh ? [] : state.allOperations,
|
||||||
hasReachedMax: refresh ? false : state.hasReachedMax,
|
hasReachedMax: refresh ? false : state.hasReachedMax,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final currentOffset = refresh ? 0 : state.allServices.length;
|
final currentOffset = refresh ? 0 : state.allOperations.length;
|
||||||
final companyId = _sessionCubit.state.company?.id;
|
final companyId = _sessionCubit.state.company?.id;
|
||||||
|
|
||||||
if (companyId == null) {
|
if (companyId == null) {
|
||||||
throw Exception("Company ID non trovato nella sessione");
|
throw Exception("Company ID non trovato nella sessione");
|
||||||
}
|
}
|
||||||
|
|
||||||
final newServices = await _repository.fetchServices(
|
final newOperations = await _repository.fetchOperations(
|
||||||
companyId: companyId,
|
companyId: companyId,
|
||||||
offset: currentOffset,
|
offset: currentOffset,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
@@ -56,21 +57,21 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Se ricevi meno record del limite, significa che non ce ne sono altri sul DB
|
// Se ricevi meno record del limite, significa che non ce ne sono altri sul DB
|
||||||
final bool reachedMax = newServices.length < 50;
|
final bool reachedMax = newOperations.length < 50;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ServicesStatus.ready,
|
status: OperationsStatus.ready,
|
||||||
allServices: refresh
|
allOperations: refresh
|
||||||
? newServices
|
? newOperations
|
||||||
: [...state.allServices, ...newServices],
|
: [...state.allOperations, ...newOperations],
|
||||||
hasReachedMax: reachedMax,
|
hasReachedMax: reachedMax,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ServicesStatus.failure,
|
status: OperationsStatus.failure,
|
||||||
errorMessage: "Errore nel caricamento servizi: $e",
|
errorMessage: "Errore nel caricamento servizi: $e",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -87,51 +88,51 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
dateRange: range ?? state.dateRange,
|
dateRange: range ?? state.dateRange,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
loadServices(refresh: true);
|
loadOperations(refresh: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pulisce tutti i filtri
|
/// Pulisce tutti i filtri
|
||||||
void clearFilters() {
|
void clearFilters() {
|
||||||
emit(state.copyWith(query: '', dateRange: null));
|
emit(state.copyWith(query: '', dateRange: null));
|
||||||
loadServices(refresh: true);
|
loadOperations(refresh: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GESTIONE BOZZA (DRAFT) ---
|
// --- GESTIONE BOZZA (DRAFT) ---
|
||||||
|
|
||||||
/// Inizializza un nuovo servizio o ne carica uno esistente per la modifica
|
/// Inizializza un nuovo servizio o ne carica uno esistente per la modifica
|
||||||
void initServiceForm({
|
void initOperationForm({
|
||||||
ServiceModel? existingService,
|
OperationModel? existingOperation,
|
||||||
String? serviceId,
|
String? operationId,
|
||||||
}) async {
|
}) async {
|
||||||
if (existingService != null) {
|
if (existingOperation != null) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: existingService,
|
currentOperation: existingOperation,
|
||||||
status: ServicesStatus.ready,
|
status: OperationsStatus.ready,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (serviceId != null) {
|
} else if (operationId != null) {
|
||||||
ServiceModel? serviceModel = state.allServices.firstWhereOrNull(
|
OperationModel? operationModel = state.allOperations.firstWhereOrNull(
|
||||||
(s) => s.id == serviceId,
|
(s) => s.id == operationId,
|
||||||
);
|
);
|
||||||
serviceModel ??= await _repository.fetchServiceById(serviceId);
|
operationModel ??= await _repository.fetchOperationById(operationId);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: serviceModel,
|
currentOperation: operationModel,
|
||||||
status: ServicesStatus.ready,
|
status: OperationsStatus.ready,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Crea un template vuoto con lo store di default (se disponibile)
|
// Crea un template vuoto con lo store di default (se disponibile)
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: ServiceModel(
|
currentOperation: OperationModel(
|
||||||
storeId: _sessionCubit.state.currentStore?.id ?? '',
|
storeId: _sessionCubit.state.currentStore?.id ?? '',
|
||||||
number: '', // Sarà compilato dall'utente
|
number: '', // Sarà compilato dall'utente
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
companyId: _sessionCubit.state.company!.id!,
|
companyId: _sessionCubit.state.company!.id!,
|
||||||
),
|
),
|
||||||
status: ServicesStatus.ready,
|
status: OperationsStatus.ready,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -151,9 +152,9 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
String? customerId,
|
String? customerId,
|
||||||
String? customerDisplayName,
|
String? customerDisplayName,
|
||||||
}) {
|
}) {
|
||||||
if (state.currentService == null) return;
|
if (state.currentOperation == null) return;
|
||||||
|
|
||||||
final updated = state.currentService!.copyWith(
|
final updated = state.currentOperation!.copyWith(
|
||||||
al: al,
|
al: al,
|
||||||
mnp: mnp,
|
mnp: mnp,
|
||||||
nip: nip,
|
nip: nip,
|
||||||
@@ -167,34 +168,38 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
customerDisplayName: customerDisplayName,
|
customerDisplayName: customerDisplayName,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(state.copyWith(currentService: updated));
|
emit(state.copyWith(currentOperation: updated));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GESTIONE MODULI COMPLESSI ---
|
// --- GESTIONE MODULI COMPLESSI ---
|
||||||
|
|
||||||
void updateEnergyServices(List<EnergyServiceModel> energyList) {
|
void updateEnergyOperations(List<EnergyOperationModel> energyList) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: state.currentService?.copyWith(
|
currentOperation: state.currentOperation?.copyWith(
|
||||||
energyServices: energyList,
|
energyOperations: energyList,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateFinServices(List<FinServiceModel> finList) {
|
void updateFinOperations(List<FinOperationModel> finList) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: state.currentService?.copyWith(finServices: finList),
|
currentOperation: state.currentOperation?.copyWith(
|
||||||
|
finOperations: finList,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateEntertainmentServices(List<EntertainmentServiceModel> entList) {
|
void updateEntertainmentOperations(
|
||||||
|
List<EntertainmentOperationModel> entList,
|
||||||
|
) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: state.currentService?.copyWith(
|
currentOperation: state.currentOperation?.copyWith(
|
||||||
entertainmentServices: entList,
|
entertainmentOperations: entList,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -202,36 +207,40 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
|
|
||||||
// --- PERSISTENZA ---
|
// --- PERSISTENZA ---
|
||||||
|
|
||||||
Future<void> saveCurrentService({
|
Future<void> saveCurrentOperation({
|
||||||
required bool isBozza,
|
required bool isBozza,
|
||||||
bool shouldPop = true,
|
bool shouldPop = true,
|
||||||
List<ServiceFileModel>? files,
|
List<OperationFileModel>? files,
|
||||||
}) async {
|
}) async {
|
||||||
if (state.currentService == null) return;
|
if (state.currentOperation == null) return;
|
||||||
|
|
||||||
emit(state.copyWith(status: ServicesStatus.saving, errorMessage: null));
|
emit(state.copyWith(status: OperationsStatus.saving, errorMessage: null));
|
||||||
try {
|
try {
|
||||||
// 1. Aggiorniamo il flag bozza in base a quale pulsante ha premuto l'utente
|
// 1. Aggiorniamo il flag bozza in base a quale pulsante ha premuto l'utente
|
||||||
final serviceToSave = state.currentService!.copyWith(
|
final operationToSave = state.currentOperation!.copyWith(
|
||||||
isBozza: isBozza,
|
isBozza: isBozza,
|
||||||
files: files,
|
files: files,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Salvataggio corazzato
|
// 2. Salvataggio corazzato
|
||||||
final updatedService = await _repository.saveFullService(serviceToSave);
|
final updatedOperation = await _repository.saveFullOperation(
|
||||||
|
operationToSave,
|
||||||
|
);
|
||||||
|
|
||||||
// 3. Reset e ricaricamento
|
// 3. Reset e ricaricamento
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: shouldPop ? ServicesStatus.saved : ServicesStatus.savedNoPop,
|
status: shouldPop
|
||||||
currentService: shouldPop ? null : updatedService,
|
? OperationsStatus.saved
|
||||||
|
: OperationsStatus.savedNoPop,
|
||||||
|
currentOperation: shouldPop ? null : updatedOperation,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await loadServices(refresh: true);
|
await loadOperations(refresh: true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ServicesStatus.failure,
|
status: OperationsStatus.failure,
|
||||||
errorMessage: e.toString(),
|
errorMessage: e.toString(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -242,9 +251,9 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
|
|
||||||
void addAttachments(List<PlatformFile> files) {
|
void addAttachments(List<PlatformFile> files) {
|
||||||
final newAttachments = files.map((file) {
|
final newAttachments = files.map((file) {
|
||||||
return ServiceFileModel(
|
return OperationFileModel(
|
||||||
id: null, // Meglio null se non è su DB
|
id: null, // Meglio null se non è su DB
|
||||||
serviceId: state.currentService?.id ?? '',
|
operationId: state.currentOperation?.id ?? '',
|
||||||
name: file.name.fileNameWithoutExtension(),
|
name: file.name.fileNameWithoutExtension(),
|
||||||
extension: file.name.fileExtension(),
|
extension: file.name.fileExtension(),
|
||||||
storagePath: '',
|
storagePath: '',
|
||||||
@@ -255,44 +264,46 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
// Creiamo una nuova lista pulita
|
// Creiamo una nuova lista pulita
|
||||||
final List<ServiceFileModel> updatedList = [
|
final List<OperationFileModel> updatedList = [
|
||||||
...(state.currentService?.files ?? []),
|
...(state.currentOperation?.files ?? []),
|
||||||
...newAttachments,
|
...newAttachments,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Emettiamo lo stato assicurandoci che il ServiceModel venga clonato
|
// Emettiamo lo stato assicurandoci che il OperationModel venga clonato
|
||||||
if (state.currentService != null) {
|
if (state.currentOperation != null) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: state.currentService!.copyWith(files: updatedList),
|
currentOperation: state.currentOperation!.copyWith(
|
||||||
|
files: updatedList,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeAttachment(int index) {
|
void removeAttachment(int index) {
|
||||||
if (state.currentService == null) return;
|
if (state.currentOperation == null) return;
|
||||||
|
|
||||||
final updatedList = List<ServiceFileModel>.from(
|
final updatedList = List<OperationFileModel>.from(
|
||||||
state.currentService!.files,
|
state.currentOperation!.files,
|
||||||
);
|
);
|
||||||
updatedList.removeAt(index);
|
updatedList.removeAt(index);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: state.currentService?.copyWith(files: updatedList),
|
currentOperation: state.currentOperation?.copyWith(files: updatedList),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveAndCopyFileToCustomer(List<ServiceFileModel> selectedFiles) async {
|
void saveAndCopyFileToCustomer(List<OperationFileModel> selectedFiles) async {
|
||||||
final currentService = state.currentService;
|
final currentOperation = state.currentOperation;
|
||||||
|
|
||||||
// 1. Check di sicurezza: se non c'è il cliente, non sappiamo dove copiare
|
// 1. Check di sicurezza: se non c'è il cliente, non sappiamo dove copiare
|
||||||
if (currentService == null || currentService.customerId == null) {
|
if (currentOperation == null || currentOperation.customerId == null) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ServicesStatus.failure,
|
status: OperationsStatus.failure,
|
||||||
errorMessage:
|
errorMessage:
|
||||||
"Impossibile copiare: nessun cliente associato alla pratica.",
|
"Impossibile copiare: nessun cliente associato alla pratica.",
|
||||||
),
|
),
|
||||||
@@ -300,19 +311,21 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(status: ServicesStatus.loading));
|
emit(state.copyWith(status: OperationsStatus.loading));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 2. SALVATAGGIO CORAZZATO
|
// 2. SALVATAGGIO CORAZZATO
|
||||||
// Chiamiamo il repo e otteniamo la pratica con TUTTI i file ora dotati di ID e storagePath
|
// Chiamiamo il repo e otteniamo la pratica con TUTTI i file ora dotati di ID e storagePath
|
||||||
final updatedService = await _repository.saveFullService(currentService);
|
final updatedOperation = await _repository.saveFullOperation(
|
||||||
|
currentOperation,
|
||||||
|
);
|
||||||
|
|
||||||
// 3. COPIA RELAZIONALE
|
// 3. COPIA RELAZIONALE
|
||||||
// Per ogni file che l'utente ha selezionato nella UI, cerchiamo la sua versione
|
// Per ogni file che l'utente ha selezionato nella UI, cerchiamo la sua versione
|
||||||
// "ufficiale" (quella con lo storagePath) nel modello appena tornato dal DB.
|
// "ufficiale" (quella con lo storagePath) nel modello appena tornato dal DB.
|
||||||
for (var selectedFile in selectedFiles) {
|
for (var selectedFile in selectedFiles) {
|
||||||
// Cerchiamo il match nel modello aggiornato
|
// Cerchiamo il match nel modello aggiornato
|
||||||
final persistedFile = updatedService.files.firstWhere(
|
final persistedFile = updatedOperation.files.firstWhere(
|
||||||
(f) =>
|
(f) =>
|
||||||
f.name == selectedFile.name &&
|
f.name == selectedFile.name &&
|
||||||
f.extension == selectedFile.extension,
|
f.extension == selectedFile.extension,
|
||||||
@@ -324,7 +337,7 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
// Creiamo il link nel database del cliente
|
// Creiamo il link nel database del cliente
|
||||||
await _repository.copyFileToCustomer(
|
await _repository.copyFileToCustomer(
|
||||||
file: persistedFile,
|
file: persistedFile,
|
||||||
customerId: currentService.customerId!,
|
customerId: currentOperation.customerId!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,14 +345,14 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
// Aggiorniamo il Cubit con il servizio salvato così la UI mostra i file come "Remoti"
|
// Aggiorniamo il Cubit con il servizio salvato così la UI mostra i file come "Remoti"
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ServicesStatus.success,
|
status: OperationsStatus.success,
|
||||||
currentService: updatedService,
|
currentOperation: updatedOperation,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ServicesStatus.failure,
|
status: OperationsStatus.failure,
|
||||||
errorMessage: "Errore durante il salvataggio e copia: $e",
|
errorMessage: "Errore durante il salvataggio e copia: $e",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
part of 'operations_cubit.dart';
|
part of 'operations_cubit.dart';
|
||||||
|
|
||||||
enum ServicesStatus {
|
enum OperationsStatus {
|
||||||
initial,
|
initial,
|
||||||
loading,
|
loading,
|
||||||
ready,
|
ready,
|
||||||
@@ -11,20 +11,20 @@ enum ServicesStatus {
|
|||||||
failure,
|
failure,
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServicesState extends Equatable {
|
class OperationsState extends Equatable {
|
||||||
final ServicesStatus status;
|
final OperationsStatus status;
|
||||||
final List<ServiceModel> allServices;
|
final List<OperationModel> allOperations;
|
||||||
final ServiceModel? currentService; // La bozza che stiamo editando
|
final OperationModel? currentOperation; // La bozza che stiamo editando
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
final String query;
|
final String query;
|
||||||
final DateTimeRange? dateRange;
|
final DateTimeRange? dateRange;
|
||||||
final bool hasReachedMax;
|
final bool hasReachedMax;
|
||||||
final bool isSavingDraft;
|
final bool isSavingDraft;
|
||||||
|
|
||||||
const ServicesState({
|
const OperationsState({
|
||||||
required this.status,
|
required this.status,
|
||||||
this.allServices = const [],
|
this.allOperations = const [],
|
||||||
this.currentService,
|
this.currentOperation,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
this.query = '',
|
this.query = '',
|
||||||
this.dateRange,
|
this.dateRange,
|
||||||
@@ -32,20 +32,20 @@ class ServicesState extends Equatable {
|
|||||||
this.isSavingDraft = false,
|
this.isSavingDraft = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
ServicesState copyWith({
|
OperationsState copyWith({
|
||||||
ServicesStatus? status,
|
OperationsStatus? status,
|
||||||
List<ServiceModel>? allServices,
|
List<OperationModel>? allOperations,
|
||||||
ServiceModel? currentService,
|
OperationModel? currentOperation,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
String? query,
|
String? query,
|
||||||
DateTimeRange? dateRange,
|
DateTimeRange? dateRange,
|
||||||
bool? hasReachedMax,
|
bool? hasReachedMax,
|
||||||
bool? isSavingDraft,
|
bool? isSavingDraft,
|
||||||
}) {
|
}) {
|
||||||
return ServicesState(
|
return OperationsState(
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
allServices: allServices ?? this.allServices,
|
allOperations: allOperations ?? this.allOperations,
|
||||||
currentService: currentService ?? this.currentService,
|
currentOperation: currentOperation ?? this.currentOperation,
|
||||||
errorMessage: errorMessage,
|
errorMessage: errorMessage,
|
||||||
query: query ?? this.query,
|
query: query ?? this.query,
|
||||||
dateRange: dateRange ?? this.dateRange,
|
dateRange: dateRange ?? this.dateRange,
|
||||||
@@ -57,8 +57,8 @@ class ServicesState extends Equatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
status,
|
status,
|
||||||
allServices,
|
allOperations,
|
||||||
currentService,
|
currentOperation,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
query,
|
query,
|
||||||
dateRange,
|
dateRange,
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
|
||||||
import 'package:flux/features/operations/data/services_repository.dart';
|
|
||||||
import 'package:flux/features/operations/models/service_file_model.dart';
|
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
|
|
||||||
part 'service_files_events.dart';
|
|
||||||
part 'service_files_state.dart';
|
|
||||||
|
|
||||||
class ServiceFilesBloc extends Bloc<ServiceFilesEvent, ServiceFilesState> {
|
|
||||||
final _repository = GetIt.I.get<ServicesRepository>();
|
|
||||||
final String? serviceId;
|
|
||||||
|
|
||||||
ServiceFilesBloc({this.serviceId})
|
|
||||||
: super(
|
|
||||||
ServiceFilesState(
|
|
||||||
status: ServiceFilesStatus.initial,
|
|
||||||
serviceId: serviceId,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
on<ServiceSavedEvent>(_onServiceSaved);
|
|
||||||
on<LoadServiceFilesEvent>(_onLoadServiceFiles);
|
|
||||||
on<AddServiceFilesEvent>(_onAddServiceFiles);
|
|
||||||
on<UploadServiceFilesEvent>(_onUploadServiceFiles);
|
|
||||||
on<UploadMultipleServiceFilesEvent>(_onUploadMultipleServiceFiles);
|
|
||||||
on<DeleteServiceFilesEvent>(_onDeleteServiceFiles);
|
|
||||||
on<ToggleServiceFileSelectionEvent>(_onToggleServiceFileSelection);
|
|
||||||
// Se il BLoC nasce con un ID, accendiamo subito lo stream!
|
|
||||||
if (serviceId != null) {
|
|
||||||
add(LoadServiceFilesEvent(serviceId: serviceId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onServiceSaved(
|
|
||||||
ServiceSavedEvent event,
|
|
||||||
Emitter<ServiceFilesState> emit,
|
|
||||||
) {
|
|
||||||
// 1. Aggiorniamo l'ID nello stato
|
|
||||||
// 2. PIALLIAMO i file locali: ormai sono partiti per Supabase!
|
|
||||||
// Così la UI si pulisce all'istante e aspetta quelli remoti.
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
serviceId: event.serviceId,
|
|
||||||
localFiles: [], // <-- LA MAGIA ANTI-DUPLICATI
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Lanciamo il caricamento
|
|
||||||
add(LoadServiceFilesEvent(serviceId: event.serviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onLoadServiceFiles(
|
|
||||||
LoadServiceFilesEvent event,
|
|
||||||
Emitter<ServiceFilesState> emit,
|
|
||||||
) async {
|
|
||||||
// Usiamo l'ID dell'evento, e se non c'è usiamo quello dello stato
|
|
||||||
final currentId = event.serviceId ?? state.serviceId;
|
|
||||||
|
|
||||||
if (currentId != null) {
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.loading));
|
|
||||||
|
|
||||||
await emit.forEach(
|
|
||||||
_repository.getServiceFilesStream(
|
|
||||||
currentId,
|
|
||||||
), // <-- Usiamo l'ID corretto!
|
|
||||||
onData: (data) => state.copyWith(
|
|
||||||
status: ServiceFilesStatus.success,
|
|
||||||
remoteFiles: data,
|
|
||||||
),
|
|
||||||
onError: (error, stackTrace) => state.copyWith(
|
|
||||||
status: ServiceFilesStatus.failure,
|
|
||||||
error: error.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onAddServiceFiles(
|
|
||||||
AddServiceFilesEvent event,
|
|
||||||
Emitter<ServiceFilesState> emit,
|
|
||||||
) async {
|
|
||||||
final currentId = state.serviceId;
|
|
||||||
// BIVIO 1: PRATICA NUOVA (Nessun ID)
|
|
||||||
if (currentId == null) {
|
|
||||||
// Mettiamo i file nel "parcheggio" locale dello State
|
|
||||||
final newLocalFiles = event.files.map((file) {
|
|
||||||
return ServiceFileModel(
|
|
||||||
id: null,
|
|
||||||
serviceId: serviceId ?? '',
|
|
||||||
name: file.name.fileNameWithoutExtension(),
|
|
||||||
extension: file.name.fileExtension(),
|
|
||||||
storagePath: '',
|
|
||||||
fileSize: file.size,
|
|
||||||
localBytes: file.bytes,
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
final List<ServiceFileModel> updatedLocalFiles = [
|
|
||||||
...state.localFiles,
|
|
||||||
...newLocalFiles,
|
|
||||||
];
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
localFiles: updatedLocalFiles,
|
|
||||||
status: ServiceFilesStatus.success,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID)
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.uploading));
|
|
||||||
try {
|
|
||||||
// Logica identica a quella che abbiamo fatto per i clienti
|
|
||||||
for (var file in event.files) {
|
|
||||||
await _repository.uploadAndRegisterServiceFile(
|
|
||||||
serviceId: serviceId!,
|
|
||||||
pickedFile: file,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.success));
|
|
||||||
} catch (e) {
|
|
||||||
emit(
|
|
||||||
state.copyWith(status: ServiceFilesStatus.failure, error: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onUploadServiceFiles(
|
|
||||||
UploadServiceFilesEvent event,
|
|
||||||
Emitter<ServiceFilesState> emit,
|
|
||||||
) async {
|
|
||||||
if (event.pickedFiles == null && event.photos == null) return;
|
|
||||||
if (event.pickedFiles!.isEmpty && event.photos!.isEmpty) return;
|
|
||||||
|
|
||||||
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.uploading));
|
|
||||||
try {
|
|
||||||
// Logica identica a quella che abbiamo fatto per i clienti
|
|
||||||
if (event.pickedFiles != null && event.pickedFiles!.isNotEmpty) {
|
|
||||||
for (var file in event.pickedFiles!) {
|
|
||||||
await _repository.uploadAndRegisterServiceFile(
|
|
||||||
serviceId: state.serviceId!,
|
|
||||||
pickedFile: file,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.success));
|
|
||||||
} catch (e) {
|
|
||||||
emit(
|
|
||||||
state.copyWith(status: ServiceFilesStatus.failure, error: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onUploadMultipleServiceFiles(
|
|
||||||
UploadMultipleServiceFilesEvent event,
|
|
||||||
Emitter<ServiceFilesState> emit,
|
|
||||||
) async {
|
|
||||||
if (event.files.isEmpty) {
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
status: ServiceFilesStatus.failure,
|
|
||||||
error: "Nessun file selezionato",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.uploading, error: null));
|
|
||||||
try {
|
|
||||||
// 2. Creiamo una lista di "Promesse" (Futures) per il repository
|
|
||||||
final List<Future<void>> uploadTasks = [];
|
|
||||||
for (var file in event.files) {
|
|
||||||
// Aggiungiamo il task alla lista, ma NON usiamo await qui dentro!
|
|
||||||
uploadTasks.add(
|
|
||||||
_repository.uploadAndRegisterServiceFile(
|
|
||||||
serviceId: state.serviceId!,
|
|
||||||
pickedFile: file,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// 3. ESECUZIONE PARALLELA!
|
|
||||||
// Aspettiamo che tutti i file siano caricati contemporaneamente.
|
|
||||||
await Future.wait(uploadTasks);
|
|
||||||
// 4. GRAN FINALE: Tutto caricato, emettiamo il success!
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.success));
|
|
||||||
} catch (e) {
|
|
||||||
// Se anche un solo file fallisce, catturiamo l'errore
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
status: ServiceFilesStatus.failure,
|
|
||||||
error: "Errore durante l'upload multiplo: $e",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onDeleteServiceFiles(
|
|
||||||
DeleteServiceFilesEvent event,
|
|
||||||
Emitter<ServiceFilesState> emit,
|
|
||||||
) async {
|
|
||||||
emit(state.copyWith(status: ServiceFilesStatus.loading));
|
|
||||||
try {
|
|
||||||
await _repository.deleteServiceFiles(state.selectedFiles);
|
|
||||||
emit(
|
|
||||||
state.copyWith(status: ServiceFilesStatus.success, selectedFiles: []),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(
|
|
||||||
state.copyWith(status: ServiceFilesStatus.failure, error: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onToggleServiceFileSelection(
|
|
||||||
ToggleServiceFileSelectionEvent event,
|
|
||||||
Emitter<ServiceFilesState> emit,
|
|
||||||
) {
|
|
||||||
List<ServiceFileModel> selectedFiles = List.from(state.selectedFiles);
|
|
||||||
if (selectedFiles.contains(event.file)) {
|
|
||||||
selectedFiles.remove(event.file);
|
|
||||||
} else {
|
|
||||||
selectedFiles.add(event.file);
|
|
||||||
}
|
|
||||||
emit(state.copyWith(selectedFiles: selectedFiles));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
part of 'service_files_bloc.dart';
|
|
||||||
|
|
||||||
abstract class ServiceFilesEvent extends Equatable {
|
|
||||||
const ServiceFilesEvent();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServiceSavedEvent extends ServiceFilesEvent {
|
|
||||||
final String serviceId;
|
|
||||||
const ServiceSavedEvent(this.serviceId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [serviceId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoadServiceFilesEvent extends ServiceFilesEvent {
|
|
||||||
final String? serviceId;
|
|
||||||
final ServiceModel? operation;
|
|
||||||
const LoadServiceFilesEvent({this.serviceId, this.operation});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [serviceId, operation];
|
|
||||||
}
|
|
||||||
|
|
||||||
class AddServiceFilesEvent extends ServiceFilesEvent {
|
|
||||||
final List<PlatformFile> files;
|
|
||||||
const AddServiceFilesEvent(this.files);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [files];
|
|
||||||
}
|
|
||||||
|
|
||||||
class UploadServiceFilesEvent extends ServiceFilesEvent {
|
|
||||||
final List<PlatformFile>? pickedFiles;
|
|
||||||
final List<File>? photos;
|
|
||||||
const UploadServiceFilesEvent({this.pickedFiles, this.photos});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [pickedFiles, photos];
|
|
||||||
}
|
|
||||||
|
|
||||||
class UploadMultipleServiceFilesEvent extends ServiceFilesEvent {
|
|
||||||
final List<PlatformFile> files;
|
|
||||||
const UploadMultipleServiceFilesEvent(this.files);
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [files];
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeleteServiceFilesEvent extends ServiceFilesEvent {}
|
|
||||||
|
|
||||||
class ToggleServiceFileSelectionEvent extends ServiceFilesEvent {
|
|
||||||
final ServiceFileModel file;
|
|
||||||
const ToggleServiceFileSelectionEvent(this.file);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
part of 'service_files_bloc.dart';
|
|
||||||
|
|
||||||
enum ServiceFilesStatus { initial, loading, uploading, success, failure }
|
|
||||||
|
|
||||||
class ServiceFilesState extends Equatable {
|
|
||||||
const ServiceFilesState({
|
|
||||||
this.serviceId,
|
|
||||||
required this.status,
|
|
||||||
this.error,
|
|
||||||
this.localFiles = const [],
|
|
||||||
this.remoteFiles = const [],
|
|
||||||
this.selectedFiles = const [],
|
|
||||||
});
|
|
||||||
|
|
||||||
final String? serviceId;
|
|
||||||
final ServiceFilesStatus status;
|
|
||||||
final String? error;
|
|
||||||
final List<ServiceFileModel> localFiles;
|
|
||||||
final List<ServiceFileModel> remoteFiles;
|
|
||||||
|
|
||||||
final List<ServiceFileModel> selectedFiles;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
serviceId,
|
|
||||||
status,
|
|
||||||
error,
|
|
||||||
localFiles,
|
|
||||||
remoteFiles,
|
|
||||||
selectedFiles,
|
|
||||||
];
|
|
||||||
|
|
||||||
List<ServiceFileModel> get allFiles => [...remoteFiles, ...localFiles];
|
|
||||||
|
|
||||||
ServiceFilesState copyWith({
|
|
||||||
String? serviceId,
|
|
||||||
ServiceFilesStatus? status,
|
|
||||||
String? error,
|
|
||||||
List<ServiceFileModel>? localFiles,
|
|
||||||
List<ServiceFileModel>? remoteFiles,
|
|
||||||
List<ServiceFileModel>? selectedFiles,
|
|
||||||
}) {
|
|
||||||
return ServiceFilesState(
|
|
||||||
serviceId: serviceId ?? this.serviceId,
|
|
||||||
status: status ?? this.status,
|
|
||||||
error: error,
|
|
||||||
localFiles: localFiles ?? this.localFiles,
|
|
||||||
remoteFiles: remoteFiles ?? this.remoteFiles,
|
|
||||||
selectedFiles: selectedFiles ?? this.selectedFiles,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,40 +4,40 @@ import 'package:flux/core/blocs/session/session_cubit.dart';
|
|||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/features/customers/data/customer_repository.dart';
|
import 'package:flux/features/customers/data/customer_repository.dart';
|
||||||
import 'package:flux/features/customers/models/customer_file_model.dart';
|
import 'package:flux/features/customers/models/customer_file_model.dart';
|
||||||
import 'package:flux/features/operations/models/service_file_model.dart';
|
import 'package:flux/features/operations/models/operation_file_model.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import '../models/service_model.dart';
|
import '../models/operation_model.dart';
|
||||||
|
|
||||||
class ServicesRepository {
|
class OperationsRepository {
|
||||||
final _supabase = Supabase.instance.client;
|
final _supabase = Supabase.instance.client;
|
||||||
final companyId = GetIt.I.get<SessionCubit>().state.company!.id;
|
final companyId = GetIt.I.get<SessionCubit>().state.company!.id;
|
||||||
final CustomerRepository _customerRepository = GetIt.I<CustomerRepository>();
|
final CustomerRepository _customerRepository = GetIt.I<CustomerRepository>();
|
||||||
|
|
||||||
// --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO ---
|
// --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO ---
|
||||||
Future<ServiceModel> fetchServiceById(String id) async {
|
Future<OperationModel> fetchOperationById(String id) async {
|
||||||
try {
|
try {
|
||||||
final response = await _supabase
|
final response = await _supabase
|
||||||
.from('operation')
|
.from('operation')
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
customer(nome),
|
customer(nome),
|
||||||
energy_service(*),
|
energy_operation(*),
|
||||||
fin_service(*),
|
fin_operation(*),
|
||||||
entertainment_service(*),
|
entertainment_operation(*),
|
||||||
service_file(*)
|
operation_file(*)
|
||||||
''')
|
''')
|
||||||
.eq('id', id)
|
.eq('id', id)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
return ServiceModel.fromMap(response);
|
return OperationModel.fromMap(response);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Errore nel caricamento del servizio: $e');
|
throw Exception('Errore nel caricamento del servizio: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- RECUPERO PAGINATO CON FILTRI E JOIN ---
|
// --- RECUPERO PAGINATO CON FILTRI E JOIN ---
|
||||||
Future<List<ServiceModel>> fetchServices({
|
Future<List<OperationModel>> fetchOperations({
|
||||||
required String companyId,
|
required String companyId,
|
||||||
required int offset,
|
required int offset,
|
||||||
int limit = 50,
|
int limit = 50,
|
||||||
@@ -51,10 +51,10 @@ class ServicesRepository {
|
|||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
customer(nome),
|
customer(nome),
|
||||||
energy_service(*),
|
energy_operation(*),
|
||||||
fin_service(*),
|
fin_operation(*),
|
||||||
entertainment_service(*),
|
entertainment_operation(*),
|
||||||
service_file(*)
|
operation_file(*)
|
||||||
''')
|
''')
|
||||||
.eq('company_id', companyId);
|
.eq('company_id', companyId);
|
||||||
|
|
||||||
@@ -77,14 +77,14 @@ class ServicesRepository {
|
|||||||
.range(offset, offset + limit - 1);
|
.range(offset, offset + limit - 1);
|
||||||
|
|
||||||
return (response as List)
|
return (response as List)
|
||||||
.map((map) => ServiceModel.fromMap(map))
|
.map((map) => OperationModel.fromMap(map))
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Errore nel caricamento servizi: $e');
|
throw Exception('Errore nel caricamento servizi: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<ServiceModel>> getLastStoreServicesStream({
|
Stream<List<OperationModel>> getLastStoreOperationsStream({
|
||||||
required String storeId,
|
required String storeId,
|
||||||
required int limit,
|
required int limit,
|
||||||
}) {
|
}) {
|
||||||
@@ -96,32 +96,32 @@ class ServicesRepository {
|
|||||||
.limit(limit)
|
.limit(limit)
|
||||||
.map(
|
.map(
|
||||||
(listOfMaps) =>
|
(listOfMaps) =>
|
||||||
listOfMaps.map((map) => ServiceModel.fromMap(map)).toList(),
|
listOfMaps.map((map) => OperationModel.fromMap(map)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SALVATAGGIO COMPLETO (PRIMA PADRE, POI FIGLI) ---
|
// --- SALVATAGGIO COMPLETO (PRIMA PADRE, POI FIGLI) ---
|
||||||
Future<ServiceModel> saveFullService(ServiceModel operation) async {
|
Future<OperationModel> saveFullOperation(OperationModel operation) async {
|
||||||
try {
|
try {
|
||||||
// 1. Upsert del record principale
|
// 1. Upsert del record principale
|
||||||
final serviceData = await _supabase
|
final operationData = await _supabase
|
||||||
.from('operation')
|
.from('operation')
|
||||||
.upsert(operation.toMap())
|
.upsert(operation.toMap())
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
final String newId = serviceData['id'];
|
final String newId = operationData['id'];
|
||||||
|
|
||||||
// 2. MODIFICA: Pulizia atomica dei figli
|
// 2. MODIFICA: Pulizia atomica dei figli
|
||||||
// Se stiamo modificando (id != null), resettiamo le tabelle collegate
|
// Se stiamo modificando (id != null), resettiamo le tabelle collegate
|
||||||
if (operation.id != null) {
|
if (operation.id != null) {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_supabase.from('energy_service').delete().eq('service_id', newId),
|
_supabase.from('energy_operation').delete().eq('operation_id', newId),
|
||||||
_supabase.from('fin_service').delete().eq('service_id', newId),
|
_supabase.from('fin_operation').delete().eq('operation_id', newId),
|
||||||
_supabase
|
_supabase
|
||||||
.from('entertainment_service')
|
.from('entertainment_operation')
|
||||||
.delete()
|
.delete()
|
||||||
.eq('service_id', newId),
|
.eq('operation_id', newId),
|
||||||
// Aggiungi qui eventuali altre tabelle pivot o file
|
// Aggiungi qui eventuali altre tabelle pivot o file
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -129,37 +129,37 @@ class ServicesRepository {
|
|||||||
// 3. Inserimento dei moduli in parallelo per velocità
|
// 3. Inserimento dei moduli in parallelo per velocità
|
||||||
final List<Future> insertTasks = [];
|
final List<Future> insertTasks = [];
|
||||||
|
|
||||||
if (operation.energyServices.isNotEmpty) {
|
if (operation.energyOperations.isNotEmpty) {
|
||||||
insertTasks.add(
|
insertTasks.add(
|
||||||
_supabase
|
_supabase
|
||||||
.from('energy_service')
|
.from('energy_operation')
|
||||||
.insert(
|
.insert(
|
||||||
operation.energyServices
|
operation.energyOperations
|
||||||
.map((item) => item.copyWith(serviceId: newId).toMap())
|
.map((item) => item.copyWith(operationId: newId).toMap())
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation.finServices.isNotEmpty) {
|
if (operation.finOperations.isNotEmpty) {
|
||||||
insertTasks.add(
|
insertTasks.add(
|
||||||
_supabase
|
_supabase
|
||||||
.from('fin_service')
|
.from('fin_operation')
|
||||||
.insert(
|
.insert(
|
||||||
operation.finServices
|
operation.finOperations
|
||||||
.map((item) => item.copyWith(serviceId: newId).toMap())
|
.map((item) => item.copyWith(operationId: newId).toMap())
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation.entertainmentServices.isNotEmpty) {
|
if (operation.entertainmentOperations.isNotEmpty) {
|
||||||
insertTasks.add(
|
insertTasks.add(
|
||||||
_supabase
|
_supabase
|
||||||
.from('entertainment_service')
|
.from('entertainment_operation')
|
||||||
.insert(
|
.insert(
|
||||||
operation.entertainmentServices
|
operation.entertainmentOperations
|
||||||
.map((item) => item.copyWith(serviceId: newId).toMap())
|
.map((item) => item.copyWith(operationId: newId).toMap())
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -186,7 +186,7 @@ class ServicesRepository {
|
|||||||
: 'image/${file.extension}';
|
: 'image/${file.extension}';
|
||||||
|
|
||||||
final fileToSave = file.copyWith(
|
final fileToSave = file.copyWith(
|
||||||
serviceId: newId,
|
operationId: newId,
|
||||||
storagePath: storagePath,
|
storagePath: storagePath,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ class ServicesRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// B. Inserimento riga nel DB relazionale
|
// B. Inserimento riga nel DB relazionale
|
||||||
await _supabase.from('service_file').insert(fileToSave.toMap());
|
await _supabase.from('operation_file').insert(fileToSave.toMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadTasks.add(uploadAndLink());
|
uploadTasks.add(uploadAndLink());
|
||||||
@@ -214,20 +214,20 @@ class ServicesRepository {
|
|||||||
|
|
||||||
// 5. GRAN FINALE: RECUPERO DEL MODELLO COMPLETO E AGGIORNATO
|
// 5. GRAN FINALE: RECUPERO DEL MODELLO COMPLETO E AGGIORNATO
|
||||||
// Interroghiamo Supabase per farci restituire la pratica con TUTTI gli ID generati
|
// Interroghiamo Supabase per farci restituire la pratica con TUTTI gli ID generati
|
||||||
// (inclusi quelli della tabella service_file appena inseriti)
|
// (inclusi quelli della tabella operation_file appena inseriti)
|
||||||
final updatedServiceData = await _supabase
|
final updatedOperationData = await _supabase
|
||||||
.from('operation')
|
.from('operation')
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
energy_service(*),
|
energy_operation(*),
|
||||||
fin_service(*),
|
fin_operation(*),
|
||||||
entertainment_service(*),
|
entertainment_operation(*),
|
||||||
service_file(*)
|
operation_file(*)
|
||||||
''')
|
''')
|
||||||
.eq('id', newId)
|
.eq('id', newId)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
return ServiceModel.fromMap(updatedServiceData);
|
return OperationModel.fromMap(updatedOperationData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Qui potresti aggiungere una logica di "rollback manuale" se necessario
|
// Qui potresti aggiungere una logica di "rollback manuale" se necessario
|
||||||
throw Exception('Errore durante il salvataggio corazzato: $e');
|
throw Exception('Errore durante il salvataggio corazzato: $e');
|
||||||
@@ -235,7 +235,7 @@ class ServicesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- ELIMINAZIONE ---
|
// --- ELIMINAZIONE ---
|
||||||
Future<void> deleteService(String id) async {
|
Future<void> deleteOperation(String id) async {
|
||||||
try {
|
try {
|
||||||
await _supabase.from('operation').delete().eq('id', id);
|
await _supabase.from('operation').delete().eq('id', id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -249,7 +249,7 @@ class ServicesRepository {
|
|||||||
// Cerchiamo i tipi più frequenti associati ai servizi di questa company
|
// Cerchiamo i tipi più frequenti associati ai servizi di questa company
|
||||||
// Nota: dobbiamo passare attraverso la tabella 'operation' per filtrare per company_id
|
// Nota: dobbiamo passare attraverso la tabella 'operation' per filtrare per company_id
|
||||||
final response = await _supabase
|
final response = await _supabase
|
||||||
.from('entertainment_service')
|
.from('entertainment_operation')
|
||||||
.select('type, operation!inner(store!inner(company_id))')
|
.select('type, operation!inner(store!inner(company_id))')
|
||||||
.eq('operation.store.company_id', companyId)
|
.eq('operation.store.company_id', companyId)
|
||||||
.limit(100); // Prendiamo un campione
|
.limit(100); // Prendiamo un campione
|
||||||
@@ -276,20 +276,20 @@ class ServicesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Ascolta in tempo reale i file caricati per una pratica
|
/// Ascolta in tempo reale i file caricati per una pratica
|
||||||
Stream<List<ServiceFileModel>> getServiceFilesStream(String serviceId) {
|
Stream<List<OperationFileModel>> getOperationFilesStream(String operationId) {
|
||||||
return _supabase
|
return _supabase
|
||||||
.from('service_file')
|
.from('operation_file')
|
||||||
.stream(primaryKey: ['id'])
|
.stream(primaryKey: ['id'])
|
||||||
.eq('service_id', serviceId)
|
.eq('operation_id', operationId)
|
||||||
.order('created_at', ascending: false)
|
.order('created_at', ascending: false)
|
||||||
.map(
|
.map(
|
||||||
(listOfMaps) =>
|
(listOfMaps) =>
|
||||||
listOfMaps.map((map) => ServiceFileModel.fromMap(map)).toList(),
|
listOfMaps.map((map) => OperationFileModel.fromMap(map)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ServiceFileModel> uploadAndRegisterServiceFile({
|
Future<OperationFileModel> uploadAndRegisterOperationFile({
|
||||||
required String serviceId,
|
required String operationId,
|
||||||
required PlatformFile pickedFile,
|
required PlatformFile pickedFile,
|
||||||
}) async {
|
}) async {
|
||||||
final cleanFileName = pickedFile.name.replaceAll(
|
final cleanFileName = pickedFile.name.replaceAll(
|
||||||
@@ -297,10 +297,10 @@ class ServicesRepository {
|
|||||||
'_',
|
'_',
|
||||||
);
|
);
|
||||||
final storagePath =
|
final storagePath =
|
||||||
'$companyId/operations/$serviceId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
|
'$companyId/operations/$operationId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
|
||||||
final int fileSize = pickedFile.size;
|
final int fileSize = pickedFile.size;
|
||||||
final fileToSave = ServiceFileModel(
|
final fileToSave = OperationFileModel(
|
||||||
serviceId: serviceId,
|
operationId: operationId,
|
||||||
name: cleanFileName.fileNameWithoutExtension(),
|
name: cleanFileName.fileNameWithoutExtension(),
|
||||||
extension: cleanFileName.fileExtension(),
|
extension: cleanFileName.fileExtension(),
|
||||||
storagePath: storagePath,
|
storagePath: storagePath,
|
||||||
@@ -327,19 +327,19 @@ class ServicesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final response = await _supabase
|
final response = await _supabase
|
||||||
.from('service_file')
|
.from('operation_file')
|
||||||
.insert(fileToSave.toMap())
|
.insert(fileToSave.toMap())
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
return ServiceFileModel.fromMap(response);
|
return OperationFileModel.fromMap(response);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw 'Errore durante l\'upload: $e';
|
throw 'Errore durante l\'upload: $e';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> copyFileToCustomer({
|
Future<void> copyFileToCustomer({
|
||||||
required ServiceFileModel file,
|
required OperationFileModel file,
|
||||||
required String customerId,
|
required String customerId,
|
||||||
}) async {
|
}) async {
|
||||||
CustomerFileModel fileToCopy = CustomerFileModel(
|
CustomerFileModel fileToCopy = CustomerFileModel(
|
||||||
@@ -352,14 +352,17 @@ class ServicesRepository {
|
|||||||
await _customerRepository.saveFileReference(fileToCopy);
|
await _customerRepository.saveFileReference(fileToCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteServiceFiles(List<ServiceFileModel> files) async {
|
Future<void> deleteOperationFiles(List<OperationFileModel> files) async {
|
||||||
if (files.isEmpty) return;
|
if (files.isEmpty) return;
|
||||||
// 1. Prepariamo le liste di ID e di Percorsi
|
// 1. Prepariamo le liste di ID e di Percorsi
|
||||||
final List<String> idsToDelete = files.map((f) => f.id!).toList();
|
final List<String> idsToDelete = files.map((f) => f.id!).toList();
|
||||||
final List<String> storagePaths = files.map((f) => f.storagePath).toList();
|
final List<String> storagePaths = files.map((f) => f.storagePath).toList();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _supabase.from('service_file').delete().inFilter('id', idsToDelete);
|
await _supabase
|
||||||
|
.from('operation_file')
|
||||||
|
.delete()
|
||||||
|
.inFilter('id', idsToDelete);
|
||||||
|
|
||||||
await _supabase.storage.from('documents').remove(storagePaths);
|
await _supabase.storage.from('documents').remove(storagePaths);
|
||||||
|
|
||||||
@@ -2,38 +2,38 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
enum EnergyType { luce, gas } // Mappa il tuo public.energy_type
|
enum EnergyType { luce, gas } // Mappa il tuo public.energy_type
|
||||||
|
|
||||||
class EnergyServiceModel extends Equatable {
|
class EnergyOperationModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final EnergyType type;
|
final EnergyType type;
|
||||||
final DateTime expiration;
|
final DateTime expiration;
|
||||||
final String providerId;
|
final String providerId;
|
||||||
final String? serviceId;
|
final String? operationId;
|
||||||
|
|
||||||
const EnergyServiceModel({
|
const EnergyOperationModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.expiration,
|
required this.expiration,
|
||||||
required this.providerId,
|
required this.providerId,
|
||||||
this.serviceId,
|
this.operationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
EnergyServiceModel copyWith({
|
EnergyOperationModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
EnergyType? type,
|
EnergyType? type,
|
||||||
DateTime? expiration,
|
DateTime? expiration,
|
||||||
String? providerId,
|
String? providerId,
|
||||||
String? serviceId,
|
String? operationId,
|
||||||
}) {
|
}) {
|
||||||
return EnergyServiceModel(
|
return EnergyOperationModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
expiration: expiration ?? this.expiration,
|
expiration: expiration ?? this.expiration,
|
||||||
providerId: providerId ?? this.providerId,
|
providerId: providerId ?? this.providerId,
|
||||||
serviceId: serviceId ?? this.serviceId,
|
operationId: operationId ?? this.operationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +44,11 @@ class EnergyServiceModel extends Equatable {
|
|||||||
type,
|
type,
|
||||||
expiration,
|
expiration,
|
||||||
providerId,
|
providerId,
|
||||||
serviceId,
|
operationId,
|
||||||
];
|
];
|
||||||
|
|
||||||
factory EnergyServiceModel.fromMap(Map<String, dynamic> map) {
|
factory EnergyOperationModel.fromMap(Map<String, dynamic> map) {
|
||||||
return EnergyServiceModel(
|
return EnergyOperationModel(
|
||||||
id: map['id'],
|
id: map['id'],
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? DateTime.parse(map['created_at'])
|
? DateTime.parse(map['created_at'])
|
||||||
@@ -56,7 +56,7 @@ class EnergyServiceModel extends Equatable {
|
|||||||
type: map['type'] == 'gas' ? EnergyType.gas : EnergyType.luce,
|
type: map['type'] == 'gas' ? EnergyType.gas : EnergyType.luce,
|
||||||
expiration: DateTime.parse(map['expiration']),
|
expiration: DateTime.parse(map['expiration']),
|
||||||
providerId: map['provider_id'],
|
providerId: map['provider_id'],
|
||||||
serviceId: map['service_id'],
|
operationId: map['operation_id'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ class EnergyServiceModel extends Equatable {
|
|||||||
'type': type.name, // .name trasforma l'enum in 'luce' o 'gas'
|
'type': type.name, // .name trasforma l'enum in 'luce' o 'gas'
|
||||||
'expiration': expiration.toIso8601String(),
|
'expiration': expiration.toIso8601String(),
|
||||||
'provider_id': providerId,
|
'provider_id': providerId,
|
||||||
'service_id': serviceId,
|
'operation_id': operationId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,40 +1,40 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class EntertainmentServiceModel extends Equatable {
|
class EntertainmentOperationModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final String type; // es. Sky, DAZN, ecc.
|
final String type; // es. Sky, DAZN, ecc.
|
||||||
final bool constrained; // Vincolato?
|
final bool constrained; // Vincolato?
|
||||||
final DateTime constrainExpiration;
|
final DateTime constrainExpiration;
|
||||||
final String? serviceId;
|
final String? operationId;
|
||||||
final String? providerId;
|
final String? providerId;
|
||||||
|
|
||||||
const EntertainmentServiceModel({
|
const EntertainmentOperationModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.constrained,
|
required this.constrained,
|
||||||
required this.constrainExpiration,
|
required this.constrainExpiration,
|
||||||
this.serviceId,
|
this.operationId,
|
||||||
this.providerId,
|
this.providerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
EntertainmentServiceModel copyWith({
|
EntertainmentOperationModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
String? type,
|
String? type,
|
||||||
bool? constrained,
|
bool? constrained,
|
||||||
DateTime? constrainExpiration,
|
DateTime? constrainExpiration,
|
||||||
String? serviceId,
|
String? operationId,
|
||||||
String? providerId,
|
String? providerId,
|
||||||
}) {
|
}) {
|
||||||
return EntertainmentServiceModel(
|
return EntertainmentOperationModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
constrained: constrained ?? this.constrained,
|
constrained: constrained ?? this.constrained,
|
||||||
constrainExpiration: constrainExpiration ?? this.constrainExpiration,
|
constrainExpiration: constrainExpiration ?? this.constrainExpiration,
|
||||||
serviceId: serviceId ?? this.serviceId,
|
operationId: operationId ?? this.operationId,
|
||||||
providerId: providerId ?? this.providerId,
|
providerId: providerId ?? this.providerId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,12 +46,12 @@ class EntertainmentServiceModel extends Equatable {
|
|||||||
type,
|
type,
|
||||||
constrained,
|
constrained,
|
||||||
constrainExpiration,
|
constrainExpiration,
|
||||||
serviceId,
|
operationId,
|
||||||
providerId,
|
providerId,
|
||||||
];
|
];
|
||||||
|
|
||||||
factory EntertainmentServiceModel.fromMap(Map<String, dynamic> map) {
|
factory EntertainmentOperationModel.fromMap(Map<String, dynamic> map) {
|
||||||
return EntertainmentServiceModel(
|
return EntertainmentOperationModel(
|
||||||
id: map['id'],
|
id: map['id'],
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? DateTime.parse(map['created_at'])
|
? DateTime.parse(map['created_at'])
|
||||||
@@ -59,7 +59,7 @@ class EntertainmentServiceModel extends Equatable {
|
|||||||
type: map['type'],
|
type: map['type'],
|
||||||
constrained: map['constrained'] ?? false,
|
constrained: map['constrained'] ?? false,
|
||||||
constrainExpiration: DateTime.parse(map['constrain_expiration']),
|
constrainExpiration: DateTime.parse(map['constrain_expiration']),
|
||||||
serviceId: map['service_id'],
|
operationId: map['operation_id'],
|
||||||
providerId: map['provider_id'],
|
providerId: map['provider_id'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ class EntertainmentServiceModel extends Equatable {
|
|||||||
'type': type,
|
'type': type,
|
||||||
'constrained': constrained,
|
'constrained': constrained,
|
||||||
'constrain_expiration': constrainExpiration.toIso8601String(),
|
'constrain_expiration': constrainExpiration.toIso8601String(),
|
||||||
'service_id': serviceId,
|
'operation_id': operationId,
|
||||||
'provider_id': providerId,
|
'provider_id': providerId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,51 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class FinServiceModel extends Equatable {
|
class FinOperationModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime expiration;
|
final DateTime expiration;
|
||||||
final String? serviceId;
|
final String? operationId;
|
||||||
final String? modelId; // FK verso model (es. iPhone, Samsung, ecc.)
|
final String? modelId; // FK verso model (es. iPhone, Samsung, ecc.)
|
||||||
final String? providerId;
|
final String? providerId;
|
||||||
|
|
||||||
const FinServiceModel({
|
const FinOperationModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
required this.expiration,
|
required this.expiration,
|
||||||
this.serviceId,
|
this.operationId,
|
||||||
this.modelId,
|
this.modelId,
|
||||||
this.providerId,
|
this.providerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
FinServiceModel copyWith({
|
FinOperationModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
DateTime? expiration,
|
DateTime? expiration,
|
||||||
String? serviceId,
|
String? operationId,
|
||||||
String? modelId,
|
String? modelId,
|
||||||
String? providerId,
|
String? providerId,
|
||||||
}) {
|
}) {
|
||||||
return FinServiceModel(
|
return FinOperationModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
expiration: expiration ?? this.expiration,
|
expiration: expiration ?? this.expiration,
|
||||||
serviceId: serviceId ?? this.serviceId,
|
operationId: operationId ?? this.operationId,
|
||||||
modelId: modelId ?? this.modelId,
|
modelId: modelId ?? this.modelId,
|
||||||
providerId: providerId ?? this.providerId,
|
providerId: providerId ?? this.providerId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, createdAt, expiration, serviceId, modelId];
|
List<Object?> get props => [id, createdAt, expiration, operationId, modelId];
|
||||||
|
|
||||||
factory FinServiceModel.fromMap(Map<String, dynamic> map) {
|
factory FinOperationModel.fromMap(Map<String, dynamic> map) {
|
||||||
return FinServiceModel(
|
return FinOperationModel(
|
||||||
id: map['id'],
|
id: map['id'],
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? DateTime.parse(map['created_at'])
|
? DateTime.parse(map['created_at'])
|
||||||
: null,
|
: null,
|
||||||
expiration: DateTime.parse(map['expiration']),
|
expiration: DateTime.parse(map['expiration']),
|
||||||
serviceId: map['service_id'],
|
operationId: map['operation_id'],
|
||||||
modelId: map['model_id'],
|
modelId: map['model_id'],
|
||||||
providerId: map['provider_id'],
|
providerId: map['provider_id'],
|
||||||
);
|
);
|
||||||
@@ -55,7 +55,7 @@ class FinServiceModel extends Equatable {
|
|||||||
return {
|
return {
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
'expiration': expiration.toIso8601String(),
|
'expiration': expiration.toIso8601String(),
|
||||||
'service_id': serviceId,
|
'operation_id': operationId,
|
||||||
'model_id': modelId,
|
'model_id': modelId,
|
||||||
'provider_id': providerId,
|
'provider_id': providerId,
|
||||||
};
|
};
|
||||||
@@ -2,23 +2,23 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class ServiceFileModel extends Equatable {
|
class OperationFileModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final String name;
|
final String name;
|
||||||
final String extension;
|
final String extension;
|
||||||
final String storagePath;
|
final String storagePath;
|
||||||
final String serviceId;
|
final String operationId;
|
||||||
final int fileSize;
|
final int fileSize;
|
||||||
final Uint8List? localBytes;
|
final Uint8List? localBytes;
|
||||||
|
|
||||||
const ServiceFileModel({
|
const OperationFileModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.extension,
|
required this.extension,
|
||||||
required this.storagePath,
|
required this.storagePath,
|
||||||
required this.serviceId,
|
required this.operationId,
|
||||||
required this.fileSize,
|
required this.fileSize,
|
||||||
this.localBytes,
|
this.localBytes,
|
||||||
});
|
});
|
||||||
@@ -37,30 +37,30 @@ class ServiceFileModel extends Equatable {
|
|||||||
|
|
||||||
bool get isPdf => extension.toLowerCase().replaceAll('.', '') == 'pdf';
|
bool get isPdf => extension.toLowerCase().replaceAll('.', '') == 'pdf';
|
||||||
|
|
||||||
ServiceFileModel copyWith({
|
OperationFileModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
String? name,
|
String? name,
|
||||||
String? extension,
|
String? extension,
|
||||||
String? storagePath,
|
String? storagePath,
|
||||||
String? serviceId,
|
String? operationId,
|
||||||
int? fileSize,
|
int? fileSize,
|
||||||
Uint8List? localBytes,
|
Uint8List? localBytes,
|
||||||
}) {
|
}) {
|
||||||
return ServiceFileModel(
|
return OperationFileModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
extension: extension ?? this.extension,
|
extension: extension ?? this.extension,
|
||||||
storagePath: storagePath ?? this.storagePath,
|
storagePath: storagePath ?? this.storagePath,
|
||||||
serviceId: serviceId ?? this.serviceId,
|
operationId: operationId ?? this.operationId,
|
||||||
fileSize: fileSize ?? this.fileSize,
|
fileSize: fileSize ?? this.fileSize,
|
||||||
localBytes: localBytes ?? this.localBytes,
|
localBytes: localBytes ?? this.localBytes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ServiceFileModel.fromMap(Map<String, dynamic> map) {
|
factory OperationFileModel.fromMap(Map<String, dynamic> map) {
|
||||||
return ServiceFileModel(
|
return OperationFileModel(
|
||||||
id: map['id'] as String,
|
id: map['id'] as String,
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? DateTime.parse(map['created_at'])
|
? DateTime.parse(map['created_at'])
|
||||||
@@ -68,7 +68,7 @@ class ServiceFileModel extends Equatable {
|
|||||||
name: map['name'] ?? '',
|
name: map['name'] ?? '',
|
||||||
extension: map['extension'] ?? '',
|
extension: map['extension'] ?? '',
|
||||||
storagePath: map['storage_path'] ?? '',
|
storagePath: map['storage_path'] ?? '',
|
||||||
serviceId: map['service_id']?.toString() ?? '',
|
operationId: map['operation_id']?.toString() ?? '',
|
||||||
fileSize: map['file_size'] is int
|
fileSize: map['file_size'] is int
|
||||||
? map['file_size']
|
? map['file_size']
|
||||||
: int.tryParse(map['file_size']?.toString() ?? '0') ?? 0,
|
: int.tryParse(map['file_size']?.toString() ?? '0') ?? 0,
|
||||||
@@ -81,7 +81,7 @@ class ServiceFileModel extends Equatable {
|
|||||||
'name': name,
|
'name': name,
|
||||||
'extension': extension,
|
'extension': extension,
|
||||||
'storage_path': storagePath,
|
'storage_path': storagePath,
|
||||||
'service_id': serviceId,
|
'operation_id': operationId,
|
||||||
'file_size': fileSize,
|
'file_size': fileSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ class ServiceFileModel extends Equatable {
|
|||||||
name,
|
name,
|
||||||
extension,
|
extension,
|
||||||
storagePath,
|
storagePath,
|
||||||
serviceId,
|
operationId,
|
||||||
fileSize,
|
fileSize,
|
||||||
localBytes,
|
localBytes,
|
||||||
];
|
];
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/features/operations/models/energy_service_model.dart';
|
import 'package:flux/features/operations/models/energy_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/entertainment_service_model.dart';
|
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/fin_service_model.dart';
|
import 'package:flux/features/operations/models/fin_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/service_file_model.dart'; // <-- Aggiunto Import
|
import 'package:flux/features/operations/models/operation_file_model.dart'; // <-- Aggiunto Import
|
||||||
|
|
||||||
class ServiceModel extends Equatable {
|
class OperationModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final String storeId;
|
final String storeId;
|
||||||
@@ -26,14 +26,14 @@ class ServiceModel extends Equatable {
|
|||||||
final int telepass;
|
final int telepass;
|
||||||
|
|
||||||
// Moduli (Liste)
|
// Moduli (Liste)
|
||||||
final List<EnergyServiceModel> energyServices;
|
final List<EnergyOperationModel> energyOperations;
|
||||||
final List<FinServiceModel> finServices;
|
final List<FinOperationModel> finOperations;
|
||||||
final List<EntertainmentServiceModel> entertainmentServices;
|
final List<EntertainmentOperationModel> entertainmentOperations;
|
||||||
|
|
||||||
// ALLEGATI (Aggiunto)
|
// ALLEGATI (Aggiunto)
|
||||||
final List<ServiceFileModel> files;
|
final List<OperationFileModel> files;
|
||||||
|
|
||||||
const ServiceModel({
|
const OperationModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
required this.storeId,
|
required this.storeId,
|
||||||
@@ -48,15 +48,15 @@ class ServiceModel extends Equatable {
|
|||||||
this.nip = 0,
|
this.nip = 0,
|
||||||
this.unica = 0,
|
this.unica = 0,
|
||||||
this.telepass = 0,
|
this.telepass = 0,
|
||||||
this.energyServices = const [],
|
this.energyOperations = const [],
|
||||||
this.finServices = const [],
|
this.finOperations = const [],
|
||||||
this.entertainmentServices = const [],
|
this.entertainmentOperations = const [],
|
||||||
this.files = const [], // <-- Aggiunto default vuoto
|
this.files = const [], // <-- Aggiunto default vuoto
|
||||||
this.customerDisplayName,
|
this.customerDisplayName,
|
||||||
required this.companyId,
|
required this.companyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
ServiceModel copyWith({
|
OperationModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
String? storeId,
|
String? storeId,
|
||||||
@@ -71,14 +71,14 @@ class ServiceModel extends Equatable {
|
|||||||
int? nip,
|
int? nip,
|
||||||
int? unica,
|
int? unica,
|
||||||
int? telepass,
|
int? telepass,
|
||||||
List<EnergyServiceModel>? energyServices,
|
List<EnergyOperationModel>? energyOperations,
|
||||||
List<FinServiceModel>? finServices,
|
List<FinOperationModel>? finOperations,
|
||||||
List<EntertainmentServiceModel>? entertainmentServices,
|
List<EntertainmentOperationModel>? entertainmentOperations,
|
||||||
List<ServiceFileModel>? files, // <-- Aggiunto
|
List<OperationFileModel>? files, // <-- Aggiunto
|
||||||
String? customerDisplayName,
|
String? customerDisplayName,
|
||||||
String? companyId,
|
String? companyId,
|
||||||
}) {
|
}) {
|
||||||
return ServiceModel(
|
return OperationModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
storeId: storeId ?? this.storeId,
|
storeId: storeId ?? this.storeId,
|
||||||
@@ -93,10 +93,10 @@ class ServiceModel extends Equatable {
|
|||||||
nip: nip ?? this.nip,
|
nip: nip ?? this.nip,
|
||||||
unica: unica ?? this.unica,
|
unica: unica ?? this.unica,
|
||||||
telepass: telepass ?? this.telepass,
|
telepass: telepass ?? this.telepass,
|
||||||
energyServices: energyServices ?? this.energyServices,
|
energyOperations: energyOperations ?? this.energyOperations,
|
||||||
finServices: finServices ?? this.finServices,
|
finOperations: finOperations ?? this.finOperations,
|
||||||
entertainmentServices:
|
entertainmentOperations:
|
||||||
entertainmentServices ?? this.entertainmentServices,
|
entertainmentOperations ?? this.entertainmentOperations,
|
||||||
files: files ?? this.files, // <-- Aggiunto
|
files: files ?? this.files, // <-- Aggiunto
|
||||||
customerDisplayName: customerDisplayName ?? this.customerDisplayName,
|
customerDisplayName: customerDisplayName ?? this.customerDisplayName,
|
||||||
companyId: companyId ?? this.companyId,
|
companyId: companyId ?? this.companyId,
|
||||||
@@ -119,16 +119,16 @@ class ServiceModel extends Equatable {
|
|||||||
nip,
|
nip,
|
||||||
unica,
|
unica,
|
||||||
telepass,
|
telepass,
|
||||||
energyServices,
|
energyOperations,
|
||||||
finServices,
|
finOperations,
|
||||||
entertainmentServices,
|
entertainmentOperations,
|
||||||
files, // <-- Aggiunto
|
files, // <-- Aggiunto
|
||||||
customerDisplayName,
|
customerDisplayName,
|
||||||
companyId,
|
companyId,
|
||||||
];
|
];
|
||||||
|
|
||||||
factory ServiceModel.fromMap(Map<String, dynamic> map) {
|
factory OperationModel.fromMap(Map<String, dynamic> map) {
|
||||||
return ServiceModel(
|
return OperationModel(
|
||||||
id: map['id'].toString(),
|
id: map['id'].toString(),
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? DateTime.parse(map['created_at'])
|
? DateTime.parse(map['created_at'])
|
||||||
@@ -147,26 +147,26 @@ class ServiceModel extends Equatable {
|
|||||||
telepass: map['telepass'] ?? 0,
|
telepass: map['telepass'] ?? 0,
|
||||||
|
|
||||||
// Estrazione sicura liste collegate
|
// Estrazione sicura liste collegate
|
||||||
energyServices:
|
energyOperations:
|
||||||
(map['energy_service'] as List?)
|
(map['energy_operation'] as List?)
|
||||||
?.map((x) => EnergyServiceModel.fromMap(x))
|
?.map((x) => EnergyOperationModel.fromMap(x))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
finServices:
|
finOperations:
|
||||||
(map['fin_service'] as List?)
|
(map['fin_operation'] as List?)
|
||||||
?.map((x) => FinServiceModel.fromMap(x))
|
?.map((x) => FinOperationModel.fromMap(x))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
entertainmentServices:
|
entertainmentOperations:
|
||||||
(map['entertainment_service'] as List?)
|
(map['entertainment_operation'] as List?)
|
||||||
?.map((x) => EntertainmentServiceModel.fromMap(x))
|
?.map((x) => EntertainmentOperationModel.fromMap(x))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
|
|
||||||
// I FILE! (Assicurati che la foreign key su Supabase usi esattamente questo nome)
|
// I FILE! (Assicurati che la foreign key su Supabase usi esattamente questo nome)
|
||||||
files:
|
files:
|
||||||
(map['service_file'] as List?)
|
(map['operation_file'] as List?)
|
||||||
?.map((x) => ServiceFileModel.fromMap(x))
|
?.map((x) => OperationFileModel.fromMap(x))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ServiceActionCard extends StatelessWidget {
|
class OperationActionCard extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final Color color;
|
final Color color;
|
||||||
final int count;
|
final int count;
|
||||||
const ServiceActionCard({
|
const OperationActionCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
@@ -5,9 +5,9 @@ import 'package:flux/core/blocs/session/session_cubit.dart';
|
|||||||
import 'package:flux/core/widgets/image_viewer_widget.dart';
|
import 'package:flux/core/widgets/image_viewer_widget.dart';
|
||||||
import 'package:flux/core/widgets/pdf_viewer_widget.dart';
|
import 'package:flux/core/widgets/pdf_viewer_widget.dart';
|
||||||
import 'package:flux/core/widgets/qr_upload_dialog.dart';
|
import 'package:flux/core/widgets/qr_upload_dialog.dart';
|
||||||
import 'package:flux/features/operations/blocs/service_files_bloc.dart';
|
import 'package:flux/features/operations/blocs/operation_files_bloc.dart';
|
||||||
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||||
import 'package:flux/features/operations/models/service_file_model.dart';
|
import 'package:flux/features/operations/models/operation_file_model.dart';
|
||||||
|
|
||||||
class AttachmentsSection extends StatelessWidget {
|
class AttachmentsSection extends StatelessWidget {
|
||||||
const AttachmentsSection({super.key});
|
const AttachmentsSection({super.key});
|
||||||
@@ -22,27 +22,29 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result != null && context.mounted) {
|
if (result != null && context.mounted) {
|
||||||
context.read<ServiceFilesBloc>().add(AddServiceFilesEvent(result.files));
|
context.read<OperationFilesBloc>().add(
|
||||||
|
AddOperationFilesEvent(result.files),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ServiceFilesBloc serviceFilesBloc = BlocProvider.of<ServiceFilesBloc>(
|
OperationFilesBloc operationFilesBloc = BlocProvider.of<OperationFilesBloc>(
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
return BlocListener<ServicesCubit, ServicesState>(
|
return BlocListener<OperationsCubit, OperationsState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.currentService?.id == null &&
|
previous.currentOperation?.id == null &&
|
||||||
current.currentService?.id != null,
|
current.currentOperation?.id != null,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
// FIGASSA! La pratica è stata salvata e ora ha un ID.
|
// FIGASSA! La pratica è stata salvata e ora ha un ID.
|
||||||
// Diciamo al Bloc dei file di agganciarsi al database.
|
// Diciamo al Bloc dei file di agganciarsi al database.
|
||||||
final newId = state.currentService!.id!;
|
final newId = state.currentOperation!.id!;
|
||||||
context.read<ServiceFilesBloc>().add(ServiceSavedEvent(newId));
|
context.read<OperationFilesBloc>().add(OperationsavedEvent(newId));
|
||||||
},
|
},
|
||||||
child: BlocBuilder<ServiceFilesBloc, ServiceFilesState>(
|
child: BlocBuilder<OperationFilesBloc, OperationFilesState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -125,8 +127,8 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
final isSelected = state.selectedFiles.contains(file);
|
final isSelected = state.selectedFiles.contains(file);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => serviceFilesBloc.add(
|
onTap: () => operationFilesBloc.add(
|
||||||
ToggleServiceFileSelectionEvent(file),
|
ToggleOperationFileSelectionEvent(file),
|
||||||
),
|
),
|
||||||
onDoubleTap: () => _handleDoubleClick(context, file),
|
onDoubleTap: () => _handleDoubleClick(context, file),
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -216,7 +218,7 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
label: const Text("Elimina"),
|
label: const Text("Elimina"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Qui lancerai l'evento per eliminare i file selezionati!
|
// Qui lancerai l'evento per eliminare i file selezionati!
|
||||||
// Es: serviceFilesBloc.add(DeleteSelectedFilesEvent());
|
// Es: operationFilesBloc.add(DeleteSelectedFilesEvent());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -243,14 +245,14 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleGenerateQr(BuildContext context) async {
|
Future<void> _handleGenerateQr(BuildContext context) async {
|
||||||
final cubit = context.read<ServicesCubit>();
|
final cubit = context.read<OperationsCubit>();
|
||||||
var currentService = cubit.state.currentService;
|
var currentOperation = cubit.state.currentOperation;
|
||||||
|
|
||||||
// 1. CATTURIAMO IL BLOC MENTRE SIAMO ANCORA NELLA PAGINA
|
// 1. CATTURIAMO IL BLOC MENTRE SIAMO ANCORA NELLA PAGINA
|
||||||
final serviceFilesBloc = context.read<ServiceFilesBloc>();
|
final operationFilesBloc = context.read<OperationFilesBloc>();
|
||||||
|
|
||||||
// 2. SE LA PRATICA E' NUOVA (Manca l'ID)
|
// 2. SE LA PRATICA E' NUOVA (Manca l'ID)
|
||||||
if (currentService == null || currentService.id == null) {
|
if (currentOperation == null || currentOperation.id == null) {
|
||||||
// NIENTE BlocListener qui! Solo un semplice Dialog di conferma
|
// NIENTE BlocListener qui! Solo un semplice Dialog di conferma
|
||||||
final bool? confirm = await showDialog<bool>(
|
final bool? confirm = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -275,42 +277,42 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
if (confirm != true) return; // Utente ha annullato
|
if (confirm != true) return; // Utente ha annullato
|
||||||
|
|
||||||
// Salviamo forzatamente in bozza
|
// Salviamo forzatamente in bozza
|
||||||
await cubit.saveCurrentService(
|
await cubit.saveCurrentOperation(
|
||||||
isBozza: true,
|
isBozza: true,
|
||||||
shouldPop: false,
|
shouldPop: false,
|
||||||
files: serviceFilesBloc.state.localFiles,
|
files: operationFilesBloc.state.localFiles,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Recuperiamo il servizio aggiornato con l'ID!
|
// Recuperiamo il servizio aggiornato con l'ID!
|
||||||
currentService = cubit.state.currentService;
|
currentOperation = cubit.state.currentOperation;
|
||||||
|
|
||||||
if (currentService?.id == null) return;
|
if (currentOperation?.id == null) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. MOSTRIAMO IL QR CODE (Con il Ponte e l'Auto-Chiusura!)
|
// 3. MOSTRIAMO IL QR CODE (Con il Ponte e l'Auto-Chiusura!)
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
final nomePratica = "Pratica ${currentService?.customerDisplayName ?? ''}"
|
final nomePratica =
|
||||||
.trim();
|
"Pratica ${currentOperation?.customerDisplayName ?? ''}".trim();
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => BlocProvider.value(
|
builder: (dialogContext) => BlocProvider.value(
|
||||||
// INIETTIAMO IL BLOC NEL CONTESTO DEL DIALOG ALIENO
|
// INIETTIAMO IL BLOC NEL CONTESTO DEL DIALOG ALIENO
|
||||||
value: serviceFilesBloc,
|
value: operationFilesBloc,
|
||||||
|
|
||||||
// ORA METTIAMO L'AUTO-CHIUSURA SUL QR CODE!
|
// ORA METTIAMO L'AUTO-CHIUSURA SUL QR CODE!
|
||||||
child: BlocListener<ServiceFilesBloc, ServiceFilesState>(
|
child: BlocListener<OperationFilesBloc, OperationFilesState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
// Se arrivano file remoti e lo stato è success, chiudiamo il QR!
|
// Se arrivano file remoti e lo stato è success, chiudiamo il QR!
|
||||||
// (Nota: usiamo dialogContext per assicurarci di chiudere il popup giusto)
|
// (Nota: usiamo dialogContext per assicurarci di chiudere il popup giusto)
|
||||||
if (state.status == ServiceFilesStatus.success &&
|
if (state.status == OperationFilesStatus.success &&
|
||||||
state.remoteFiles.isNotEmpty) {
|
state.remoteFiles.isNotEmpty) {
|
||||||
Navigator.of(dialogContext).pop();
|
Navigator.of(dialogContext).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: QrUploadDialog(
|
child: QrUploadDialog(
|
||||||
deepLinkUrl:
|
deepLinkUrl:
|
||||||
'fluxapp:///operation/${currentService!.id}/upload?name=${Uri.encodeComponent(nomePratica)}',
|
'fluxapp:///operation/${currentOperation!.id}/upload?name=${Uri.encodeComponent(nomePratica)}',
|
||||||
title: 'Scatta per\n$nomePratica',
|
title: 'Scatta per\n$nomePratica',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -322,7 +324,7 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
// --- LOGICA DI COPIA AL CLIENTE ---
|
// --- LOGICA DI COPIA AL CLIENTE ---
|
||||||
void saveAndCopyFilesToCustomer(
|
void saveAndCopyFilesToCustomer(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<ServiceFileModel> files,
|
List<OperationFileModel> files,
|
||||||
) {
|
) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -341,7 +343,7 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(ctx);
|
Navigator.pop(ctx);
|
||||||
// 1. Diciamo al Cubit di salvare in Bozza e fare la copia
|
// 1. Diciamo al Cubit di salvare in Bozza e fare la copia
|
||||||
context.read<ServicesCubit>().saveAndCopyFileToCustomer(files);
|
context.read<OperationsCubit>().saveAndCopyFileToCustomer(files);
|
||||||
},
|
},
|
||||||
child: const Text("Salva e Copia"),
|
child: const Text("Salva e Copia"),
|
||||||
),
|
),
|
||||||
@@ -351,7 +353,7 @@ class AttachmentsSection extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- LOGICA DI VISUALIZZAZIONE OVERLAY ---
|
// --- LOGICA DI VISUALIZZAZIONE OVERLAY ---
|
||||||
void _handleDoubleClick(BuildContext context, ServiceFileModel file) {
|
void _handleDoubleClick(BuildContext context, OperationFileModel file) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flux/features/customers/ui/customer_search_sheet.dart';
|
import 'package:flux/features/customers/ui/customer_search_sheet.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
|
|
||||||
class CustomerSection extends StatelessWidget {
|
class CustomerSection extends StatelessWidget {
|
||||||
final ServiceModel operation;
|
final OperationModel operation;
|
||||||
|
|
||||||
const CustomerSection({super.key, required this.operation});
|
const CustomerSection({super.key, required this.operation});
|
||||||
|
|
||||||
@@ -2,32 +2,32 @@ 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/providers/blocs/provider_cubit.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/master_data/providers/models/provider_model.dart';
|
||||||
import 'package:flux/features/operations/models/energy_service_model.dart'; // Assicurati degli import
|
import 'package:flux/features/operations/models/energy_operation_model.dart'; // Assicurati degli import
|
||||||
|
|
||||||
class EnergyServiceDialog extends StatefulWidget {
|
class EnergyOperationDialog extends StatefulWidget {
|
||||||
final List<EnergyServiceModel> initialServices;
|
final List<EnergyOperationModel> initialOperations;
|
||||||
final String
|
final String
|
||||||
currentStoreId; // Ci serve per sapere per quale negozio caricare i gestori
|
currentStoreId; // Ci serve per sapere per quale negozio caricare i gestori
|
||||||
|
|
||||||
const EnergyServiceDialog({
|
const EnergyOperationDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.initialServices,
|
required this.initialOperations,
|
||||||
required this.currentStoreId,
|
required this.currentStoreId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EnergyServiceDialog> createState() => _EnergyServiceDialogState();
|
State<EnergyOperationDialog> createState() => _EnergyOperationDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EnergyServiceDialogState extends State<EnergyServiceDialog> {
|
class _EnergyOperationDialogState extends State<EnergyOperationDialog> {
|
||||||
// Lista temporanea per non "sporcare" il cubit finché non si preme Conferma
|
// Lista temporanea per non "sporcare" il cubit finché non si preme Conferma
|
||||||
late List<EnergyServiceModel> _tempList;
|
late List<EnergyOperationModel> _tempList;
|
||||||
bool _isAddingNew = false;
|
bool _isAddingNew = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tempList = List.from(widget.initialServices);
|
_tempList = List.from(widget.initialOperations);
|
||||||
// Al caricamento della modale, chiediamo al Cubit di recuperare i gestori veri!
|
// Al caricamento della modale, chiediamo al Cubit di recuperare i gestori veri!
|
||||||
context.read<ProvidersCubit>().loadActiveProvidersForStore(
|
context.read<ProvidersCubit>().loadActiveProvidersForStore(
|
||||||
widget.currentStoreId,
|
widget.currentStoreId,
|
||||||
@@ -52,9 +52,9 @@ class _EnergyServiceDialogState extends State<EnergyServiceDialog> {
|
|||||||
// Cambia vista in base al flag
|
// Cambia vista in base al flag
|
||||||
child: _isAddingNew
|
child: _isAddingNew
|
||||||
? _EnergyForm(
|
? _EnergyForm(
|
||||||
onSave: (newService) {
|
onSave: (newOperation) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tempList.add(newService);
|
_tempList.add(newOperation);
|
||||||
_isAddingNew = false; // Torna alla lista
|
_isAddingNew = false; // Torna alla lista
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -101,7 +101,7 @@ class _EnergyServiceDialogState extends State<EnergyServiceDialog> {
|
|||||||
// VISTA 1: LA LISTA DEI CONTRATTI
|
// VISTA 1: LA LISTA DEI CONTRATTI
|
||||||
// ==========================================
|
// ==========================================
|
||||||
class _EnergyList extends StatelessWidget {
|
class _EnergyList extends StatelessWidget {
|
||||||
final List<EnergyServiceModel> operations;
|
final List<EnergyOperationModel> operations;
|
||||||
final List<ProviderModel>
|
final List<ProviderModel>
|
||||||
activeProviders; // <--- NUOVO: La lista vera dal Cubit
|
activeProviders; // <--- NUOVO: La lista vera dal Cubit
|
||||||
final Function(int) onDelete;
|
final Function(int) onDelete;
|
||||||
@@ -193,7 +193,7 @@ class _EnergyList extends StatelessWidget {
|
|||||||
// VISTA 2: IL FORM DI INSERIMENTO
|
// VISTA 2: IL FORM DI INSERIMENTO
|
||||||
// ==========================================
|
// ==========================================
|
||||||
class _EnergyForm extends StatefulWidget {
|
class _EnergyForm extends StatefulWidget {
|
||||||
final Function(EnergyServiceModel) onSave;
|
final Function(EnergyOperationModel) onSave;
|
||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
|
|
||||||
const _EnergyForm({required this.onSave, required this.onCancel});
|
const _EnergyForm({required this.onSave, required this.onCancel});
|
||||||
@@ -400,12 +400,12 @@ class _EnergyFormState extends State<_EnergyForm> {
|
|||||||
(_selectedProviderId == null || _selectedExpiration == null)
|
(_selectedProviderId == null || _selectedExpiration == null)
|
||||||
? null // Disabilitato se mancano dati obbligatori
|
? null // Disabilitato se mancano dati obbligatori
|
||||||
: () {
|
: () {
|
||||||
final newService = EnergyServiceModel(
|
final newOperation = EnergyOperationModel(
|
||||||
type: _selectedType,
|
type: _selectedType,
|
||||||
expiration: _selectedExpiration!,
|
expiration: _selectedExpiration!,
|
||||||
providerId: _selectedProviderId!,
|
providerId: _selectedProviderId!,
|
||||||
);
|
);
|
||||||
widget.onSave(newService);
|
widget.onSave(newOperation);
|
||||||
},
|
},
|
||||||
child: const Text("Salva Contratto"),
|
child: const Text("Salva Contratto"),
|
||||||
),
|
),
|
||||||
@@ -3,34 +3,34 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.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/master_data/providers/models/provider_model.dart';
|
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||||
import 'package:flux/features/operations/data/services_repository.dart';
|
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||||
import 'package:flux/features/operations/models/entertainment_service_model.dart';
|
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
class EntertainmentServiceDialog extends StatefulWidget {
|
class EntertainmentOperationDialog extends StatefulWidget {
|
||||||
final List<EntertainmentServiceModel> initialServices;
|
final List<EntertainmentOperationModel> initialOperations;
|
||||||
final String currentStoreId;
|
final String currentStoreId;
|
||||||
|
|
||||||
const EntertainmentServiceDialog({
|
const EntertainmentOperationDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.initialServices,
|
required this.initialOperations,
|
||||||
required this.currentStoreId,
|
required this.currentStoreId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EntertainmentServiceDialog> createState() =>
|
State<EntertainmentOperationDialog> createState() =>
|
||||||
_EntertainmentServiceDialogState();
|
_EntertainmentOperationDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EntertainmentServiceDialogState
|
class _EntertainmentOperationDialogState
|
||||||
extends State<EntertainmentServiceDialog> {
|
extends State<EntertainmentOperationDialog> {
|
||||||
late List<EntertainmentServiceModel> _tempList;
|
late List<EntertainmentOperationModel> _tempList;
|
||||||
bool _isAddingNew = false;
|
bool _isAddingNew = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tempList = List.from(widget.initialServices);
|
_tempList = List.from(widget.initialOperations);
|
||||||
// Carichiamo i provider attivi per lo store corrente
|
// Carichiamo i provider attivi per lo store corrente
|
||||||
context.read<ProvidersCubit>().loadActiveProvidersForStore(
|
context.read<ProvidersCubit>().loadActiveProvidersForStore(
|
||||||
widget.currentStoreId,
|
widget.currentStoreId,
|
||||||
@@ -57,8 +57,8 @@ class _EntertainmentServiceDialogState
|
|||||||
child: _isAddingNew
|
child: _isAddingNew
|
||||||
? _EntertainmentForm(
|
? _EntertainmentForm(
|
||||||
// Il form che abbiamo creato prima
|
// Il form che abbiamo creato prima
|
||||||
onSave: (newService) => setState(() {
|
onSave: (newOperation) => setState(() {
|
||||||
_tempList.add(newService);
|
_tempList.add(newOperation);
|
||||||
_isAddingNew = false;
|
_isAddingNew = false;
|
||||||
}),
|
}),
|
||||||
onCancel: () => setState(() => _isAddingNew = false),
|
onCancel: () => setState(() => _isAddingNew = false),
|
||||||
@@ -94,7 +94,7 @@ class _EntertainmentServiceDialogState
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EntertainmentList extends StatelessWidget {
|
class _EntertainmentList extends StatelessWidget {
|
||||||
final List<EntertainmentServiceModel> operations;
|
final List<EntertainmentOperationModel> operations;
|
||||||
final List<ProviderModel> allProviders;
|
final List<ProviderModel> allProviders;
|
||||||
final Function(int) onDelete;
|
final Function(int) onDelete;
|
||||||
final VoidCallback onAddTap;
|
final VoidCallback onAddTap;
|
||||||
@@ -194,7 +194,7 @@ class _EntertainmentList extends StatelessWidget {
|
|||||||
// ---ENTERTAINMENT FORM (MODALE)---
|
// ---ENTERTAINMENT FORM (MODALE)---
|
||||||
|
|
||||||
class _EntertainmentForm extends StatefulWidget {
|
class _EntertainmentForm extends StatefulWidget {
|
||||||
final Function(EntertainmentServiceModel) onSave;
|
final Function(EntertainmentOperationModel) onSave;
|
||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
|
|
||||||
const _EntertainmentForm({required this.onSave, required this.onCancel});
|
const _EntertainmentForm({required this.onSave, required this.onCancel});
|
||||||
@@ -280,7 +280,7 @@ class _EntertainmentFormState extends State<_EntertainmentForm> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// Suggerimenti rapidi (Chip)
|
// Suggerimenti rapidi (Chip)
|
||||||
FutureBuilder<List<String>>(
|
FutureBuilder<List<String>>(
|
||||||
future: GetIt.I<ServicesRepository>().fetchTopEntertainmentTypes(
|
future: GetIt.I<OperationsRepository>().fetchTopEntertainmentTypes(
|
||||||
GetIt.I<SessionCubit>().state.company!.id!,
|
GetIt.I<SessionCubit>().state.company!.id!,
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@@ -376,7 +376,7 @@ class _EntertainmentFormState extends State<_EntertainmentForm> {
|
|||||||
(_selectedProviderId == null || _typeController.text.isEmpty)
|
(_selectedProviderId == null || _typeController.text.isEmpty)
|
||||||
? null
|
? null
|
||||||
: () => widget.onSave(
|
: () => widget.onSave(
|
||||||
EntertainmentServiceModel(
|
EntertainmentOperationModel(
|
||||||
providerId: _selectedProviderId!,
|
providerId: _selectedProviderId!,
|
||||||
type: _typeController.text,
|
type: _typeController.text,
|
||||||
constrained: _isConstrained,
|
constrained: _isConstrained,
|
||||||
@@ -5,36 +5,36 @@ 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/blocs/product_cubit.dart';
|
||||||
import 'package:flux/features/master_data/products/models/model_model.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/operations/models/fin_service_model.dart';
|
import 'package:flux/features/operations/models/fin_operation_model.dart';
|
||||||
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// DIALOG PRINCIPALE
|
// DIALOG PRINCIPALE
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class FinanceServiceDialog extends StatefulWidget {
|
class FinanceOperationDialog extends StatefulWidget {
|
||||||
final List<FinServiceModel> initialServices;
|
final List<FinOperationModel> initialOperations;
|
||||||
final String currentStoreId;
|
final String currentStoreId;
|
||||||
final ProductCubit productCubit;
|
final ProductCubit productCubit;
|
||||||
|
|
||||||
const FinanceServiceDialog({
|
const FinanceOperationDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.initialServices,
|
required this.initialOperations,
|
||||||
required this.currentStoreId,
|
required this.currentStoreId,
|
||||||
required this.productCubit,
|
required this.productCubit,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FinanceServiceDialog> createState() => _FinanceServiceDialogState();
|
State<FinanceOperationDialog> createState() => _FinanceOperationDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FinanceServiceDialogState extends State<FinanceServiceDialog> {
|
class _FinanceOperationDialogState extends State<FinanceOperationDialog> {
|
||||||
late List<FinServiceModel> _tempList;
|
late List<FinOperationModel> _tempList;
|
||||||
bool _isAddingNew = false;
|
bool _isAddingNew = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tempList = List.from(widget.initialServices);
|
_tempList = List.from(widget.initialOperations);
|
||||||
// Carichiamo i dati necessari dai Cubit
|
// Carichiamo i dati necessari dai Cubit
|
||||||
context.read<ProvidersCubit>().loadActiveProvidersForStore(
|
context.read<ProvidersCubit>().loadActiveProvidersForStore(
|
||||||
widget.currentStoreId,
|
widget.currentStoreId,
|
||||||
@@ -109,7 +109,7 @@ class _FinanceServiceDialogState extends State<FinanceServiceDialog> {
|
|||||||
// VISTA LISTA (STORICA)
|
// VISTA LISTA (STORICA)
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class _FinanceList extends StatelessWidget {
|
class _FinanceList extends StatelessWidget {
|
||||||
final List<FinServiceModel> operations;
|
final List<FinOperationModel> operations;
|
||||||
final List<ProviderModel> allProviders;
|
final List<ProviderModel> allProviders;
|
||||||
final List<ModelModel> allModels;
|
final List<ModelModel> allModels;
|
||||||
final Function(int) onDelete;
|
final Function(int) onDelete;
|
||||||
@@ -221,7 +221,7 @@ class _FinanceList extends StatelessWidget {
|
|||||||
// FORM CON OMNI-SEARCH
|
// FORM CON OMNI-SEARCH
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class _FinanceForm extends StatefulWidget {
|
class _FinanceForm extends StatefulWidget {
|
||||||
final Function(FinServiceModel) onSave;
|
final Function(FinOperationModel) onSave;
|
||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
|
|
||||||
const _FinanceForm({required this.onSave, required this.onCancel});
|
const _FinanceForm({required this.onSave, required this.onCancel});
|
||||||
@@ -428,7 +428,7 @@ class _FinanceFormState extends State<_FinanceForm> {
|
|||||||
: () {
|
: () {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
widget.onSave(
|
widget.onSave(
|
||||||
FinServiceModel(
|
FinOperationModel(
|
||||||
providerId: _selectedProviderId!,
|
providerId: _selectedProviderId!,
|
||||||
modelId: _selectedModel!.id!,
|
modelId: _selectedModel!.id!,
|
||||||
expiration: DateTime(
|
expiration: DateTime(
|
||||||
@@ -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/operations/blocs/operations_cubit.dart';
|
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
|
|
||||||
class GeneralInfoSection extends StatelessWidget {
|
class GeneralInfoSection extends StatelessWidget {
|
||||||
final ServiceModel operation;
|
final OperationModel operation;
|
||||||
const GeneralInfoSection({super.key, required this.operation});
|
const GeneralInfoSection({super.key, required this.operation});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -44,7 +44,7 @@ class GeneralInfoSection extends StatelessWidget {
|
|||||||
prefixIcon: Icon(Icons.phone),
|
prefixIcon: Icon(Icons.phone),
|
||||||
),
|
),
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
context.read<ServicesCubit>().updateField(number: val);
|
context.read<OperationsCubit>().updateField(number: val);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -63,7 +63,7 @@ class GeneralInfoSection extends StatelessWidget {
|
|||||||
activeThumbColor: Colors.orange,
|
activeThumbColor: Colors.orange,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
context.read<ServicesCubit>().updateField(isBozza: val);
|
context.read<OperationsCubit>().updateField(isBozza: val);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -79,7 +79,9 @@ class GeneralInfoSection extends StatelessWidget {
|
|||||||
activeThumbColor: Colors.green,
|
activeThumbColor: Colors.green,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
context.read<ServicesCubit>().updateField(resultOk: val);
|
context.read<OperationsCubit>().updateField(
|
||||||
|
resultOk: val,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -100,7 +102,7 @@ class GeneralInfoSection extends StatelessWidget {
|
|||||||
alignLabelWithHint: true,
|
alignLabelWithHint: true,
|
||||||
),
|
),
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
context.read<ServicesCubit>().updateField(note: val);
|
context.read<OperationsCubit>().updateField(note: val);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
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/operations/blocs/operations_cubit.dart';
|
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/attachment_section.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/attachment_section.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/customer_section.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/customer_section.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/general_info_section.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/general_info_section.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/services_grid.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/operations_grid.dart';
|
||||||
|
|
||||||
class ServiceFormScreen extends StatefulWidget {
|
class OperationFormScreen extends StatefulWidget {
|
||||||
final String? serviceId;
|
final String? operationId;
|
||||||
final ServiceModel? existingService; // <-- AGGIUNTO
|
final OperationModel? existingOperation; // <-- AGGIUNTO
|
||||||
|
|
||||||
const ServiceFormScreen({
|
const OperationFormScreen({
|
||||||
super.key,
|
super.key,
|
||||||
this.serviceId,
|
this.operationId,
|
||||||
this.existingService, // <-- AGGIUNTO
|
this.existingOperation, // <-- AGGIUNTO
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ServiceFormScreen> createState() => _ServiceFormScreenState();
|
State<OperationFormScreen> createState() => _OperationFormScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ServiceFormScreenState extends State<ServiceFormScreen> {
|
class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// Diamo in pasto al Cubit tutto quello che abbiamo!
|
// Diamo in pasto al Cubit tutto quello che abbiamo!
|
||||||
context.read<ServicesCubit>().initServiceForm(
|
context.read<OperationsCubit>().initOperationForm(
|
||||||
existingService: widget.existingService,
|
existingOperation: widget.existingOperation,
|
||||||
serviceId: widget.serviceId,
|
operationId: widget.operationId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _performSave(BuildContext context, {required bool isBozza}) {
|
void _performSave(BuildContext context, {required bool isBozza}) {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
context.read<ServicesCubit>().saveCurrentService(isBozza: isBozza);
|
context.read<OperationsCubit>().saveCurrentOperation(isBozza: isBozza);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocConsumer<ServicesCubit, ServicesState>(
|
return BlocConsumer<OperationsCubit, OperationsState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.status == ServicesStatus.saved) {
|
if (state.status == OperationsStatus.saved) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text("Pratica salvata con successo!"),
|
content: Text("Pratica salvata con successo!"),
|
||||||
@@ -52,7 +52,7 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
|
|||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
if (state.status == ServicesStatus.failure) {
|
if (state.status == OperationsStatus.failure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text("Errore: ${state.errorMessage ?? ''}"),
|
content: Text("Errore: ${state.errorMessage ?? ''}"),
|
||||||
@@ -60,7 +60,7 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (state.status == ServicesStatus.savedNoPop) {
|
if (state.status == OperationsStatus.savedNoPop) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text("Pratica salvata con successo!"),
|
content: Text("Pratica salvata con successo!"),
|
||||||
@@ -70,9 +70,9 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final operation = state.currentService;
|
final operation = state.currentOperation;
|
||||||
final isSaving = state.status == ServicesStatus.saving;
|
final isSaving = state.status == OperationsStatus.saving;
|
||||||
final isEditMode = widget.serviceId != null;
|
final isEditMode = widget.operationId != null;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -120,7 +120,7 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
|
|||||||
GeneralInfoSection(operation: operation),
|
GeneralInfoSection(operation: operation),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
ServicesGrid(operation: operation),
|
OperationsGrid(operation: operation),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
AttachmentsSection(),
|
AttachmentsSection(),
|
||||||
@@ -3,24 +3,25 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flux/features/operations/blocs/service_files_bloc.dart';
|
import 'package:flux/features/operations/blocs/operation_files_bloc.dart';
|
||||||
|
|
||||||
class ServiceMobileUploadScreen extends StatefulWidget {
|
class OperationMobileUploadScreen extends StatefulWidget {
|
||||||
final String serviceId;
|
final String operationId;
|
||||||
final String serviceName;
|
final String operationName;
|
||||||
|
|
||||||
const ServiceMobileUploadScreen({
|
const OperationMobileUploadScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.serviceId,
|
required this.operationId,
|
||||||
required this.serviceName,
|
required this.operationName,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ServiceMobileUploadScreen> createState() =>
|
State<OperationMobileUploadScreen> createState() =>
|
||||||
_ServiceMobileUploadScreenState();
|
_OperationMobileUploadScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
|
class _OperationMobileUploadScreenState
|
||||||
|
extends State<OperationMobileUploadScreen> {
|
||||||
// 1. LA NOSTRA STAGING AREA (Il "Carrello")
|
// 1. LA NOSTRA STAGING AREA (Il "Carrello")
|
||||||
final List<PlatformFile> _stagedFiles = [];
|
final List<PlatformFile> _stagedFiles = [];
|
||||||
|
|
||||||
@@ -35,10 +36,10 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<ServiceFilesBloc, ServiceFilesState>(
|
return BlocListener<OperationFilesBloc, OperationFilesState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
// Quando il BLoC ci dice che ha finito l'upload (Success), chiudiamo la pagina!
|
// Quando il BLoC ci dice che ha finito l'upload (Success), chiudiamo la pagina!
|
||||||
if (state.status == ServiceFilesStatus.success && _isUploading) {
|
if (state.status == OperationFilesStatus.success && _isUploading) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text("Tutti i file caricati con successo! ✅"),
|
content: Text("Tutti i file caricati con successo! ✅"),
|
||||||
@@ -46,7 +47,7 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
|
|||||||
);
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
if (state.status == ServiceFilesStatus.failure) {
|
if (state.status == OperationFilesStatus.failure) {
|
||||||
setState(() => _isUploading = false);
|
setState(() => _isUploading = false);
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
@@ -55,7 +56,7 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
|
|||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("Upload Pratica:\n${widget.serviceName}"),
|
title: Text("Upload Pratica:\n${widget.operationName}"),
|
||||||
automaticallyImplyLeading: !_isUploading,
|
automaticallyImplyLeading: !_isUploading,
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
@@ -294,8 +295,8 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
|
|||||||
|
|
||||||
// Diciamo al BLoC di caricare tutti i file.
|
// Diciamo al BLoC di caricare tutti i file.
|
||||||
// Usiamo il tuo evento esistente per ogni file (il BLoC li metterà in coda)
|
// Usiamo il tuo evento esistente per ogni file (il BLoC li metterà in coda)
|
||||||
final bloc = context.read<ServiceFilesBloc>();
|
final bloc = context.read<OperationFilesBloc>();
|
||||||
bloc.add(UploadMultipleServiceFilesEvent(_stagedFiles));
|
bloc.add(UploadMultipleOperationFilesEvent(_stagedFiles));
|
||||||
|
|
||||||
// N.B: Il Navigator.pop() viene chiamato dal BlocListener in alto quando lo stato diventa "success"!
|
// N.B: Il Navigator.pop() viene chiamato dal BlocListener in alto quando lo stato diventa "success"!
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,20 @@ 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/blocs/product_cubit.dart';
|
||||||
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||||
import 'package:flux/features/operations/models/energy_service_model.dart';
|
import 'package:flux/features/operations/models/energy_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/entertainment_service_model.dart';
|
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/fin_service_model.dart';
|
import 'package:flux/features/operations/models/fin_operation_model.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/action_card.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/action_card.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/energy_service_dialog.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/energy_operation_dialog.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/entertainment_service_card.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/entertainment_operation_card.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/finance_service_dialog.dart';
|
import 'package:flux/features/operations/ui/operation_form_screen/finance_operation_dialog.dart';
|
||||||
import 'package:flux/features/operations/ui/service_form_screen/int_dialogs.dart'; // Assicurati di importare il modello
|
import 'package:flux/features/operations/ui/operation_form_screen/int_dialogs.dart'; // Assicurati di importare il modello
|
||||||
|
|
||||||
class ServicesGrid extends StatelessWidget {
|
class OperationsGrid extends StatelessWidget {
|
||||||
final ServiceModel operation;
|
final OperationModel operation;
|
||||||
|
|
||||||
const ServicesGrid({super.key, required this.operation});
|
const OperationsGrid({super.key, required this.operation});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -60,7 +60,7 @@ class ServicesGrid extends StatelessWidget {
|
|||||||
"AL",
|
"AL",
|
||||||
operation.al,
|
operation.al,
|
||||||
(val) =>
|
(val) =>
|
||||||
context.read<ServicesCubit>().updateField(al: val),
|
context.read<OperationsCubit>().updateField(al: val),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ActionCard(
|
ActionCard(
|
||||||
@@ -73,7 +73,7 @@ class ServicesGrid extends StatelessWidget {
|
|||||||
"MNP",
|
"MNP",
|
||||||
operation.mnp,
|
operation.mnp,
|
||||||
(val) =>
|
(val) =>
|
||||||
context.read<ServicesCubit>().updateField(mnp: val),
|
context.read<OperationsCubit>().updateField(mnp: val),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ActionCard(
|
ActionCard(
|
||||||
@@ -86,7 +86,7 @@ class ServicesGrid extends StatelessWidget {
|
|||||||
"NIP",
|
"NIP",
|
||||||
operation.nip,
|
operation.nip,
|
||||||
(val) =>
|
(val) =>
|
||||||
context.read<ServicesCubit>().updateField(nip: val),
|
context.read<OperationsCubit>().updateField(nip: val),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ActionCard(
|
ActionCard(
|
||||||
@@ -98,8 +98,9 @@ class ServicesGrid extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
"Unica",
|
"Unica",
|
||||||
operation.unica,
|
operation.unica,
|
||||||
(val) =>
|
(val) => context.read<OperationsCubit>().updateField(
|
||||||
context.read<ServicesCubit>().updateField(unica: val),
|
unica: val,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ActionCard(
|
ActionCard(
|
||||||
@@ -111,7 +112,7 @@ class ServicesGrid extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
"Telepass",
|
"Telepass",
|
||||||
operation.telepass,
|
operation.telepass,
|
||||||
(val) => context.read<ServicesCubit>().updateField(
|
(val) => context.read<OperationsCubit>().updateField(
|
||||||
telepass: val,
|
telepass: val,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -120,23 +121,24 @@ class ServicesGrid extends StatelessWidget {
|
|||||||
// --- MODULI COMPLESSI (Le liste) ---
|
// --- MODULI COMPLESSI (Le liste) ---
|
||||||
ActionCard(
|
ActionCard(
|
||||||
label: "Energia",
|
label: "Energia",
|
||||||
count: operation.energyServices.length,
|
count: operation.energyOperations.length,
|
||||||
icon: Icons.bolt,
|
icon: Icons.bolt,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// Apriamo la modale e aspettiamo il risultato
|
// Apriamo la modale e aspettiamo il risultato
|
||||||
final result = await showDialog<List<EnergyServiceModel>>(
|
final result =
|
||||||
|
await showDialog<List<EnergyOperationModel>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => EnergyServiceDialog(
|
builder: (context) => EnergyOperationDialog(
|
||||||
currentStoreId: operation.storeId,
|
currentStoreId: operation.storeId,
|
||||||
initialServices: operation
|
initialOperations: operation
|
||||||
.energyServices, // Passiamo la lista attuale
|
.energyOperations, // Passiamo la lista attuale
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Se l'utente ha premuto "Conferma" e non "Annulla" o tap fuori
|
// Se l'utente ha premuto "Conferma" e non "Annulla" o tap fuori
|
||||||
if (result != null && context.mounted) {
|
if (result != null && context.mounted) {
|
||||||
context.read<ServicesCubit>().updateEnergyServices(
|
context.read<OperationsCubit>().updateEnergyOperations(
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -144,44 +146,47 @@ class ServicesGrid extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
ActionCard(
|
ActionCard(
|
||||||
label: "Finanziam.",
|
label: "Finanziam.",
|
||||||
count: operation.finServices.length,
|
count: operation.finOperations.length,
|
||||||
icon: Icons.euro_symbol,
|
icon: Icons.euro_symbol,
|
||||||
color: Colors.teal,
|
color: Colors.teal,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await showDialog<List<FinServiceModel>>(
|
final result = await showDialog<List<FinOperationModel>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => FinanceServiceDialog(
|
builder: (context) => FinanceOperationDialog(
|
||||||
productCubit: context.read<ProductCubit>(),
|
productCubit: context.read<ProductCubit>(),
|
||||||
currentStoreId: operation.storeId,
|
currentStoreId: operation.storeId,
|
||||||
initialServices: operation
|
initialOperations: operation
|
||||||
.finServices, // Passiamo la lista attuale
|
.finOperations, // Passiamo la lista attuale
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != null && context.mounted) {
|
if (result != null && context.mounted) {
|
||||||
context.read<ServicesCubit>().updateFinServices(result);
|
context.read<OperationsCubit>().updateFinOperations(
|
||||||
|
result,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ActionCard(
|
ActionCard(
|
||||||
label: "Intratten.",
|
label: "Intratten.",
|
||||||
count: operation.entertainmentServices.length,
|
count: operation.entertainmentOperations.length,
|
||||||
icon: Icons.movie_filter_outlined,
|
icon: Icons.movie_filter_outlined,
|
||||||
color: Colors.purple,
|
color: Colors.purple,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result =
|
final result =
|
||||||
await showDialog<List<EntertainmentServiceModel>>(
|
await showDialog<List<EntertainmentOperationModel>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => EntertainmentServiceDialog(
|
builder: (context) => EntertainmentOperationDialog(
|
||||||
initialServices: operation.entertainmentServices,
|
initialOperations:
|
||||||
|
operation.entertainmentOperations,
|
||||||
currentStoreId: operation.storeId,
|
currentStoreId: operation.storeId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != null && context.mounted) {
|
if (result != null && context.mounted) {
|
||||||
context
|
context
|
||||||
.read<ServicesCubit>()
|
.read<OperationsCubit>()
|
||||||
.updateEntertainmentServices(result);
|
.updateEntertainmentOperations(result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
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/operations/blocs/operations_cubit.dart';
|
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
import 'package:flux/features/operations/utils/service_actions.dart';
|
import 'package:flux/features/operations/utils/operation_actions.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
// Importa i tuoi modelli e cubit
|
// Importa i tuoi modelli e cubit
|
||||||
|
|
||||||
class ServicesScreen extends StatefulWidget {
|
class OperationsScreen extends StatefulWidget {
|
||||||
const ServicesScreen({super.key});
|
const OperationsScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ServicesScreen> createState() => _ServicesScreenState();
|
State<OperationsScreen> createState() => _OperationsScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ServicesScreenState extends State<ServicesScreen> {
|
class _OperationsScreenState extends State<OperationsScreen> {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -22,12 +22,12 @@ class _ServicesScreenState extends State<ServicesScreen> {
|
|||||||
// Agganciamo il listener per la paginazione (Scroll Infinito)
|
// Agganciamo il listener per la paginazione (Scroll Infinito)
|
||||||
_scrollController.addListener(_onScroll);
|
_scrollController.addListener(_onScroll);
|
||||||
// Carichiamo i servizi iniziali
|
// Carichiamo i servizi iniziali
|
||||||
context.read<ServicesCubit>().loadServices();
|
context.read<OperationsCubit>().loadOperations();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onScroll() {
|
void _onScroll() {
|
||||||
if (_isBottom) {
|
if (_isBottom) {
|
||||||
context.read<ServicesCubit>().loadServices();
|
context.read<OperationsCubit>().loadOperations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,16 +60,16 @@ class _ServicesScreenState extends State<ServicesScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: BlocBuilder<ServicesCubit, ServicesState>(
|
body: BlocBuilder<OperationsCubit, OperationsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
// 1. Stato di caricamento iniziale
|
// 1. Stato di caricamento iniziale
|
||||||
if (state.status == ServicesStatus.loading &&
|
if (state.status == OperationsStatus.loading &&
|
||||||
state.allServices.isEmpty) {
|
state.allOperations.isEmpty) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Lista vuota
|
// 2. Lista vuota
|
||||||
if (state.allServices.isEmpty) {
|
if (state.allOperations.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -77,9 +77,9 @@ class _ServicesScreenState extends State<ServicesScreen> {
|
|||||||
const Text("Nessuna pratica trovata."),
|
const Text("Nessuna pratica trovata."),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => context.read<ServicesCubit>().loadServices(
|
onPressed: () => context
|
||||||
refresh: true,
|
.read<OperationsCubit>()
|
||||||
),
|
.loadOperations(refresh: true),
|
||||||
child: const Text("Riprova"),
|
child: const Text("Riprova"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -90,15 +90,15 @@ class _ServicesScreenState extends State<ServicesScreen> {
|
|||||||
// 3. La Lista (con Pull-to-refresh)
|
// 3. La Lista (con Pull-to-refresh)
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () =>
|
onRefresh: () =>
|
||||||
context.read<ServicesCubit>().loadServices(refresh: true),
|
context.read<OperationsCubit>().loadOperations(refresh: true),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
padding: const EdgeInsets.only(bottom: 80), // Spazio per il FAB
|
padding: const EdgeInsets.only(bottom: 80), // Spazio per il FAB
|
||||||
itemCount: state.hasReachedMax
|
itemCount: state.hasReachedMax
|
||||||
? state.allServices.length
|
? state.allOperations.length
|
||||||
: state.allServices.length + 1,
|
: state.allOperations.length + 1,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index >= state.allServices.length) {
|
if (index >= state.allOperations.length) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
@@ -107,21 +107,21 @@ class _ServicesScreenState extends State<ServicesScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final operation = state.allServices[index];
|
final operation = state.allOperations[index];
|
||||||
return _buildServiceCard(context, operation);
|
return _buildOperationCard(context, operation);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () => startNewService(context),
|
onPressed: () => startNewOperation(context),
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildServiceCard(BuildContext context, ServiceModel operation) {
|
Widget _buildOperationCard(BuildContext context, OperationModel operation) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
@@ -164,11 +164,11 @@ class _ServicesScreenState extends State<ServicesScreen> {
|
|||||||
children: [
|
children: [
|
||||||
if (operation.al > 0 || operation.mnp > 0)
|
if (operation.al > 0 || operation.mnp > 0)
|
||||||
_miniBadge("📞 Tel", Colors.blue),
|
_miniBadge("📞 Tel", Colors.blue),
|
||||||
if (operation.energyServices.isNotEmpty)
|
if (operation.energyOperations.isNotEmpty)
|
||||||
_miniBadge("⚡ Energy", Colors.green),
|
_miniBadge("⚡ Energy", Colors.green),
|
||||||
if (operation.finServices.isNotEmpty)
|
if (operation.finOperations.isNotEmpty)
|
||||||
_miniBadge("💰 Fin", Colors.purple),
|
_miniBadge("💰 Fin", Colors.purple),
|
||||||
if (operation.entertainmentServices.isNotEmpty)
|
if (operation.entertainmentOperations.isNotEmpty)
|
||||||
_miniBadge("📺 Ent", Colors.red),
|
_miniBadge("📺 Ent", Colors.red),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -180,7 +180,7 @@ class _ServicesScreenState extends State<ServicesScreen> {
|
|||||||
extra: operation, // <-- LA MAGIA È QUI: Passa l'oggetto intero!
|
extra: operation, // <-- LA MAGIA È QUI: Passa l'oggetto intero!
|
||||||
// Teniamo anche il parametro URL per coerenza di routing
|
// Teniamo anche il parametro URL per coerenza di routing
|
||||||
queryParameters: operation.id != null
|
queryParameters: operation.id != null
|
||||||
? {'serviceId': operation.id!}
|
? {'operationId': operation.id!}
|
||||||
: {},
|
: {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/features/master_data/store/bloc/store_cubit.dart';
|
import 'package:flux/features/master_data/store/bloc/store_cubit.dart';
|
||||||
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||||
import 'package:flux/features/operations/models/service_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// Avvia la creazione di un nuovo servizio partendo dalla selezione dell'operatore.
|
/// Avvia la creazione di un nuovo servizio partendo dalla selezione dell'operatore.
|
||||||
void startNewService(BuildContext context) {
|
void startNewOperation(BuildContext context) {
|
||||||
final session = context.read<SessionCubit>().state;
|
final session = context.read<SessionCubit>().state;
|
||||||
final currentStoreId = session.currentStore?.id;
|
final currentStoreId = session.currentStore?.id;
|
||||||
|
|
||||||
@@ -53,8 +53,8 @@ void startNewService(BuildContext context) {
|
|||||||
title: Text(member.name),
|
title: Text(member.name),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// 1. Inizializza il form nel Cubit
|
// 1. Inizializza il form nel Cubit
|
||||||
context.read<ServicesCubit>().initServiceForm(
|
context.read<OperationsCubit>().initOperationForm(
|
||||||
existingService: ServiceModel(
|
existingOperation: OperationModel(
|
||||||
storeId: currentStoreId,
|
storeId: currentStoreId,
|
||||||
employeeId: member.id,
|
employeeId: member.id,
|
||||||
number: '',
|
number: '',
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
"welcomeBack": "Welcome back, {name}! 👋",
|
"welcomeBack": "Welcome back, {name}! 👋",
|
||||||
"latestServices": "Latest Operations",
|
"latestOperations": "Latest Operations",
|
||||||
"masterData": "Master Data",
|
"masterData": "Master Data",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"newService": "Operation",
|
"newOperation": "Operation",
|
||||||
"expiring_contracts": "Expiring Contracts",
|
"expiring_contracts": "Expiring Contracts",
|
||||||
"sticky_notes": "Sticky Notes",
|
"sticky_notes": "Sticky Notes",
|
||||||
"my_tasks": "My Tasks",
|
"my_tasks": "My Tasks",
|
||||||
"latest_service_tickets": "Latest operation tickets"
|
"latest_operation_tickets": "Latest operation tickets"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -50,15 +50,15 @@
|
|||||||
"commonNewPassword": "Nuova Password",
|
"commonNewPassword": "Nuova Password",
|
||||||
"commonNote": "Nota",
|
"commonNote": "Nota",
|
||||||
"commonSave": "Salva",
|
"commonSave": "Salva",
|
||||||
"commonService": "Servizio",
|
"commonOperation": "Servizio",
|
||||||
"commonSettings": "Impostazioni",
|
"commonSettings": "Impostazioni",
|
||||||
"commonStickyNotes": "Sticky Notes",
|
"commonStickyNotes": "Sticky Notes",
|
||||||
"commonTask": "Attività",
|
"commonTask": "Attività",
|
||||||
"homeExpiringContracts": "Contratti in scadenza",
|
"homeExpiringContracts": "Contratti in scadenza",
|
||||||
"homeLatestServiceTickets": "Ultime assistenze",
|
"homeLatestOperationTickets": "Ultime assistenze",
|
||||||
"homeLatestServices": "Ultimi Servizi",
|
"homeLatestOperations": "Ultimi Servizi",
|
||||||
"homeMyTasks": "Mie Attività",
|
"homeMyTasks": "Mie Attività",
|
||||||
"homeNewServiceTicket": "Nuova assistenza",
|
"homeNewOperationTicket": "Nuova assistenza",
|
||||||
"homeNoStoreFound": "Nessun negozio trovato",
|
"homeNoStoreFound": "Nessun negozio trovato",
|
||||||
"homeWelcomeBack": "Bentornato, {name}! 👋",
|
"homeWelcomeBack": "Bentornato, {name}! 👋",
|
||||||
"imageViewerWidgetErrorOpening": "Errore durante l'apertura dell'immagine",
|
"imageViewerWidgetErrorOpening": "Errore durante l'apertura dell'immagine",
|
||||||
|
|||||||
@@ -220,11 +220,11 @@ abstract class AppLocalizations {
|
|||||||
/// **'Salva'**
|
/// **'Salva'**
|
||||||
String get commonSave;
|
String get commonSave;
|
||||||
|
|
||||||
/// No description provided for @commonService.
|
/// No description provided for @commonOperation.
|
||||||
///
|
///
|
||||||
/// In it, this message translates to:
|
/// In it, this message translates to:
|
||||||
/// **'Servizio'**
|
/// **'Servizio'**
|
||||||
String get commonService;
|
String get commonOperation;
|
||||||
|
|
||||||
/// No description provided for @commonSettings.
|
/// No description provided for @commonSettings.
|
||||||
///
|
///
|
||||||
@@ -250,17 +250,17 @@ abstract class AppLocalizations {
|
|||||||
/// **'Contratti in scadenza'**
|
/// **'Contratti in scadenza'**
|
||||||
String get homeExpiringContracts;
|
String get homeExpiringContracts;
|
||||||
|
|
||||||
/// No description provided for @homeLatestServiceTickets.
|
/// No description provided for @homeLatestOperationTickets.
|
||||||
///
|
///
|
||||||
/// In it, this message translates to:
|
/// In it, this message translates to:
|
||||||
/// **'Ultime assistenze'**
|
/// **'Ultime assistenze'**
|
||||||
String get homeLatestServiceTickets;
|
String get homeLatestOperationTickets;
|
||||||
|
|
||||||
/// No description provided for @homeLatestServices.
|
/// No description provided for @homeLatestOperations.
|
||||||
///
|
///
|
||||||
/// In it, this message translates to:
|
/// In it, this message translates to:
|
||||||
/// **'Ultimi Servizi'**
|
/// **'Ultimi Servizi'**
|
||||||
String get homeLatestServices;
|
String get homeLatestOperations;
|
||||||
|
|
||||||
/// No description provided for @homeMyTasks.
|
/// No description provided for @homeMyTasks.
|
||||||
///
|
///
|
||||||
@@ -268,11 +268,11 @@ abstract class AppLocalizations {
|
|||||||
/// **'Mie Attività'**
|
/// **'Mie Attività'**
|
||||||
String get homeMyTasks;
|
String get homeMyTasks;
|
||||||
|
|
||||||
/// No description provided for @homeNewServiceTicket.
|
/// No description provided for @homeNewOperationTicket.
|
||||||
///
|
///
|
||||||
/// In it, this message translates to:
|
/// In it, this message translates to:
|
||||||
/// **'Nuova assistenza'**
|
/// **'Nuova assistenza'**
|
||||||
String get homeNewServiceTicket;
|
String get homeNewOperationTicket;
|
||||||
|
|
||||||
/// No description provided for @homeNoStoreFound.
|
/// No description provided for @homeNoStoreFound.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get homeExpiringContracts => 'Contratti in scadenza';
|
String get homeExpiringContracts => 'Contratti in scadenza';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeLatestServiceTickets => 'Ultime assistenze';
|
String get homeLatestOperationTickets => 'Ultime assistenze';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeLatestServices => 'Ultimi Servizi';
|
String get homeLatestOperations => 'Ultimi Servizi';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeMasterData => 'Anagrafiche';
|
String get homeMasterData => 'Anagrafiche';
|
||||||
@@ -24,7 +24,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get homeMyTasks => 'Mie Attività';
|
String get homeMyTasks => 'Mie Attività';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeNewService => 'Servizio';
|
String get homeNewOperation => 'Servizio';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeSettings => 'Impostazioni';
|
String get homeSettings => 'Impostazioni';
|
||||||
@@ -41,7 +41,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get homeNoStoreFound => 'Nessun negozio trovato';
|
String get homeNoStoreFound => 'Nessun negozio trovato';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeNewServiceTicket => 'Nuova assistenza';
|
String get homeNewOperationTicket => 'Nuova assistenza';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeNewNote => 'Nota';
|
String get homeNewNote => 'Nota';
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
String get commonSave => 'Salva';
|
String get commonSave => 'Salva';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get commonService => 'Servizio';
|
String get commonOperation => 'Servizio';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get commonSettings => 'Impostazioni';
|
String get commonSettings => 'Impostazioni';
|
||||||
@@ -96,16 +96,16 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
String get homeExpiringContracts => 'Contratti in scadenza';
|
String get homeExpiringContracts => 'Contratti in scadenza';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeLatestServiceTickets => 'Ultime assistenze';
|
String get homeLatestOperationTickets => 'Ultime assistenze';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeLatestServices => 'Ultimi Servizi';
|
String get homeLatestOperations => 'Ultimi Servizi';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeMyTasks => 'Mie Attività';
|
String get homeMyTasks => 'Mie Attività';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeNewServiceTicket => 'Nuova assistenza';
|
String get homeNewOperationTicket => 'Nuova assistenza';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeNoStoreFound => 'Nessun negozio trovato';
|
String get homeNoStoreFound => 'Nessun negozio trovato';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
||||||
import 'package:flux/features/home/latest_store_services/bloc/latest_store_services_bloc.dart';
|
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||||
import 'package:flux/l10n/app_localizations.dart';
|
import 'package:flux/l10n/app_localizations.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -27,7 +27,6 @@ import 'package:flux/features/master_data/staff/data/staff_repository.dart';
|
|||||||
import 'package:flux/features/master_data/store/bloc/store_cubit.dart';
|
import 'package:flux/features/master_data/store/bloc/store_cubit.dart';
|
||||||
import 'package:flux/features/master_data/store/data/store_repository.dart';
|
import 'package:flux/features/master_data/store/data/store_repository.dart';
|
||||||
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||||
import 'package:flux/features/operations/data/services_repository.dart';
|
|
||||||
import 'package:flux/features/settings/settings.dart';
|
import 'package:flux/features/settings/settings.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -52,7 +51,7 @@ void main() async {
|
|||||||
BlocProvider<CustomerCubit>(create: (_) => CustomerCubit()),
|
BlocProvider<CustomerCubit>(create: (_) => CustomerCubit()),
|
||||||
BlocProvider<ProductCubit>(create: (_) => ProductCubit()),
|
BlocProvider<ProductCubit>(create: (_) => ProductCubit()),
|
||||||
BlocProvider<StaffCubit>(create: (_) => StaffCubit()),
|
BlocProvider<StaffCubit>(create: (_) => StaffCubit()),
|
||||||
BlocProvider<ServicesCubit>(create: (_) => ServicesCubit()),
|
BlocProvider<OperationsCubit>(create: (_) => OperationsCubit()),
|
||||||
BlocProvider<ProvidersCubit>(create: (_) => ProvidersCubit()),
|
BlocProvider<ProvidersCubit>(create: (_) => ProvidersCubit()),
|
||||||
],
|
],
|
||||||
child: const FluxApp(),
|
child: const FluxApp(),
|
||||||
@@ -85,7 +84,9 @@ Future<void> setupLocator() async {
|
|||||||
getIt.registerLazySingleton<CustomerRepository>(() => CustomerRepository());
|
getIt.registerLazySingleton<CustomerRepository>(() => CustomerRepository());
|
||||||
getIt.registerLazySingleton<ProductRepository>(() => ProductRepository());
|
getIt.registerLazySingleton<ProductRepository>(() => ProductRepository());
|
||||||
getIt.registerLazySingleton<StaffRepository>(() => StaffRepository());
|
getIt.registerLazySingleton<StaffRepository>(() => StaffRepository());
|
||||||
getIt.registerLazySingleton<ServicesRepository>(() => ServicesRepository());
|
getIt.registerLazySingleton<OperationsRepository>(
|
||||||
|
() => OperationsRepository(),
|
||||||
|
);
|
||||||
getIt.registerLazySingleton<ProviderRepository>(() => ProviderRepository());
|
getIt.registerLazySingleton<ProviderRepository>(() => ProviderRepository());
|
||||||
|
|
||||||
// NOTA: CompanyRepository l'ho tolto perché la logica della Company
|
// NOTA: CompanyRepository l'ho tolto perché la logica della Company
|
||||||
|
|||||||
Reference in New Issue
Block a user