This commit is contained in:
2026-05-13 12:41:07 +02:00
parent 216fd85888
commit efb82b0d4a
15 changed files with 657 additions and 50 deletions

View File

@@ -164,4 +164,8 @@ class SessionCubit extends Cubit<SessionState> {
void setIsMobileDevice(bool isMobile) {
emit(state.copyWith(isMobileDevice: isMobile));
}
void setIsSingleUserMode(bool isSingleUser) {
emit(state.copyWith(isSingleUserMode: isSingleUser));
}
}

View File

@@ -25,6 +25,7 @@ class SessionState extends Equatable {
final StaffMemberModel? currentStaffMember;
final OnboardingStep onboardingStep;
final bool isMobileDevice;
final bool isSingleUserMode;
const SessionState({
this.status = SessionStatus.initial,
@@ -34,6 +35,7 @@ class SessionState extends Equatable {
this.currentStaffMember,
this.onboardingStep = OnboardingStep.none,
this.isMobileDevice = false,
this.isSingleUserMode = false,
});
/// Metodo per creare una copia dello stato modificando solo i campi necessari
@@ -45,6 +47,7 @@ class SessionState extends Equatable {
StaffMemberModel? currentStaffMember,
OnboardingStep? onboardingStep,
bool? isMobileDevice,
bool? isSingleUserMode,
}) {
return SessionState(
status: status ?? this.status,
@@ -54,6 +57,7 @@ class SessionState extends Equatable {
currentStaffMember: currentStaffMember ?? this.currentStaffMember,
onboardingStep: onboardingStep ?? this.onboardingStep,
isMobileDevice: isMobileDevice ?? this.isMobileDevice,
isSingleUserMode: isSingleUserMode ?? this.isSingleUserMode,
);
}
@@ -66,6 +70,7 @@ class SessionState extends Equatable {
currentStaffMember,
onboardingStep,
isMobileDevice,
isSingleUserMode,
];
// Helper rapidi per la UI

View File

@@ -23,6 +23,7 @@ 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/models/staff_member_model.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';
@@ -33,14 +34,13 @@ import 'package:flux/features/operations/blocs/operation_list_cubit.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_list_screen.dart';
import 'package:flux/features/settings/settings_view.dart';
import 'package:flux/features/settings/settings_screen.dart';
import 'package:flux/features/settings/theme_settings_view.dart';
import 'package:flux/features/tickets/blocs/ticket_form_cubit.dart';
import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart';
import 'package:flux/features/tickets/models/ticket_model.dart';
import 'package:flux/features/tickets/ui/ticket_form_screen.dart';
import 'package:flux/features/tickets/ui/ticket_list_screen.dart';
import 'package:flux/features/tracking/blocs/tracking_cubit.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
@@ -171,7 +171,7 @@ class AppRouter {
GoRoute(
path: '/settings',
name: Routes.settings,
builder: (context, state) => const SettingsView(),
builder: (context, state) => const SettingsScreen(),
routes: [
GoRoute(
path: 'themeSettings',
@@ -213,15 +213,12 @@ class AppRouter {
builder: (context, state) {
// 1. Leggiamo l'ID dall'URL
final String pathId = state.pathParameters['id'] ?? 'new';
final record =
state.extra
as ({StaffMemberModel createdBy, TicketModel ticket});
// 2. Leggiamo l'oggetto dalla RAM (se arriviamo da un tap interno all'app)
final TicketModel? ticketFromExtra = state.extra as TicketModel?;
// 3. Capiamo se è un nuovo ticket o una modifica
final String? realTicketId = pathId == 'new' ? null : pathId;
context.read<StaffCubit>().loadStaffForStore(
GetIt.I.get<SessionCubit>().state.currentStore!.id!,
);
context.read<CustomersCubit>().loadCustomers();
context.read<ProductsCubit>().loadModels();
context.read<ProductsCubit>().loadBrands();
@@ -235,19 +232,11 @@ class AppRouter {
),
),
BlocProvider(create: (context) => TicketFormCubit()),
BlocProvider(
create: (context) => TrackingCubit(
repo: repo,
parentId: parentId,
parentType: parentType,
companyId: companyId,
),
),
],
child: TicketFormScreen(
ticketId: realTicketId,
existingTicket: ticketFromExtra,
existingTicket: record.ticket,
),
);
},
@@ -277,6 +266,7 @@ class AppRouter {
name: Routes.operationForm,
builder: (context, state) {
final String pathId = state.pathParameters['id'] ?? 'new';
final OperationModel? operationFromExtra =
state.extra as OperationModel?;
final String? realOperationId = pathId == 'new' ? null : pathId;
@@ -301,7 +291,12 @@ class AppRouter {
parentType: AttachmentParentType.operation,
),
),
BlocProvider(create: (context) => OperationFormCubit()),
BlocProvider(
create: (context) => OperationFormCubit(
createdById: createdById,
createdByName: createdByName,
),
),
],
child: OperationFormScreen(
operationId: realOperationId,

View File

@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
// Importa il tuo StaffModel
/// Funzione helper globale per lanciare la modale ovunque ti trovi con 1 riga di codice
Future<dynamic> showStaffSelectorModal(BuildContext context) async {
return showModalBottomSheet(
context: context,
isScrollControlled:
true, // Permette alla modale di essere più alta se serve
backgroundColor: Colors.transparent,
builder: (context) => const StaffSelectorModal(),
);
}
class StaffSelectorModal extends StatelessWidget {
const StaffSelectorModal({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
padding: const EdgeInsets.all(24),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min, // Occupa solo lo spazio necessario
children: [
// --- Maniglietta superiore (UX standard dei BottomSheet) ---
Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 24),
decoration: BoxDecoration(
color: theme.dividerColor,
borderRadius: BorderRadius.circular(2),
),
),
// --- Titolo ---
const Text(
'Chi sei?',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'Seleziona il tuo profilo per continuare',
style: TextStyle(color: Colors.grey.shade600),
),
const SizedBox(height: 32),
BlocBuilder<StaffCubit, StaffState>(
builder: (context, state) {
if (state.status == StaffStatus.loading) {
return const CircularProgressIndicator();
}
final staffList = state.storeStaff;
return _buildStaffGrid(context, staffList);
},
),
const SizedBox(height: 16),
// --- Tasto Annulla ---
TextButton(
onPressed: () => Navigator.of(context).pop(), // Restituisce null
child: const Text('Annulla'),
),
],
),
),
);
}
Widget _buildStaffGrid(BuildContext context, List<dynamic> staffList) {
return Wrap(
spacing: 16,
runSpacing: 16,
alignment: WrapAlignment.center,
children: staffList.map((staff) {
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
// Quando l'utente tappa il suo nome, la modale si chiude
// e restituisce il modello (o l'ID) alla schermata precedente!
Navigator.of(context).pop(staff);
},
child: Container(
width: 100, // Pulsanti larghi e comodi
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.surfaceContainerHighest.withOpacity(0.3),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Theme.of(context).dividerColor),
),
child: Column(
children: [
CircleAvatar(
radius: 30,
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
child: Text(
staff['name'].substring(0, 1).toUpperCase(),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 12),
Text(
staff['name'],
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}).toList(),
);
}
}
Future<StaffMemberModel?> getStaffMember(BuildContext context) async {
final sessionState = context.read<SessionCubit>().state;
if (sessionState.isSingleUserMode) {
// Dispositivo personale: non rompiamo le palle. Usiamo l'utente loggato.
return sessionState.currentStaffMember;
} else {
// Dispositivo Condiviso (Kiosk Mode): Chiediamo chi è!
return await showStaffSelectorModal(context);
}
}