rework-onboarding #7
@@ -51,7 +51,7 @@ class SessionCubit extends Cubit<SessionState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Controllo Negozi
|
// 2. Controllo Negozi
|
||||||
final stores = await _repository.getStoresByCompanyId(company.id);
|
final stores = await _repository.getStoresByCompanyId(company.id!);
|
||||||
if (stores.isEmpty) {
|
if (stores.isEmpty) {
|
||||||
return emit(
|
return emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flux/features/auth/ui/auth_screen.dart';
|
||||||
import 'package:flux/features/customers/models/customer_model.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_detail_screen.dart';
|
||||||
|
import 'package:flux/features/home/ui/home_screen.dart';
|
||||||
import 'package:flux/features/master_data/products/ui/products_screen.dart';
|
import 'package:flux/features/master_data/products/ui/products_screen.dart';
|
||||||
|
import 'package:flux/features/onboarding/ui/onboarding_screen.dart';
|
||||||
import 'package:flux/features/services/models/service_model.dart';
|
import 'package:flux/features/services/models/service_model.dart';
|
||||||
import 'package:flux/features/services/ui/service_form_screen/service_form_screen.dart';
|
import 'package:flux/features/services/ui/service_form_screen/service_form_screen.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -58,7 +61,8 @@ class AppRouter {
|
|||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/login',
|
path: '/login',
|
||||||
builder: (context, state) => const LoginScreen(),
|
//builder: (context, state) => const LoginScreen(),
|
||||||
|
builder: (context, state) => const AuthScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/onboarding',
|
path: '/onboarding',
|
||||||
@@ -69,7 +73,7 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: (context, state) => const DashboardScreen(), // La tua home
|
builder: (context, state) => const HomeScreen(), // La tua home
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/customer/:id',
|
path: '/customer/:id',
|
||||||
|
|||||||
6
lib/core/utils/validators.dart
Normal file
6
lib/core/utils/validators.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
String? notEmptyValidator(String? value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Campo obbligatorio';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -132,6 +132,22 @@ class CompanyModel extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory CompanyModel.empty() {
|
||||||
|
return const CompanyModel(
|
||||||
|
id: null,
|
||||||
|
createdAt: null,
|
||||||
|
userId: '',
|
||||||
|
ragioneSociale: '',
|
||||||
|
indirizzo: '',
|
||||||
|
cap: '',
|
||||||
|
citta: '',
|
||||||
|
provincia: '',
|
||||||
|
partitaIva: '',
|
||||||
|
codiceFiscale: '',
|
||||||
|
codiceUnivoco: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
factory CompanyModel.fromMap(Map<String, dynamic> map) {
|
factory CompanyModel.fromMap(Map<String, dynamic> map) {
|
||||||
return CompanyModel(
|
return CompanyModel(
|
||||||
id: map['id'] as String?,
|
id: map['id'] as String?,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class _CreateCompanyScreenState extends State<CreateCompanyScreen> {
|
|||||||
void _onSave() {
|
void _onSave() {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
// Recuperiamo l'ID utente attuale da Supabase o dal SessionBloc
|
// Recuperiamo l'ID utente attuale da Supabase o dal SessionBloc
|
||||||
final userId = context.read<SessionBloc>().state.userId!;
|
final userId = context.read<SessionCubit>().state.user!.id;
|
||||||
|
|
||||||
final company = CompanyModel(
|
final company = CompanyModel(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@@ -77,7 +77,7 @@ class _CreateCompanyScreenState extends State<CreateCompanyScreen> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Qui chiami il tuo Bloc dell'autenticazione per fare logout
|
// Qui chiami il tuo Bloc dell'autenticazione per fare logout
|
||||||
// Esempio se hai un AuthBloc o SessionBloc:
|
// Esempio se hai un AuthBloc o SessionBloc:
|
||||||
context.read<AuthBloc>().add(LogoutRequested());
|
//context.read<AuthBloc>().add(LogoutRequested());
|
||||||
|
|
||||||
// Se vuoi solo tornare brutalmente alla login per testare il logo:
|
// Se vuoi solo tornare brutalmente alla login per testare il logo:
|
||||||
// Navigator.of(context).pushReplacementNamed('/login');
|
// Navigator.of(context).pushReplacementNamed('/login');
|
||||||
@@ -92,7 +92,7 @@ class _CreateCompanyScreenState extends State<CreateCompanyScreen> {
|
|||||||
//GetIt.I.get<AppSettings>().setCurrentCompany(state.company);
|
//GetIt.I.get<AppSettings>().setCurrentCompany(state.company);
|
||||||
|
|
||||||
// 2. Notifichiamo il SessionBloc per cambiare pagina
|
// 2. Notifichiamo il SessionBloc per cambiare pagina
|
||||||
context.read<SessionBloc>().add(AppStarted());
|
//context.read<SessionCubit>().add(AppStarted());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.status == CompanyStatus.failure) {
|
if (state.status == CompanyStatus.failure) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ part 'customer_state.dart';
|
|||||||
|
|
||||||
class CustomerCubit extends Cubit<CustomerState> {
|
class CustomerCubit extends Cubit<CustomerState> {
|
||||||
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
|
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
|
||||||
final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
// Variabile per gestire il debounce della ricerca
|
// Variabile per gestire il debounce della ricerca
|
||||||
Timer? _searchDebounce;
|
Timer? _searchDebounce;
|
||||||
@@ -22,7 +22,7 @@ class CustomerCubit extends Cubit<CustomerState> {
|
|||||||
emit(state.copyWith(status: CustomerStatus.loading));
|
emit(state.copyWith(status: CustomerStatus.loading));
|
||||||
try {
|
try {
|
||||||
final customers = await _repository.getCustomers(
|
final customers = await _repository.getCustomers(
|
||||||
_sessionBloc.state.company!.id,
|
_sessionCubit.state.company!.id!,
|
||||||
);
|
);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(status: CustomerStatus.success, customers: customers),
|
state.copyWith(status: CustomerStatus.success, customers: customers),
|
||||||
@@ -111,7 +111,7 @@ class CustomerCubit extends Cubit<CustomerState> {
|
|||||||
// Nessun "loading" state qui, per evitare sfarfallii visivi mentre si scrive
|
// Nessun "loading" state qui, per evitare sfarfallii visivi mentre si scrive
|
||||||
try {
|
try {
|
||||||
final results = await _repository.searchCustomers(
|
final results = await _repository.searchCustomers(
|
||||||
_sessionBloc.state.company!.id,
|
_sessionCubit.state.company!.id!,
|
||||||
query,
|
query,
|
||||||
);
|
);
|
||||||
emit(
|
emit(
|
||||||
@@ -137,7 +137,7 @@ class CustomerCubit extends Cubit<CustomerState> {
|
|||||||
nome: name,
|
nome: name,
|
||||||
telefono: phone ?? '',
|
telefono: phone ?? '',
|
||||||
email: email ?? '',
|
email: email ?? '',
|
||||||
companyId: _sessionBloc.state.company!.id,
|
companyId: _sessionCubit.state.company!.id!,
|
||||||
note: '',
|
note: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import '../models/customer_model.dart';
|
|||||||
|
|
||||||
class CustomerRepository {
|
class CustomerRepository {
|
||||||
final SupabaseClient _supabase = GetIt.I<SupabaseClient>();
|
final SupabaseClient _supabase = GetIt.I<SupabaseClient>();
|
||||||
final String companyId = GetIt.I.get<SessionBloc>().state.company!.id;
|
final String companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
|
||||||
|
|
||||||
// Crea un nuovo cliente
|
// Crea un nuovo cliente
|
||||||
Future<CustomerModel> saveCustomer(CustomerModel customer) async {
|
Future<CustomerModel> saveCustomer(CustomerModel customer) async {
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ class _CustomersContentState extends State<CustomersContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _loadInitialCustomers() {
|
void _loadInitialCustomers() {
|
||||||
final companyId = context.read<SessionBloc>().state.company?.id;
|
final companyId = context.read<SessionCubit>().state.company?.id;
|
||||||
if (companyId != null) {
|
if (companyId != null) {
|
||||||
context.read<CustomerCubit>().loadCustomers();
|
context.read<CustomerCubit>().loadCustomers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSearch(String query) {
|
void _onSearch(String query) {
|
||||||
final companyId = context.read<SessionBloc>().state.company?.id;
|
final companyId = context.read<SessionCubit>().state.company?.id;
|
||||||
if (companyId != null) {
|
if (companyId != null) {
|
||||||
context.read<CustomerCubit>().searchCustomers(query);
|
context.read<CustomerCubit>().searchCustomers(query);
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ class _CustomersContentState extends State<CustomersContent> {
|
|||||||
child: CustomerForm(
|
child: CustomerForm(
|
||||||
customer: customer,
|
customer: customer,
|
||||||
onSave: (customerFromForm) {
|
onSave: (customerFromForm) {
|
||||||
final session = context.read<SessionBloc>().state;
|
final session = context.read<SessionCubit>().state;
|
||||||
final companyId = session.company?.id;
|
final companyId = session.company?.id;
|
||||||
|
|
||||||
if (companyId == null) return;
|
if (companyId == null) return;
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class DashboardContent extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SessionBloc, SessionState>(
|
return BlocBuilder<SessionCubit, SessionState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final store = state.selectedStore;
|
final store = state.currentStore;
|
||||||
final company = state.company;
|
final company = state.company;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SessionBloc, SessionState>(
|
return BlocBuilder<SessionCubit, SessionState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
@@ -203,7 +203,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(
|
Text(
|
||||||
GetIt.I.get<SessionBloc>().state.company?.ragioneSociale ??
|
GetIt.I.get<SessionCubit>().state.company?.ragioneSociale ??
|
||||||
"Utente",
|
"Utente",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -246,9 +246,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(dialogContext); // Chiude la Dialog
|
Navigator.pop(dialogContext); // Chiude la Dialog
|
||||||
context.read<AuthBloc>().add(
|
/* context.read<AuthBloc>().add(
|
||||||
LogoutRequested(),
|
LogoutRequested(),
|
||||||
); // Esegue il logout
|
); // Esegue il logout */
|
||||||
},
|
},
|
||||||
child: const Text("Esci"),
|
child: const Text("Esci"),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ part 'product_state.dart';
|
|||||||
|
|
||||||
class ProductCubit extends Cubit<ProductState> {
|
class ProductCubit extends Cubit<ProductState> {
|
||||||
final ProductRepository _repository = GetIt.I<ProductRepository>();
|
final ProductRepository _repository = GetIt.I<ProductRepository>();
|
||||||
final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
ProductCubit() : super(const ProductState());
|
ProductCubit() : super(const ProductState());
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class ProductCubit extends Cubit<ProductState> {
|
|||||||
emit(state.copyWith(status: ProductStatus.loading));
|
emit(state.copyWith(status: ProductStatus.loading));
|
||||||
try {
|
try {
|
||||||
final brands = await _repository.getBrands(
|
final brands = await _repository.getBrands(
|
||||||
_sessionBloc.state.company!.id,
|
_sessionCubit.state.company!.id!,
|
||||||
);
|
);
|
||||||
emit(state.copyWith(status: ProductStatus.success, brands: brands));
|
emit(state.copyWith(status: ProductStatus.success, brands: brands));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -54,7 +54,7 @@ class ProductCubit extends Cubit<ProductState> {
|
|||||||
final brand = BrandModel(
|
final brand = BrandModel(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
companyId: _sessionBloc.state.company!.id,
|
companyId: _sessionCubit.state.company!.id!,
|
||||||
);
|
);
|
||||||
final newBrand = await _repository.upsertBrand(brand);
|
final newBrand = await _repository.upsertBrand(brand);
|
||||||
await loadBrands(); // Ricarichiamo la lista aggiornata
|
await loadBrands(); // Ricarichiamo la lista aggiornata
|
||||||
@@ -137,7 +137,10 @@ class ProductCubit extends Cubit<ProductState> {
|
|||||||
// 1. Cerchiamo o creiamo il Brand
|
// 1. Cerchiamo o creiamo il Brand
|
||||||
// (Usa una funzione upsert o una ricerca rapida nel repository)
|
// (Usa una funzione upsert o una ricerca rapida nel repository)
|
||||||
brand ??= await _repository.upsertBrand(
|
brand ??= await _repository.upsertBrand(
|
||||||
BrandModel(name: brandName, companyId: _sessionBloc.state.company!.id),
|
BrandModel(
|
||||||
|
name: brandName,
|
||||||
|
companyId: _sessionCubit.state.company!.id!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Creiamo il Modello legato al Brand
|
// 2. Creiamo il Modello legato al Brand
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ProvidersState extends Equatable {
|
|||||||
|
|
||||||
class ProvidersCubit extends Cubit<ProvidersState> {
|
class ProvidersCubit extends Cubit<ProvidersState> {
|
||||||
final ProviderRepository _repository = GetIt.I<ProviderRepository>();
|
final ProviderRepository _repository = GetIt.I<ProviderRepository>();
|
||||||
final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
ProvidersCubit() : super(const ProvidersState());
|
ProvidersCubit() : super(const ProvidersState());
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ class ProvidersCubit extends Cubit<ProvidersState> {
|
|||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
try {
|
try {
|
||||||
final all = await _repository.fetchAllCompanyProviders(
|
final all = await _repository.fetchAllCompanyProviders(
|
||||||
_sessionBloc.state.company!.id,
|
_sessionCubit.state.company!.id!,
|
||||||
);
|
);
|
||||||
List<String> associated = [];
|
List<String> associated = [];
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ class ProvidersCubit extends Cubit<ProvidersState> {
|
|||||||
) async {
|
) async {
|
||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
// Assicuriamoci di settare la companyId prima di salvare
|
// Assicuriamoci di settare la companyId prima di salvare
|
||||||
provider = provider.copyWith(companyId: _sessionBloc.state.company!.id);
|
provider = provider.copyWith(companyId: _sessionCubit.state.company!.id);
|
||||||
try {
|
try {
|
||||||
// 1. Salviamo l'anagrafica (upsert)
|
// 1. Salviamo l'anagrafica (upsert)
|
||||||
// Se è un nuovo provider, l'ID potrebbe essere generato qui dal DB
|
// Se è un nuovo provider, l'ID potrebbe essere generato qui dal DB
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ part 'staff_state.dart';
|
|||||||
|
|
||||||
class StaffCubit extends Cubit<StaffState> {
|
class StaffCubit extends Cubit<StaffState> {
|
||||||
final StaffRepository _repository = GetIt.I.get<StaffRepository>();
|
final StaffRepository _repository = GetIt.I.get<StaffRepository>();
|
||||||
final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
StaffCubit() : super(const StaffState());
|
StaffCubit() : super(const StaffState());
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ class StaffCubit extends Cubit<StaffState> {
|
|||||||
emit(state.copyWith(isLoading: true, error: null));
|
emit(state.copyWith(isLoading: true, error: null));
|
||||||
try {
|
try {
|
||||||
final staff = await _repository.getStaffMembers(
|
final staff = await _repository.getStaffMembers(
|
||||||
_sessionBloc.state.company!.id,
|
_sessionCubit.state.company!.id!,
|
||||||
);
|
);
|
||||||
final Map<String, List<StoreModel>> storesByStaff = {};
|
final Map<String, List<StoreModel>> storesByStaff = {};
|
||||||
for (StaffMemberModel member in staff) {
|
for (StaffMemberModel member in staff) {
|
||||||
|
|||||||
@@ -18,44 +18,61 @@ enum SystemRole {
|
|||||||
class StaffMemberModel extends Equatable {
|
class StaffMemberModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final String companyId;
|
final String companyId;
|
||||||
final String storeId;
|
|
||||||
final String userId;
|
final String userId;
|
||||||
final String name;
|
final String name;
|
||||||
final String surname;
|
final String? email;
|
||||||
final String?
|
final String? phoneNumber;
|
||||||
jobTitle; // Testo libero! Il cliente ci scrive quello che vuole.
|
final String? jobTitle;
|
||||||
final SystemRole systemRole; // ENUM! Il sistema non si frega.
|
final SystemRole systemRole;
|
||||||
|
final bool isActive;
|
||||||
|
|
||||||
const StaffMemberModel({
|
const StaffMemberModel({
|
||||||
this.id,
|
this.id,
|
||||||
required this.companyId,
|
required this.companyId,
|
||||||
required this.storeId,
|
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.surname,
|
this.email,
|
||||||
|
this.phoneNumber,
|
||||||
this.jobTitle,
|
this.jobTitle,
|
||||||
this.systemRole = SystemRole.user, // Sicurezza di default
|
this.systemRole = SystemRole.user,
|
||||||
|
this.isActive = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
StaffMemberModel copyWith({
|
StaffMemberModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? companyId,
|
String? companyId,
|
||||||
String? storeId,
|
|
||||||
String? userId,
|
String? userId,
|
||||||
String? name,
|
String? name,
|
||||||
String? surname,
|
String? surname,
|
||||||
|
String? email,
|
||||||
|
String? phoneNumber,
|
||||||
String? jobTitle,
|
String? jobTitle,
|
||||||
SystemRole? systemRole,
|
SystemRole? systemRole,
|
||||||
|
bool? isActive,
|
||||||
}) {
|
}) {
|
||||||
return StaffMemberModel(
|
return StaffMemberModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
companyId: companyId ?? this.companyId,
|
companyId: companyId ?? this.companyId,
|
||||||
storeId: storeId ?? this.storeId,
|
|
||||||
userId: userId ?? this.userId,
|
userId: userId ?? this.userId,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
surname: surname ?? this.surname,
|
email: email ?? this.email,
|
||||||
|
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||||
jobTitle: jobTitle ?? this.jobTitle,
|
jobTitle: jobTitle ?? this.jobTitle,
|
||||||
systemRole: systemRole ?? this.systemRole,
|
systemRole: systemRole ?? this.systemRole,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory StaffMemberModel.empty() {
|
||||||
|
return const StaffMemberModel(
|
||||||
|
companyId: '',
|
||||||
|
userId: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phoneNumber: '',
|
||||||
|
jobTitle: '',
|
||||||
|
systemRole: SystemRole.user,
|
||||||
|
isActive: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,14 +80,13 @@ class StaffMemberModel extends Equatable {
|
|||||||
return StaffMemberModel(
|
return StaffMemberModel(
|
||||||
id: map['id'] as String?,
|
id: map['id'] as String?,
|
||||||
companyId: map['company_id'] ?? '',
|
companyId: map['company_id'] ?? '',
|
||||||
storeId: map['store_id'] ?? '',
|
|
||||||
userId: map['user_id'] ?? '',
|
userId: map['user_id'] ?? '',
|
||||||
name: map['name'] ?? '',
|
name: map['name'] ?? '',
|
||||||
surname: map['surname'] ?? '',
|
email: map['email'] as String?,
|
||||||
jobTitle: map['job_title'] as String?, // Semplice stringa
|
phoneNumber: map['phone_number'] as String?,
|
||||||
systemRole: SystemRole.fromString(
|
jobTitle: map['job_title'] as String?,
|
||||||
map['system_role'],
|
systemRole: SystemRole.fromString(map['system_role']),
|
||||||
), // Lettura tipizzata
|
isActive: map['is_active'] ?? true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,12 +94,13 @@ class StaffMemberModel extends Equatable {
|
|||||||
return {
|
return {
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
'company_id': companyId,
|
'company_id': companyId,
|
||||||
'store_id': storeId,
|
|
||||||
'user_id': userId,
|
'user_id': userId,
|
||||||
'name': name,
|
'name': name,
|
||||||
'surname': surname,
|
if (email != null) 'email': email,
|
||||||
|
if (phoneNumber != null) 'phone_number': phoneNumber,
|
||||||
if (jobTitle != null) 'job_title': jobTitle,
|
if (jobTitle != null) 'job_title': jobTitle,
|
||||||
'system_role': systemRole.name, // Trasforma SystemRole.admin in 'admin'
|
'system_role': systemRole.name, // Trasforma SystemRole.admin in 'admin'
|
||||||
|
'is_active': isActive,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,11 +108,12 @@ class StaffMemberModel extends Equatable {
|
|||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
id,
|
id,
|
||||||
companyId,
|
companyId,
|
||||||
storeId,
|
|
||||||
userId,
|
userId,
|
||||||
name,
|
name,
|
||||||
surname,
|
email,
|
||||||
|
phoneNumber,
|
||||||
jobTitle,
|
jobTitle,
|
||||||
systemRole,
|
systemRole,
|
||||||
|
isActive,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,8 +135,13 @@ class _StaffScreenState extends State<StaffScreen> {
|
|||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (member.email.isNotEmpty) Text(member.email),
|
if (member.email != null && member.email!.isNotEmpty)
|
||||||
Text(member.phone.isNotEmpty ? member.phone : "Nessun telefono"),
|
Text(member.email!),
|
||||||
|
Text(
|
||||||
|
member.phoneNumber != null && member.phoneNumber!.isNotEmpty
|
||||||
|
? member.phoneNumber!
|
||||||
|
: "Nessun telefono",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: const Icon(Icons.edit_note),
|
trailing: const Icon(Icons.edit_note),
|
||||||
@@ -148,7 +153,7 @@ class _StaffScreenState extends State<StaffScreen> {
|
|||||||
void _openStaffForm(BuildContext context, {StaffMemberModel? member}) {
|
void _openStaffForm(BuildContext context, {StaffMemberModel? member}) {
|
||||||
final nameController = TextEditingController(text: member?.name);
|
final nameController = TextEditingController(text: member?.name);
|
||||||
final emailController = TextEditingController(text: member?.email);
|
final emailController = TextEditingController(text: member?.email);
|
||||||
final phoneController = TextEditingController(text: member?.phone);
|
final phoneController = TextEditingController(text: member?.phoneNumber);
|
||||||
|
|
||||||
// 1. Inizializziamo la lista temporanea attingendo dallo stato del Cubit
|
// 1. Inizializziamo la lista temporanea attingendo dallo stato del Cubit
|
||||||
// Usiamo storesByStaff (la mappa che indicizza i negozi per ogni ID dipendente)
|
// Usiamo storesByStaff (la mappa che indicizza i negozi per ogni ID dipendente)
|
||||||
@@ -264,16 +269,16 @@ class _StaffScreenState extends State<StaffScreen> {
|
|||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final companyId = context
|
final companyId = context
|
||||||
.read<SessionBloc>()
|
.read<SessionCubit>()
|
||||||
.state
|
.state
|
||||||
.company!
|
.company!
|
||||||
.id;
|
.id!;
|
||||||
|
//TODO sistemare StaffScreen per il nuovo modello
|
||||||
final updatedMember = StaffMemberModel(
|
/* final updatedMember = StaffMemberModel(
|
||||||
id: member?.id,
|
id: member?.id,
|
||||||
name: nameController.text,
|
name: nameController.text,
|
||||||
email: emailController.text,
|
email: emailController.text,
|
||||||
phone: phoneController.text,
|
phoneNumber: phoneController.text,
|
||||||
companyId: companyId,
|
companyId: companyId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -281,7 +286,7 @@ class _StaffScreenState extends State<StaffScreen> {
|
|||||||
context.read<StaffCubit>().saveStaffWithStores(
|
context.read<StaffCubit>().saveStaffWithStores(
|
||||||
member: updatedMember,
|
member: updatedMember,
|
||||||
selectedStoreIds: tempSelectedStores,
|
selectedStoreIds: tempSelectedStores,
|
||||||
);
|
); */
|
||||||
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ part 'store_state.dart';
|
|||||||
class StoreCubit extends Cubit<StoreState> {
|
class StoreCubit extends Cubit<StoreState> {
|
||||||
final StoreRepository _repository = GetIt.I<StoreRepository>();
|
final StoreRepository _repository = GetIt.I<StoreRepository>();
|
||||||
final StaffRepository _staffRepository = GetIt.I<StaffRepository>();
|
final StaffRepository _staffRepository = GetIt.I<StaffRepository>();
|
||||||
final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
StoreCubit() : super(const StoreState(stores: []));
|
StoreCubit() : super(const StoreState(stores: []));
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class StoreCubit extends Cubit<StoreState> {
|
|||||||
emit(state.copyWith(status: StoreStatus.loading));
|
emit(state.copyWith(status: StoreStatus.loading));
|
||||||
try {
|
try {
|
||||||
final stores = await _repository.fetchAllCompanyStores(
|
final stores = await _repository.fetchAllCompanyStores(
|
||||||
_sessionBloc.state.company!.id,
|
_sessionCubit.state.company!.id!,
|
||||||
);
|
);
|
||||||
final Map<String, List<StaffMemberModel>> staffByStore = {};
|
final Map<String, List<StaffMemberModel>> staffByStore = {};
|
||||||
for (StoreModel store in stores) {
|
for (StoreModel store in stores) {
|
||||||
|
|||||||
@@ -81,6 +81,17 @@ class StoreModel extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory StoreModel.empty() {
|
||||||
|
return const StoreModel(
|
||||||
|
nome: '',
|
||||||
|
companyId: '',
|
||||||
|
indirizzo: '',
|
||||||
|
cap: '',
|
||||||
|
comune: '',
|
||||||
|
provincia: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
factory StoreModel.fromMap(Map<String, dynamic> map) {
|
factory StoreModel.fromMap(Map<String, dynamic> map) {
|
||||||
final providersPivotList = map['associated_providers'] as List?;
|
final providersPivotList = map['associated_providers'] as List?;
|
||||||
List<ProviderModel> providers = [];
|
List<ProviderModel> providers = [];
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class _CreateStoreScreenState extends State<CreateStoreScreen> {
|
|||||||
|
|
||||||
/// Funzione magica per copiare i dati dall'azienda salvata in GetIt
|
/// Funzione magica per copiare i dati dall'azienda salvata in GetIt
|
||||||
void _useCompanyAddress() {
|
void _useCompanyAddress() {
|
||||||
final company = context.read<SessionBloc>().state.company;
|
final company = context.read<SessionCubit>().state.company;
|
||||||
if (company != null) {
|
if (company != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_indirizzoController.text = company.indirizzo;
|
_indirizzoController.text = company.indirizzo;
|
||||||
@@ -58,7 +58,7 @@ class _CreateStoreScreenState extends State<CreateStoreScreen> {
|
|||||||
|
|
||||||
void _onSave() {
|
void _onSave() {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
final company = context.read<SessionBloc>().state.company;
|
final company = context.read<SessionCubit>().state.company;
|
||||||
|
|
||||||
if (company == null) {
|
if (company == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -69,7 +69,7 @@ class _CreateStoreScreenState extends State<CreateStoreScreen> {
|
|||||||
|
|
||||||
final store = StoreModel(
|
final store = StoreModel(
|
||||||
nome: _nomeController.text.trim(),
|
nome: _nomeController.text.trim(),
|
||||||
companyId: company.id,
|
companyId: company.id!,
|
||||||
indirizzo: _indirizzoController.text.trim(),
|
indirizzo: _indirizzoController.text.trim(),
|
||||||
cap: _capController.text.trim(),
|
cap: _capController.text.trim(),
|
||||||
comune: _comuneController.text.trim(),
|
comune: _comuneController.text.trim(),
|
||||||
@@ -84,10 +84,10 @@ class _CreateStoreScreenState extends State<CreateStoreScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Il tuo primo Negozio')),
|
appBar: AppBar(title: const Text('Il tuo primo Negozio')),
|
||||||
body: BlocConsumer<StoreCubit, StoreState>(
|
body: BlocBuilder<StoreCubit, StoreState>(
|
||||||
listener: (context, state) {
|
/* listener: (context, state) {
|
||||||
if (state.status == StoreStatus.success) {
|
if (state.status == StoreStatus.success) {
|
||||||
context.read<SessionBloc>().add(AppStarted());
|
context.read<SessionCubit>().;
|
||||||
}
|
}
|
||||||
if (state.status == StoreStatus.failure) {
|
if (state.status == StoreStatus.failure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -96,7 +96,7 @@ class _CreateStoreScreenState extends State<CreateStoreScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}, */
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
|||||||
@@ -130,10 +130,10 @@ class _StoreFormState extends State<StoreForm> {
|
|||||||
comune: comuneController.text,
|
comune: comuneController.text,
|
||||||
provincia: provinciaController.text,
|
provincia: provinciaController.text,
|
||||||
companyId: context
|
companyId: context
|
||||||
.read<SessionBloc>()
|
.read<SessionCubit>()
|
||||||
.state
|
.state
|
||||||
.company!
|
.company!
|
||||||
.id, // Recuperiamo la companyId
|
.id!, // Recuperiamo la companyId
|
||||||
isActive: widget.store?.isActive ?? true,
|
isActive: widget.store?.isActive ?? true,
|
||||||
isPaid: widget.store?.isPaid ?? false,
|
isPaid: widget.store?.isPaid ?? false,
|
||||||
paymentExpiration: widget.store?.paymentExpiration,
|
paymentExpiration: widget.store?.paymentExpiration,
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ class OnboardingCubit extends Cubit<OnboardingState> {
|
|||||||
// PARANOIA MODE: Forziamo i legami e il ruolo di sistema 'admin'
|
// PARANOIA MODE: Forziamo i legami e il ruolo di sistema 'admin'
|
||||||
final staffToSave = staff.copyWith(
|
final staffToSave = staff.copyWith(
|
||||||
companyId: state.companyId!,
|
companyId: state.companyId!,
|
||||||
storeId: state.storeId!,
|
|
||||||
userId: _sessionCubit.state.user!.id, // Dall'utente loggato in Supabase
|
userId: _sessionCubit.state.user!.id, // Dall'utente loggato in Supabase
|
||||||
systemRole: SystemRole.admin, // Blindato!
|
systemRole: SystemRole.admin, // Blindato!
|
||||||
);
|
);
|
||||||
|
|||||||
83
lib/features/onboarding/ui/company_onboarding_form.dart
Normal file
83
lib/features/onboarding/ui/company_onboarding_form.dart
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flux/core/utils/validators.dart';
|
||||||
|
import 'package:flux/core/widgets/flux_text_field.dart';
|
||||||
|
import 'package:flux/features/company/models/company_model.dart';
|
||||||
|
import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart';
|
||||||
|
import 'package:flux/features/onboarding/blocs/onboarding_state.dart';
|
||||||
|
|
||||||
|
class CompanyOnboardingForm extends StatefulWidget {
|
||||||
|
final OnboardingState state;
|
||||||
|
const CompanyOnboardingForm({super.key, required this.state});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CompanyOnboardingForm> createState() => _CompanyOnboardingFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CompanyOnboardingFormState extends State<CompanyOnboardingForm> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final _nameCtrl = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameCtrl.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"Iniziamo! 🏢",
|
||||||
|
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
"Inserisci i dati della tua attività per configurare il tuo ambiente FLUX.",
|
||||||
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 48),
|
||||||
|
|
||||||
|
FluxTextField(
|
||||||
|
label: 'Ragione Sociale / Nome Azienda',
|
||||||
|
controller: _nameCtrl,
|
||||||
|
validator: notEmptyValidator,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
const Spacer(),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
final newCompany = CompanyModel.empty().copyWith(
|
||||||
|
ragioneSociale: _nameCtrl.text.trim(),
|
||||||
|
);
|
||||||
|
context.read<OnboardingCubit>().saveCompany(newCompany);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"Salva e Prosegui",
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
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/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
|
import 'package:flux/core/utils/validators.dart';
|
||||||
import 'package:flux/features/company/models/company_model.dart';
|
import 'package:flux/features/company/models/company_model.dart';
|
||||||
import 'package:flux/features/master_data/store/models/store_model.dart';
|
import 'package:flux/features/master_data/store/models/store_model.dart';
|
||||||
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||||
@@ -9,6 +10,7 @@ import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart';
|
|||||||
// Sostituisci con il percorso corretto della tua FluxTextField
|
// Sostituisci con il percorso corretto della tua FluxTextField
|
||||||
import 'package:flux/core/widgets/flux_text_field.dart';
|
import 'package:flux/core/widgets/flux_text_field.dart';
|
||||||
import 'package:flux/features/onboarding/blocs/onboarding_state.dart';
|
import 'package:flux/features/onboarding/blocs/onboarding_state.dart';
|
||||||
|
import 'package:flux/features/onboarding/ui/company_onboarding_form.dart';
|
||||||
|
|
||||||
class OnboardingScreen extends StatefulWidget {
|
class OnboardingScreen extends StatefulWidget {
|
||||||
const OnboardingScreen({super.key});
|
const OnboardingScreen({super.key});
|
||||||
@@ -21,14 +23,10 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
late PageController _pageController;
|
late PageController _pageController;
|
||||||
|
|
||||||
// --- CHIAVI DEI FORM (Per la validazione indipendente di ogni step) ---
|
// --- CHIAVI DEI FORM (Per la validazione indipendente di ogni step) ---
|
||||||
final _companyFormKey = GlobalKey<FormState>();
|
|
||||||
final _storeFormKey = GlobalKey<FormState>();
|
final _storeFormKey = GlobalKey<FormState>();
|
||||||
final _staffFormKey = GlobalKey<FormState>();
|
final _staffFormKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
// --- CONTROLLERS: STEP 1 (Company) ---
|
|
||||||
final _companyNameCtrl = TextEditingController();
|
|
||||||
final _companyVatCtrl = TextEditingController();
|
|
||||||
|
|
||||||
// --- CONTROLLERS: STEP 2 (Store) ---
|
// --- CONTROLLERS: STEP 2 (Store) ---
|
||||||
final _storeNameCtrl = TextEditingController();
|
final _storeNameCtrl = TextEditingController();
|
||||||
final _storeAddressCtrl = TextEditingController();
|
final _storeAddressCtrl = TextEditingController();
|
||||||
@@ -49,8 +47,6 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_pageController.dispose();
|
_pageController.dispose();
|
||||||
_companyNameCtrl.dispose();
|
|
||||||
_companyVatCtrl.dispose();
|
|
||||||
_storeNameCtrl.dispose();
|
_storeNameCtrl.dispose();
|
||||||
_storeAddressCtrl.dispose();
|
_storeAddressCtrl.dispose();
|
||||||
_staffFirstNameCtrl.dispose();
|
_staffFirstNameCtrl.dispose();
|
||||||
@@ -72,14 +68,6 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validatore generico riutilizzabile
|
|
||||||
String? _requireValidator(String? value) {
|
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return 'Campo obbligatorio';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocConsumer<OnboardingCubit, OnboardingState>(
|
return BlocConsumer<OnboardingCubit, OnboardingState>(
|
||||||
@@ -129,7 +117,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
physics:
|
physics:
|
||||||
const NeverScrollableScrollPhysics(), // Vietato lo swipe manuale!
|
const NeverScrollableScrollPhysics(), // Vietato lo swipe manuale!
|
||||||
children: [
|
children: [
|
||||||
_buildCompanyForm(context, state),
|
CompanyOnboardingForm(state: state), // Step 1: Company
|
||||||
_buildStoreForm(context, state),
|
_buildStoreForm(context, state),
|
||||||
_buildStaffForm(context, state),
|
_buildStaffForm(context, state),
|
||||||
],
|
],
|
||||||
@@ -149,73 +137,6 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// SCHERMATE DEI SINGOLI STEP
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
Widget _buildCompanyForm(BuildContext context, OnboardingState state) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(32.0),
|
|
||||||
child: Form(
|
|
||||||
key: _companyFormKey,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"Iniziamo! 🏢",
|
|
||||||
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text(
|
|
||||||
"Inserisci i dati della tua attività per configurare il tuo ambiente FLUX.",
|
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 48),
|
|
||||||
|
|
||||||
FluxTextField(
|
|
||||||
label: 'Ragione Sociale / Nome Azienda',
|
|
||||||
controller: _companyNameCtrl,
|
|
||||||
validator: _requireValidator,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FluxTextField(
|
|
||||||
label: 'Partita IVA',
|
|
||||||
controller: _companyVatCtrl,
|
|
||||||
validator: _requireValidator,
|
|
||||||
),
|
|
||||||
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
if (_companyFormKey.currentState!.validate()) {
|
|
||||||
// MOCK DI ESEMPIO: Sostituisci con il tuo vero CompanyModel
|
|
||||||
final newCompany = CompanyModel(
|
|
||||||
ownerId: '', // Questo lo gestirà o ignorerà il Cubit
|
|
||||||
name: _companyNameCtrl.text.trim(),
|
|
||||||
vatNumber: _companyVatCtrl.text.trim(),
|
|
||||||
);
|
|
||||||
context.read<OnboardingCubit>().saveCompany(newCompany);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
"Salva e Prosegui",
|
|
||||||
style: TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStoreForm(BuildContext context, OnboardingState state) {
|
Widget _buildStoreForm(BuildContext context, OnboardingState state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(32.0),
|
padding: const EdgeInsets.all(32.0),
|
||||||
@@ -239,13 +160,13 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
FluxTextField(
|
FluxTextField(
|
||||||
label: 'Nome Negozio (es. Sede Centrale)',
|
label: 'Nome Negozio (es. Sede Centrale)',
|
||||||
controller: _storeNameCtrl,
|
controller: _storeNameCtrl,
|
||||||
validator: _requireValidator,
|
validator: notEmptyValidator,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FluxTextField(
|
FluxTextField(
|
||||||
label: 'Indirizzo completo',
|
label: 'Indirizzo completo',
|
||||||
controller: _storeAddressCtrl,
|
controller: _storeAddressCtrl,
|
||||||
validator: _requireValidator,
|
validator: notEmptyValidator,
|
||||||
),
|
),
|
||||||
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
@@ -258,11 +179,9 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_storeFormKey.currentState!.validate()) {
|
if (_storeFormKey.currentState!.validate()) {
|
||||||
final newStore = StoreModel(
|
final newStore = StoreModel.empty().copyWith(
|
||||||
companyId: '', // Iniettato dal Cubit
|
nome: _storeNameCtrl.text.trim(),
|
||||||
name: _storeNameCtrl.text.trim(),
|
indirizzo: _storeAddressCtrl.text.trim(),
|
||||||
address: _storeAddressCtrl.text.trim(),
|
|
||||||
isActive: true,
|
|
||||||
);
|
);
|
||||||
context.read<OnboardingCubit>().saveStore(newStore);
|
context.read<OnboardingCubit>().saveStore(newStore);
|
||||||
}
|
}
|
||||||
@@ -302,13 +221,13 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
FluxTextField(
|
FluxTextField(
|
||||||
label: 'Nome',
|
label: 'Nome',
|
||||||
controller: _staffFirstNameCtrl,
|
controller: _staffFirstNameCtrl,
|
||||||
validator: _requireValidator,
|
validator: notEmptyValidator,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FluxTextField(
|
FluxTextField(
|
||||||
label: 'Cognome',
|
label: 'Cognome',
|
||||||
controller: _staffLastNameCtrl,
|
controller: _staffLastNameCtrl,
|
||||||
validator: _requireValidator,
|
validator: notEmptyValidator,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FluxTextField(
|
FluxTextField(
|
||||||
@@ -329,14 +248,9 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_staffFormKey.currentState!.validate()) {
|
if (_staffFormKey.currentState!.validate()) {
|
||||||
final newStaff = StaffMemberModel(
|
final newStaff = StaffMemberModel.empty().copyWith(
|
||||||
companyId: '', // Iniettato dal Cubit
|
|
||||||
storeId: '', // Iniettato dal Cubit
|
|
||||||
userId: '', // Iniettato dal Cubit
|
|
||||||
name: _staffFirstNameCtrl.text.trim(),
|
name: _staffFirstNameCtrl.text.trim(),
|
||||||
surname: _staffLastNameCtrl.text.trim(),
|
|
||||||
jobTitle: _staffJobTitleCtrl.text.trim(),
|
jobTitle: _staffJobTitleCtrl.text.trim(),
|
||||||
// systemRole non viene passato qui: la Paranoia Mode del Cubit forzerà "admin"
|
|
||||||
);
|
);
|
||||||
context.read<OnboardingCubit>().saveStaff(newStaff);
|
context.read<OnboardingCubit>().saveStaff(newStaff);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,211 +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/widgets/flux_text_field.dart';
|
|
||||||
import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart';
|
|
||||||
import 'package:flux/features/onboarding/blocs/onboarding_state.dart';
|
|
||||||
// Importa i tuoi file (cubit, modelli, ecc.)
|
|
||||||
|
|
||||||
class OnboardingScreen extends StatefulWidget {
|
|
||||||
const OnboardingScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<OnboardingScreen> createState() => _OnboardingScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _OnboardingScreenState extends State<OnboardingScreen> {
|
|
||||||
late PageController _pageController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
// Inizializziamo il controller sulla pagina giusta.
|
|
||||||
// L'indice parte da 0. company=0, store=1, staff=2.
|
|
||||||
final initialStep = context.read<OnboardingCubit>().state.step;
|
|
||||||
_pageController = PageController(initialPage: _getPageIndex(initialStep));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_pageController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
int _getPageIndex(OnboardingStep step) {
|
|
||||||
switch (step) {
|
|
||||||
case OnboardingStep.company:
|
|
||||||
return 0;
|
|
||||||
case OnboardingStep.store:
|
|
||||||
return 1;
|
|
||||||
case OnboardingStep.staff:
|
|
||||||
return 2;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocConsumer<OnboardingCubit, OnboardingState>(
|
|
||||||
// Ascoltiamo solo quando cambia lo step per animare la pagina
|
|
||||||
listenWhen: (previous, current) => previous.step != current.step,
|
|
||||||
listener: (context, state) {
|
|
||||||
if (state.step == OnboardingStep.completed) {
|
|
||||||
// Il SessionCubit prenderà il controllo e farà il redirect,
|
|
||||||
// qui potremmo mostrare un bel toast di successo.
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text("Configurazione completata! 🚀")),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final targetPage = _getPageIndex(state.step);
|
|
||||||
_pageController.animateToPage(
|
|
||||||
targetPage,
|
|
||||||
duration: const Duration(milliseconds: 500),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
builder: (context, state) {
|
|
||||||
return Scaffold(
|
|
||||||
body: SafeArea(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
// Il PageView disabilita lo scorrimento manuale con NeverScrollableScrollPhysics
|
|
||||||
PageView(
|
|
||||||
controller: _pageController,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
children: [
|
|
||||||
_buildCompanyForm(context, state),
|
|
||||||
_buildStoreForm(context, state),
|
|
||||||
_buildStaffForm(
|
|
||||||
context,
|
|
||||||
state,
|
|
||||||
), // Qui c'è la magia paranoica
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// Overlay di caricamento universale
|
|
||||||
if (state.isLoading)
|
|
||||||
Container(
|
|
||||||
color: Colors.black.withValues(alpha: 0.5),
|
|
||||||
child: const Center(child: CircularProgressIndicator()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- I METODI DEI FORM ---
|
|
||||||
// (Nella realtà li metterai in file separati o widget custom per pulizia)
|
|
||||||
|
|
||||||
Widget _buildCompanyForm(BuildContext context, OnboardingState state) {
|
|
||||||
// Controller e chiavi del form...
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"Step 1: La tua Azienda",
|
|
||||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text("Inserisci i dati della tua attività."),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
|
|
||||||
// Esempio usando la tua FluxTextField
|
|
||||||
FluxTextField(label: 'Ragione Sociale / Nome Azienda'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FluxTextField(label: 'Partita IVA'),
|
|
||||||
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// 1. Valida il form
|
|
||||||
// 2. Crea il CompanyModel
|
|
||||||
// 3. Chiama il Cubit:
|
|
||||||
// context.read<OnboardingCubit>().saveCompany(newCompany);
|
|
||||||
},
|
|
||||||
child: const Text("Salva e prosegui"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStoreForm(BuildContext context, OnboardingState state) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"Step 2: Il primo Negozio",
|
|
||||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text("Dove si trova il tuo punto vendita principale?"),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
|
|
||||||
FluxTextField(label: 'Nome Negozio (es. Sede Centrale)'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FluxTextField(label: 'Indirizzo'),
|
|
||||||
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// context.read<OnboardingCubit>().saveStore(newStore);
|
|
||||||
},
|
|
||||||
child: const Text("Salva Negozio"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStaffForm(BuildContext context, OnboardingState state) {
|
|
||||||
// NOTA PARANOICA: Qui chiediamo jobTitle (Testo libero),
|
|
||||||
// ma NON diamo modo di scegliere il system_role!
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"Step 3: Il tuo Profilo",
|
|
||||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text("Crea il tuo profilo operativo per iniziare a lavorare."),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
|
|
||||||
FluxTextField(label: 'Nome'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FluxTextField(label: 'Cognome'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// TESTO LIBERO! Il cliente ci scrive quello che vuole ("CEO", "Boss", "Stagista")
|
|
||||||
FluxTextField(label: 'Ruolo / Etichetta (es. Titolare)'),
|
|
||||||
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// Quando chiami il Cubit, passi i dati della UI.
|
|
||||||
// Ti ricordi? L'OnboardingCubit forzerà `system_role = SystemRole.admin`
|
|
||||||
// dietro le quinte!
|
|
||||||
// context.read<OnboardingCubit>().saveStaff(newStaff);
|
|
||||||
},
|
|
||||||
child: const Text("Inizia a usare Flux!"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ part 'services_state.dart';
|
|||||||
|
|
||||||
class ServicesCubit extends Cubit<ServicesState> {
|
class ServicesCubit extends Cubit<ServicesState> {
|
||||||
final ServicesRepository _repository = GetIt.I<ServicesRepository>();
|
final ServicesRepository _repository = GetIt.I<ServicesRepository>();
|
||||||
final SessionBloc _sessionBloc = GetIt.I<SessionBloc>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
ServicesCubit() : super(const ServicesState(status: ServicesStatus.initial));
|
ServicesCubit() : super(const ServicesState(status: ServicesStatus.initial));
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final currentOffset = refresh ? 0 : state.allServices.length;
|
final currentOffset = refresh ? 0 : state.allServices.length;
|
||||||
final companyId = _sessionBloc.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");
|
||||||
@@ -126,10 +126,10 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentService: ServiceModel(
|
currentService: ServiceModel(
|
||||||
storeId: _sessionBloc.state.selectedStore?.id ?? '',
|
storeId: _sessionCubit.state.currentStore?.id ?? '',
|
||||||
number: '', // Sarà compilato dall'utente
|
number: '', // Sarà compilato dall'utente
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
companyId: _sessionBloc.state.company!.id,
|
companyId: _sessionCubit.state.company!.id!,
|
||||||
),
|
),
|
||||||
status: ServicesStatus.ready,
|
status: ServicesStatus.ready,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import '../models/service_model.dart';
|
|||||||
|
|
||||||
class ServicesRepository {
|
class ServicesRepository {
|
||||||
final _supabase = Supabase.instance.client;
|
final _supabase = Supabase.instance.client;
|
||||||
final companyId = GetIt.I.get<SessionBloc>().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 ---
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ class _EntertainmentFormState extends State<_EntertainmentForm> {
|
|||||||
// Suggerimenti rapidi (Chip)
|
// Suggerimenti rapidi (Chip)
|
||||||
FutureBuilder<List<String>>(
|
FutureBuilder<List<String>>(
|
||||||
future: GetIt.I<ServicesRepository>().fetchTopEntertainmentTypes(
|
future: GetIt.I<ServicesRepository>().fetchTopEntertainmentTypes(
|
||||||
GetIt.I<SessionBloc>().state.company!.id,
|
GetIt.I<SessionCubit>().state.company!.id!,
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final suggestions = snapshot.data ?? ["Netflix", "DAZN", "Sky"];
|
final suggestions = snapshot.data ?? ["Netflix", "DAZN", "Sky"];
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ 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 startNewService(BuildContext context) {
|
||||||
final session = context.read<SessionBloc>().state;
|
final session = context.read<SessionCubit>().state;
|
||||||
final currentStoreId = session.selectedStore?.id;
|
final currentStoreId = session.currentStore?.id;
|
||||||
|
|
||||||
if (currentStoreId == null) {
|
if (currentStoreId == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -59,7 +59,7 @@ void startNewService(BuildContext context) {
|
|||||||
employeeId: member.id,
|
employeeId: member.id,
|
||||||
number: '',
|
number: '',
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
companyId: session.company!.id,
|
companyId: session.company!.id!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user