reworked operation (#12)

Reviewed-on: #12
Co-authored-by: Mark M2 Macbook <marco@catelli.it>
Co-committed-by: Mark M2 Macbook <marco@catelli.it>
This commit is contained in:
2026-05-04 15:36:42 +02:00
committed by brontomark
parent 9f57207a39
commit 94ad524bae
110 changed files with 5831 additions and 5306 deletions

View File

@@ -24,7 +24,7 @@ class CoreRepository {
return CompanyModel.fromMap(response);
} catch (e) {
debugPrint('Errore recupero azienda: $e');
throw Exception('Errore recupero azienda: $e');
throw Exception('$e');
}
}
@@ -38,7 +38,7 @@ class CoreRepository {
if (response == null) return null;
return CompanyModel.fromMap(response);
} catch (e) {
debugPrint('Errore recupero azienda per ID: $e');
debugPrint('$e');
return null;
}
}
@@ -50,12 +50,12 @@ class CoreRepository {
.select()
.eq('company_id', companyId)
.eq('is_active', true) // Buona pratica
.order('nome'); // O come si chiama il campo nome
.order('name'); // O come si chiama il campo nome
return (response as List).map((s) => StoreModel.fromMap(s)).toList();
} catch (e) {
debugPrint('Errore recupero negozi: $e');
throw Exception('Errore recupero negozi: $e');
throw Exception('$e');
}
}
@@ -71,7 +71,7 @@ class CoreRepository {
return StaffMemberModel.fromMap(response);
} catch (e) {
debugPrint('Errore recupero profilo staff: $e');
throw Exception('Errore recupero profilo staff: $e');
throw Exception('$e');
}
}
@@ -87,7 +87,7 @@ class CoreRepository {
return CompanyModel.fromMap(response);
} catch (e) {
debugPrint('Creazione azienda fallita: $e');
throw Exception('Creazione azienda fallita: $e');
throw Exception('$e');
}
}
@@ -101,7 +101,7 @@ class CoreRepository {
return StoreModel.fromMap(response);
} catch (e) {
debugPrint('Creazione negozio fallita: $e');
throw Exception('Creazione negozio fallita: $e');
throw Exception('$e');
}
}
@@ -120,7 +120,7 @@ class CoreRepository {
return StaffMemberModel.fromMap(response);
} catch (e) {
debugPrint('Creazione profilo staff fallita: $e');
throw Exception('Creazione profilo staff fallita: $e');
throw Exception('$e');
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:go_router/go_router.dart';
class AppShell extends StatelessWidget {
@@ -43,21 +44,21 @@ class AppShell extends StatelessWidget {
onDestinationSelected: (index) =>
_onItemTapped(index, context),
labelType: NavigationRailLabelType.all,
destinations: const [
destinations: [
NavigationRailDestination(
icon: Icon(Icons.dashboard_outlined),
selectedIcon: Icon(Icons.dashboard),
label: Text('Dashboard'),
label: Text(context.l10n.commonDashboard),
),
NavigationRailDestination(
icon: Icon(Icons.folder_special_outlined),
selectedIcon: Icon(Icons.folder_special),
label: Text('Anagrafiche'),
label: Text(context.l10n.commonMasterData),
),
NavigationRailDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: Text('Impostazioni'),
label: Text(context.l10n.commonSettings),
),
],
),
@@ -73,21 +74,21 @@ class AppShell extends StatelessWidget {
: NavigationBar(
selectedIndex: currentIndex,
onDestinationSelected: (index) => _onItemTapped(index, context),
destinations: const [
destinations: [
NavigationDestination(
icon: Icon(Icons.dashboard_outlined),
selectedIcon: Icon(Icons.dashboard),
label: 'Dashboard',
label: context.l10n.commonDashboard,
),
NavigationDestination(
icon: Icon(Icons.folder_special_outlined),
selectedIcon: Icon(Icons.folder_special),
label: 'Anagrafiche',
label: context.l10n.commonMasterData,
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Impostazioni',
label: context.l10n.commonSettings,
),
],
),

View File

@@ -4,22 +4,31 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/data/core_repository.dart';
import 'package:flux/core/layout/app_shell.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/core/widgets/set_password_screen.dart';
import 'package:flux/features/auth/ui/auth_screen.dart';
import 'package:flux/features/customers/blocs/customer_files_bloc.dart';
import 'package:flux/features/customers/blocs/customers_cubit.dart';
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_mobile_upload_screen.dart';
import 'package:flux/features/customers/ui/customers_content.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/products/blocs/product_cubit.dart';
import 'package:flux/features/master_data/products/ui/products_screen.dart';
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
import 'package:flux/features/master_data/providers/ui/providers_master_data_screen.dart';
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
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/onboarding/blocs/onboarding_cubit.dart';
import 'package:flux/features/onboarding/ui/onboarding_screen.dart';
import 'package:flux/features/services/blocs/service_files_bloc.dart';
import 'package:flux/features/services/models/service_model.dart';
import 'package:flux/features/services/ui/service_form_screen/service_form_screen.dart';
import 'package:flux/features/services/ui/service_form_screen/service_mobile_upload_screen.dart';
import 'package:flux/features/operations/blocs/operation_files_bloc.dart';
import 'package:flux/features/operations/models/operation_model.dart';
import 'package:flux/features/operations/ui/operation_form_screen.dart';
import 'package:flux/features/operations/ui/operation_mobile_upload_screen.dart';
import 'package:flux/features/operations/ui/operations_screen.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
@@ -92,23 +101,24 @@ class AppRouter {
routes: [
GoRoute(
path: 'products', // Diventa /master-data/products
builder: (context, state) => const ProductsScreen(),
builder: (context, state) {
context.read<ProductsCubit>().refreshCubit();
return const ProductsScreen();
},
),
GoRoute(
path: 'staff', // Diventa /master-data/staff
builder: (context, state) =>
const Scaffold(body: Center(child: Text("Lista Staff"))),
builder: (context, state) => const StaffScreen(),
),
GoRoute(
path: 'stores', // Diventa /master-data/stores
builder: (context, state) =>
const Scaffold(body: Center(child: Text("Lista Negozi"))),
builder: (context, state) => const StoresScreen(),
),
GoRoute(
path: 'providers', // Diventa /master-data/providers
builder: (context, state) => const Scaffold(
body: Center(child: Text("Lista Fornitori")),
),
builder: (context, state) =>
const ProvidersMasterDataScreen(),
),
],
),
@@ -117,7 +127,7 @@ class AppRouter {
GoRoute(
path: '/settings',
builder: (context, state) => Scaffold(
appBar: AppBar(title: const Text("Impostazioni")),
appBar: AppBar(title: Text(context.l10n.commonSettings)),
body: Center(
child: ElevatedButton.icon(
onPressed: () => context.read<SessionCubit>().signOut(),
@@ -127,15 +137,19 @@ class AppRouter {
),
),
),
GoRoute(
path: '/operations',
builder: (context, state) => const OperationsScreen(),
),
GoRoute(
path: '/customers',
builder: (context, state) =>
const CustomersContent(), // O come si chiama il tuo widget della lista!
),
],
),
// --- DETTAGLI E OPERATIVITÀ (FUORI DALLA SHELL - TUTTO SCHERMO) ---
GoRoute(
path: '/customers',
builder: (context, state) =>
const CustomersContent(), // O come si chiama il tuo widget della lista!
),
GoRoute(
path: '/customer/:id',
builder: (context, state) {
@@ -161,31 +175,58 @@ class AppRouter {
},
),
GoRoute(
path: '/service-form',
name: 'service-form',
path: '/operation-form',
name: 'operation-form',
builder: (context, state) {
final existingService = state.extra as ServiceModel?;
final serviceId = state.uri.queryParameters['serviceId'];
final existingOperation = state.extra as OperationModel?;
final operationId = state.uri.queryParameters['operationId'];
final currentStoreId = GetIt.I
.get<SessionCubit>()
.state
.currentStore!
.id!;
context.read<CustomersCubit>().loadCustomers();
context.read<ProvidersCubit>().loadActiveProvidersForStore(
currentStoreId,
);
context.read<ProductsCubit>().loadModels();
context.read<ProductsCubit>().loadBrands();
context.read<StaffCubit>().loadStaffForStore(currentStoreId);
return BlocProvider(
create: (context) =>
ServiceFilesBloc(serviceId: serviceId ?? existingService?.id),
child: ServiceFormScreen(
serviceId: serviceId ?? existingService?.id,
existingService: existingService,
create: (context) => OperationFilesBloc(
operationId: operationId ?? existingOperation?.id,
),
child: OperationFormScreen(
operationId: operationId ?? existingOperation?.id,
existingOperation: existingOperation,
),
);
},
),
GoRoute(
path: '/service/:id/upload',
path: '/operation/:id/upload',
builder: (context, state) {
final serviceId = state.pathParameters['id']!;
final serviceName = state.uri.queryParameters['name'] ?? 'Pratica';
final operationId = state.pathParameters['id']!;
final operationName =
state.uri.queryParameters['name'] ?? 'Pratica';
final currentStoreId = GetIt.I
.get<SessionCubit>()
.state
.currentStore!
.id!;
context.read<CustomersCubit>().loadCustomers();
context.read<ProvidersCubit>().loadActiveProvidersForStore(
currentStoreId,
);
context.read<ProductsCubit>().loadModels();
context.read<ProductsCubit>().loadBrands();
context.read<StaffCubit>().loadStaffForStore(currentStoreId);
return BlocProvider(
create: (context) => ServiceFilesBloc(serviceId: serviceId),
child: ServiceMobileUploadScreen(
serviceId: serviceId,
serviceName: serviceName,
create: (context) => OperationFilesBloc(operationId: operationId),
child: OperationMobileUploadScreen(
operationId: operationId,
operationName: operationName,
),
);
},

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flux/core/utils/extensions.dart';
class AppMessage {
final String key;
final String? argument;
const AppMessage({required this.key, this.argument});
String translatedMessage(BuildContext context) {
switch (key) {
case 'authCubitCheckEmailToConfirmAccount':
return context.l10n.authCubitCheckEmailToConfirmAccount;
case 'authCubitResetPasswordEmailSentTo':
return context.l10n.authCubitResetPasswordEmailSentTo(argument!);
default:
return 'empty message';
}
}
}

View File

@@ -1,3 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flux/l10n/app_localizations.dart';
extension MyStringExtensions on String? {
// Gestiamo anche il nullable per sicurezza
String myFormat() {
@@ -40,3 +43,7 @@ extension MyStringExtensions on String? {
.join('.'); // Ritorna tutto tranne l'ultima parte
}
}
extension LocalizationExtension on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this)!;
}

View File

@@ -1,5 +1,6 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/core/utils/functions.dart';
class ImageViewerWidget extends StatelessWidget {
@@ -36,8 +37,8 @@ class ImageViewerWidget extends StatelessWidget {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return const Text(
"Errore caricamento immagine (Permessi negati?)",
return Text(
context.l10n.imageViewerWidgetErrorOpening,
style: TextStyle(color: Colors.red),
);
}

View File

@@ -1,5 +1,6 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/core/utils/functions.dart';
import 'package:pdfx/pdfx.dart';
import 'package:internet_file/internet_file.dart';
@@ -74,13 +75,13 @@ class _PdfViewerWidgetState extends State<PdfViewerWidget> {
if (_errorMessage != null) {
return Scaffold(
appBar: AppBar(leading: const CloseButton()),
body: Center(child: Text("Errore: $_errorMessage")),
body: Center(child: Text(context.l10n.commonError(_errorMessage!))),
);
}
return Scaffold(
appBar: AppBar(
title: const Text("Anteprima PDF"),
title: Text(context.l10n.pdfViewerAnteprimaPdf),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:qr_flutter/qr_flutter.dart';
class QrUploadDialog extends StatelessWidget {
@@ -84,7 +85,7 @@ class QrUploadDialog extends StatelessWidget {
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("CHIUDI"),
child: Text(context.l10n.commonClose),
),
],
actionsAlignment: MainAxisAlignment.center,

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/core/widgets/flux_text_field.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
@@ -25,9 +26,7 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
final newPassword = _passwordCtrl.text.trim();
if (newPassword.length < 6) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("La password deve avere almeno 6 caratteri"),
),
SnackBar(content: Text(context.l10n.setPasswordScreenAtLeast6Chars)),
);
return;
}
@@ -43,23 +42,23 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
// 2. Finito! Lo mandiamo alla home o facciamo ricaricare la sessione al SessionCubit
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Password impostata! Benvenuto a bordo 🚀"),
SnackBar(
content: Text(context.l10n.setPasswordScreenPasswordSetWelcome),
),
);
context.go('/'); // Rimandiamo al router principale
}
} on AuthException catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Errore Auth: ${e.message}")));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.authError(e.message))),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Errore: $e")));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.commonError(e.toString()))),
);
}
} finally {
if (mounted) setState(() => _isLoading = false);
@@ -70,7 +69,7 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Benvenuto in FLUX!"),
title: Text(context.l10n.setPasswordScreenWelcomeInFlux),
automaticallyImplyLeading:
false, // Non può tornare indietro, deve mettere la password!
),
@@ -82,21 +81,21 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
children: [
const Icon(Icons.lock_reset, size: 80, color: Colors.blueAccent),
const SizedBox(height: 24),
const Text(
"Imposta la tua Password",
Text(
context.l10n.setPasswordScreenSetPassword,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
"Hai accettato l'invito. Scegli una password sicura per accedere in futuro.",
Text(
context.l10n.setPasswordInviteAcceptedChoosePassword,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 32),
FluxTextField(
controller: _passwordCtrl,
label: "Nuova Password",
label: context.l10n.commonNewPassword,
icon: Icons.lock,
isPassword: true,
),
@@ -108,8 +107,8 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
),
child: _isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"SALVA E INIZIA",
: Text(
context.l10n.setPasswordScreenSaveAndStart,
style: TextStyle(fontSize: 16),
),
),