refined responsive ui for dashboard and start customer work
This commit is contained in:
@@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flux/core/blocs/session/session_bloc.dart';
|
import 'package:flux/core/blocs/session/session_bloc.dart';
|
||||||
import 'package:flux/features/auth/ui/auth_screen.dart';
|
import 'package:flux/features/auth/ui/auth_screen.dart';
|
||||||
import 'package:flux/features/company/ui/create_company_screen.dart';
|
import 'package:flux/features/company/ui/create_company_screen.dart';
|
||||||
import 'package:flux/features/home/ui/dashboard_content.dart';
|
import 'package:flux/features/home_and_dashboard/ui/home_screen.dart';
|
||||||
import 'package:flux/features/store/ui/create_store_screen.dart';
|
import 'package:flux/features/store/ui/create_store_screen.dart';
|
||||||
import 'package:flux/ui/home_screen.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
|||||||
82
lib/features/customers/blocs/customer_bloc.dart
Normal file
82
lib/features/customers/blocs/customer_bloc.dart
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flux/features/customers/data/customer_repository.dart';
|
||||||
|
import 'package:flux/features/customers/models/customer_model.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
part 'customer_events.dart';
|
||||||
|
part 'customer_state.dart';
|
||||||
|
|
||||||
|
class CustomerBloc extends Bloc<CustomerEvent, CustomerState> {
|
||||||
|
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
|
||||||
|
|
||||||
|
CustomerBloc() : super(const CustomerState()) {
|
||||||
|
on<LoadCustomersRequested>(_onLoadCustomers);
|
||||||
|
on<CreateCustomerRequested>(_onCreateCustomer);
|
||||||
|
on<SearchCustomersRequested>(_onSearchCustomers);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadCustomers(
|
||||||
|
LoadCustomersRequested event,
|
||||||
|
Emitter<CustomerState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: CustomerStatus.loading));
|
||||||
|
try {
|
||||||
|
final customers = await _repository.getCustomers(event.companyId);
|
||||||
|
emit(
|
||||||
|
state.copyWith(status: CustomerStatus.success, customers: customers),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: CustomerStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onCreateCustomer(
|
||||||
|
CreateCustomerRequested event,
|
||||||
|
Emitter<CustomerState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: CustomerStatus.loading));
|
||||||
|
try {
|
||||||
|
final newCustomer = await _repository.createCustomer(event.customer);
|
||||||
|
|
||||||
|
// Aggiorniamo la lista locale aggiungendo il nuovo cliente in cima
|
||||||
|
final updatedList = List<CustomerModel>.from(state.customers)
|
||||||
|
..insert(0, newCustomer);
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: CustomerStatus.success,
|
||||||
|
customers: updatedList,
|
||||||
|
lastCreatedCustomer:
|
||||||
|
newCustomer, // Lo passiamo per le Dialog "al volo"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: CustomerStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSearchCustomers(
|
||||||
|
SearchCustomersRequested event,
|
||||||
|
Emitter<CustomerState> emit,
|
||||||
|
) async {
|
||||||
|
// Non mettiamo loading per evitare flickering durante la digitazione
|
||||||
|
try {
|
||||||
|
final results = await _repository.searchCustomers(
|
||||||
|
event.companyId,
|
||||||
|
event.query,
|
||||||
|
);
|
||||||
|
emit(state.copyWith(status: CustomerStatus.success, customers: results));
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
lib/features/customers/blocs/customer_events.dart
Normal file
26
lib/features/customers/blocs/customer_events.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
part of 'customer_bloc.dart';
|
||||||
|
|
||||||
|
abstract class CustomerEvent extends Equatable {
|
||||||
|
const CustomerEvent();
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carica tutti i clienti dell'azienda
|
||||||
|
class LoadCustomersRequested extends CustomerEvent {
|
||||||
|
final String companyId;
|
||||||
|
const LoadCustomersRequested(this.companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea un cliente (usato sia dalla lista che dalla Dialog operazioni)
|
||||||
|
class CreateCustomerRequested extends CustomerEvent {
|
||||||
|
final CustomerModel customer;
|
||||||
|
const CreateCustomerRequested(this.customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ricerca in tempo reale
|
||||||
|
class SearchCustomersRequested extends CustomerEvent {
|
||||||
|
final String companyId;
|
||||||
|
final String query;
|
||||||
|
const SearchCustomersRequested(this.companyId, this.query);
|
||||||
|
}
|
||||||
40
lib/features/customers/blocs/customer_state.dart
Normal file
40
lib/features/customers/blocs/customer_state.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
part of 'customer_bloc.dart';
|
||||||
|
|
||||||
|
enum CustomerStatus { initial, loading, success, failure }
|
||||||
|
|
||||||
|
class CustomerState extends Equatable {
|
||||||
|
final CustomerStatus status;
|
||||||
|
final List<CustomerModel> customers; // Per la lista generale
|
||||||
|
final CustomerModel?
|
||||||
|
lastCreatedCustomer; // <--- Fondamentale per la Dialog "al volo"
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
const CustomerState({
|
||||||
|
this.status = CustomerStatus.initial,
|
||||||
|
this.customers = const [],
|
||||||
|
this.lastCreatedCustomer,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
CustomerState copyWith({
|
||||||
|
CustomerStatus? status,
|
||||||
|
List<CustomerModel>? customers,
|
||||||
|
CustomerModel? lastCreatedCustomer,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return CustomerState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
customers: customers ?? this.customers,
|
||||||
|
lastCreatedCustomer: lastCreatedCustomer ?? this.lastCreatedCustomer,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
status,
|
||||||
|
customers,
|
||||||
|
lastCreatedCustomer,
|
||||||
|
errorMessage,
|
||||||
|
];
|
||||||
|
}
|
||||||
56
lib/features/customers/data/customer_repository.dart
Normal file
56
lib/features/customers/data/customer_repository.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
import '../models/customer_model.dart';
|
||||||
|
|
||||||
|
class CustomerRepository {
|
||||||
|
final SupabaseClient _client = GetIt.I<SupabaseClient>();
|
||||||
|
|
||||||
|
// Crea un nuovo cliente
|
||||||
|
Future<CustomerModel> createCustomer(CustomerModel customer) async {
|
||||||
|
try {
|
||||||
|
final response = await _client
|
||||||
|
.from('customer')
|
||||||
|
.insert(customer.toJson())
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
return CustomerModel.fromJson(response);
|
||||||
|
} catch (e) {
|
||||||
|
throw 'Errore durante la creazione del cliente: $e';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera tutti i clienti dell'azienda
|
||||||
|
Future<List<CustomerModel>> getCustomers(String companyId) async {
|
||||||
|
try {
|
||||||
|
final response = await _client
|
||||||
|
.from('customer')
|
||||||
|
.select()
|
||||||
|
.eq('company_id', companyId)
|
||||||
|
.eq('is_active', true)
|
||||||
|
.order('nome');
|
||||||
|
|
||||||
|
return (response as List).map((c) => CustomerModel.fromJson(c)).toList();
|
||||||
|
} catch (e) {
|
||||||
|
throw 'Errore nel recupero clienti';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ricerca clienti per nome o telefono (fondamentale per la UX)
|
||||||
|
Future<List<CustomerModel>> searchCustomers(
|
||||||
|
String companyId,
|
||||||
|
String query,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _client
|
||||||
|
.from('customer')
|
||||||
|
.select()
|
||||||
|
.eq('company_id', companyId)
|
||||||
|
.or('nome.ilike.%$query%,telefono.ilike.%$query%')
|
||||||
|
.limit(10);
|
||||||
|
|
||||||
|
return (response as List).map((c) => CustomerModel.fromJson(c)).toList();
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
lib/features/customers/models/customer_model.dart
Normal file
101
lib/features/customers/models/customer_model.dart
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class CustomerModel extends Equatable {
|
||||||
|
final BigInt? id; // Bigint in SQL
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final String nome;
|
||||||
|
final String telefono;
|
||||||
|
final String email;
|
||||||
|
final String note;
|
||||||
|
final DateTime? dataUltimoContatto;
|
||||||
|
final bool nonDisturbare;
|
||||||
|
final String companyId; // UUID
|
||||||
|
final bool isActive;
|
||||||
|
|
||||||
|
const CustomerModel({
|
||||||
|
this.id,
|
||||||
|
this.createdAt,
|
||||||
|
required this.nome,
|
||||||
|
required this.telefono,
|
||||||
|
required this.email,
|
||||||
|
required this.note,
|
||||||
|
this.dataUltimoContatto,
|
||||||
|
this.nonDisturbare = false,
|
||||||
|
required this.companyId,
|
||||||
|
this.isActive = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
nome,
|
||||||
|
telefono,
|
||||||
|
email,
|
||||||
|
note,
|
||||||
|
dataUltimoContatto,
|
||||||
|
nonDisturbare,
|
||||||
|
companyId,
|
||||||
|
isActive,
|
||||||
|
];
|
||||||
|
|
||||||
|
CustomerModel copyWith({
|
||||||
|
BigInt? id,
|
||||||
|
DateTime? createdAt,
|
||||||
|
String? nome,
|
||||||
|
String? telefono,
|
||||||
|
String? email,
|
||||||
|
String? note,
|
||||||
|
DateTime? dataUltimoContatto,
|
||||||
|
bool? nonDisturbare,
|
||||||
|
String? companyId,
|
||||||
|
bool? isActive,
|
||||||
|
}) {
|
||||||
|
return CustomerModel(
|
||||||
|
id: id ?? this.id,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
nome: nome ?? this.nome,
|
||||||
|
telefono: telefono ?? this.telefono,
|
||||||
|
email: email ?? this.email,
|
||||||
|
note: note ?? this.note,
|
||||||
|
dataUltimoContatto: dataUltimoContatto ?? this.dataUltimoContatto,
|
||||||
|
nonDisturbare: nonDisturbare ?? this.nonDisturbare,
|
||||||
|
companyId: companyId ?? this.companyId,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CustomerModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CustomerModel(
|
||||||
|
id: json['id'],
|
||||||
|
createdAt: json['created_at'] != null
|
||||||
|
? DateTime.parse(json['created_at'])
|
||||||
|
: null,
|
||||||
|
nome: json['nome'],
|
||||||
|
telefono: json['telefono'],
|
||||||
|
email: json['email'],
|
||||||
|
note: json['note'] ?? '',
|
||||||
|
dataUltimoContatto: json['data_ultimo_contatto'] != null
|
||||||
|
? DateTime.parse(json['data_ultimo_contatto'])
|
||||||
|
: null,
|
||||||
|
nonDisturbare: json['non_disturbare'] ?? false,
|
||||||
|
companyId: json['company_id'],
|
||||||
|
isActive: json['is_active'] ?? true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
'nome': nome,
|
||||||
|
'telefono': telefono,
|
||||||
|
'email': email,
|
||||||
|
'note': note,
|
||||||
|
if (dataUltimoContatto != null)
|
||||||
|
'data_ultimo_contatto': dataUltimoContatto!.toIso8601String(),
|
||||||
|
'non_disturbare': nonDisturbare,
|
||||||
|
'company_id': companyId,
|
||||||
|
'is_active': isActive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flux/core/blocs/session/session_bloc.dart';
|
|
||||||
import 'package:flux/core/theme/theme.dart';
|
|
||||||
|
|
||||||
class DashboardContent extends StatelessWidget {
|
|
||||||
final bool? isLargeScreen;
|
|
||||||
|
|
||||||
const DashboardContent({super.key, this.isLargeScreen});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// Ascoltiamo il SessionBloc per avere i dati in tempo reale
|
|
||||||
return BlocBuilder<SessionBloc, SessionState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final store = state.selectedStore;
|
|
||||||
final company = state.company;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
// Un'AppBar elegante che si rimpicciolisce
|
|
||||||
SliverAppBar(
|
|
||||||
expandedHeight: 120.0,
|
|
||||||
floating: false,
|
|
||||||
pinned: true,
|
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
|
||||||
title: Text(
|
|
||||||
store?.nome ?? 'Flux Dashboard',
|
|
||||||
style: TextStyle(color: context.primaryText),
|
|
||||||
),
|
|
||||||
background: Container(color: context.background),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.settings_outlined),
|
|
||||||
onPressed: () => Navigator.pushNamed(context, '/settings'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Center(
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints(
|
|
||||||
maxWidth: 1200,
|
|
||||||
), // Larghezza massima "confortevole"
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(20.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildWelcomeHeader(context, company?.ragioneSociale),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
_SectionTitle(title: 'AZIONI RAPIDE'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildGridActions(context),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
_SectionTitle(title: 'STATO NEGOZIO'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildInfoCard(context, store),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildWelcomeHeader(BuildContext context, String? companyName) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Bentornato,',
|
|
||||||
style: TextStyle(color: context.secondaryText, fontSize: 16),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
companyName ?? 'La tua Azienda',
|
|
||||||
style: TextStyle(
|
|
||||||
color: context.primaryText,
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildGridActions(BuildContext context) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
// Calcoliamo il numero di colonne in base alla larghezza
|
|
||||||
// Sotto i 600px (mobile): 2 colonne
|
|
||||||
// Tra 600 e 1000px (tablet): 3 o 4 colonne
|
|
||||||
// Sopra i 1000px (desktop): 6 colonne
|
|
||||||
int crossAxisCount = 2;
|
|
||||||
if (constraints.maxWidth > 1000) {
|
|
||||||
crossAxisCount = 6;
|
|
||||||
} else if (constraints.maxWidth > 600) {
|
|
||||||
crossAxisCount = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GridView.count(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
crossAxisCount: crossAxisCount,
|
|
||||||
mainAxisSpacing: 16,
|
|
||||||
crossAxisSpacing: 16,
|
|
||||||
// Su desktop rendiamo i tasti un po' più squadrati (1.0)
|
|
||||||
// Su mobile manteniamo il rettangolo (1.5)
|
|
||||||
childAspectRatio: constraints.maxWidth > 600 ? 1.2 : 1.5,
|
|
||||||
children: [
|
|
||||||
_ActionCard(
|
|
||||||
label: 'Nuova Op',
|
|
||||||
icon: Icons.add_task_rounded,
|
|
||||||
color: context.accent,
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
_ActionCard(
|
|
||||||
label: 'Clienti',
|
|
||||||
icon: Icons.people_alt_rounded,
|
|
||||||
color: Colors.orange,
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
_ActionCard(
|
|
||||||
label: 'Campagne',
|
|
||||||
icon: Icons.campaign_rounded,
|
|
||||||
color: Colors.purple,
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
_ActionCard(
|
|
||||||
label: 'Report',
|
|
||||||
icon: Icons.analytics_rounded,
|
|
||||||
color: Colors.teal,
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
// Se siamo su desktop, possiamo aggiungere altri slot senza affollare
|
|
||||||
if (constraints.maxWidth > 600) ...[
|
|
||||||
_ActionCard(
|
|
||||||
label: 'Impostazioni',
|
|
||||||
icon: Icons.settings,
|
|
||||||
color: Colors.grey,
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
_ActionCard(
|
|
||||||
label: 'Supporto',
|
|
||||||
icon: Icons.help_outline,
|
|
||||||
color: Colors.blueGrey,
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInfoCard(BuildContext context, dynamic store) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: context.accent.withValues(alpha: 0.1),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.location_on_rounded, color: context.accent),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'${store?.indirizzo}, ${store?.comune}',
|
|
||||||
style: TextStyle(color: context.primaryText),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ActionCard extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
final IconData icon;
|
|
||||||
final Color color;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
|
|
||||||
const _ActionCard({
|
|
||||||
required this.label,
|
|
||||||
required this.icon,
|
|
||||||
required this.color,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: context.background,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(color: context.accent.withValues(alpha: 0.1)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(icon, color: color, size: 32),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SectionTitle extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
const _SectionTitle({required this.title});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
color: context.secondaryText,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 12,
|
|
||||||
letterSpacing: 1.1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
206
lib/features/home_and_dashboard/ui/dashboard_content.dart
Normal file
206
lib/features/home_and_dashboard/ui/dashboard_content.dart
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flux/core/blocs/session/session_bloc.dart';
|
||||||
|
import 'package:flux/core/theme/theme.dart';
|
||||||
|
|
||||||
|
class DashboardContent extends StatelessWidget {
|
||||||
|
final bool isLargeScreen;
|
||||||
|
const DashboardContent({super.key, this.isLargeScreen = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<SessionBloc, SessionState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final store = state.selectedStore;
|
||||||
|
final company = state.company;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: context.background,
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
expandedHeight: 100.0,
|
||||||
|
floating: false,
|
||||||
|
pinned: true,
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: context.background,
|
||||||
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
titlePadding: const EdgeInsets.only(left: 24, bottom: 16),
|
||||||
|
title: Text(
|
||||||
|
store?.nome ?? 'Dashboard',
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.primaryText,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 1200),
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildWelcome(context, company?.ragioneSociale),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
const _SectionTitle(title: 'AZIONI RAPIDE'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildAdaptiveGrid(context),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
const _SectionTitle(title: 'INFO PUNTO VENDITA'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildStoreCard(context, store),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWelcome(BuildContext context, String? name) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Benvenuto in',
|
||||||
|
style: TextStyle(color: context.secondaryText, fontSize: 16),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
name ?? 'Azienda',
|
||||||
|
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAdaptiveGrid(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
// Logica Colonne: Mobile 2, Tablet 3, Desktop 4+
|
||||||
|
int crossAxisCount = 2;
|
||||||
|
if (constraints.maxWidth > 1000) {
|
||||||
|
crossAxisCount = 5;
|
||||||
|
} else if (constraints.maxWidth > 700) {
|
||||||
|
crossAxisCount = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GridView.count(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
crossAxisSpacing: 16,
|
||||||
|
childAspectRatio: isLargeScreen ? 1.3 : 1.5,
|
||||||
|
children: [
|
||||||
|
_ActionCard(
|
||||||
|
label: 'Nuova Op',
|
||||||
|
icon: Icons.add_task,
|
||||||
|
color: context.accent,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
_ActionCard(
|
||||||
|
label: 'Clienti',
|
||||||
|
icon: Icons.people,
|
||||||
|
color: Colors.orange,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
_ActionCard(
|
||||||
|
label: 'Campagne',
|
||||||
|
icon: Icons.campaign,
|
||||||
|
color: Colors.purple,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
_ActionCard(
|
||||||
|
label: 'Report',
|
||||||
|
icon: Icons.analytics,
|
||||||
|
color: Colors.teal,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStoreCard(BuildContext context, dynamic store) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.accent.withValues(alpha: 0.05),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: context.accent.withValues(alpha: 0.1)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.location_on, color: context.accent),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Text('${store?.indirizzo}, ${store?.comune} (${store?.provincia})'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget di supporto rimasti invariati (ActionCard e SectionTitle)
|
||||||
|
class _ActionCard extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final IconData icon;
|
||||||
|
final Color color;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const _ActionCard({
|
||||||
|
required this.label,
|
||||||
|
required this.icon,
|
||||||
|
required this.color,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
// CAMBIA QUI: da Border.all a BorderSide
|
||||||
|
side: BorderSide(
|
||||||
|
color: context.accent.withValues(alpha: 0.1),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: color, size: 32),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SectionTitle extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
const _SectionTitle({required this.title});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.accent,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ 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_bloc.dart';
|
import 'package:flux/core/blocs/session/session_bloc.dart';
|
||||||
import 'package:flux/core/theme/theme.dart';
|
import 'package:flux/core/theme/theme.dart';
|
||||||
import 'package:flux/features/home/ui/dashboard_content.dart';
|
import 'dashboard_content.dart'; // Importiamo il contenuto della dashboard
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
@@ -20,25 +20,28 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
// Definiamo se siamo su uno schermo "Large" (es. sopra i 900px)
|
// Se lo schermo è più largo di 900px usiamo il layout Desktop
|
||||||
final bool isLargeScreen = constraints.maxWidth > 900;
|
final bool isLargeScreen = constraints.maxWidth > 900;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
// --- SIDEBAR (Solo per schermi grandi) ---
|
// --- SIDEBAR (Solo Desktop/Tablet) ---
|
||||||
if (isLargeScreen)
|
if (isLargeScreen)
|
||||||
NavigationRail(
|
NavigationRail(
|
||||||
selectedIndex: _selectedIndex,
|
selectedIndex: _selectedIndex,
|
||||||
onDestinationSelected: (int index) {
|
onDestinationSelected: (index) =>
|
||||||
setState(() => _selectedIndex = index);
|
setState(() => _selectedIndex = index),
|
||||||
},
|
extended: constraints.maxWidth > 1200,
|
||||||
extended:
|
backgroundColor: context.background,
|
||||||
constraints.maxWidth >
|
selectedIconTheme: IconThemeData(color: context.accent),
|
||||||
1200, // Si allarga se c'è molto spazio
|
selectedLabelTextStyle: TextStyle(
|
||||||
labelType: constraints.maxWidth > 1200
|
color: context.accent,
|
||||||
? NavigationRailLabelType.none
|
fontWeight: FontWeight.bold,
|
||||||
: NavigationRailLabelType.all,
|
),
|
||||||
|
unselectedLabelTextStyle: TextStyle(
|
||||||
|
color: context.secondaryText,
|
||||||
|
),
|
||||||
leading: _buildRailHeader(constraints.maxWidth > 1200),
|
leading: _buildRailHeader(constraints.maxWidth > 1200),
|
||||||
destinations: const [
|
destinations: const [
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
@@ -59,18 +62,20 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// --- CONTENUTO PRINCIPALE ---
|
// --- CONTENUTO DINAMICO ---
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildMainContent(context, state, isLargeScreen),
|
child: _buildPageContent(_selectedIndex, isLargeScreen),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// --- BOTTOM NAVIGATION (Solo per Mobile) ---
|
// --- BOTTOM BAR (Solo Mobile) ---
|
||||||
bottomNavigationBar: isLargeScreen
|
bottomNavigationBar: isLargeScreen
|
||||||
? null
|
? null
|
||||||
: BottomNavigationBar(
|
: BottomNavigationBar(
|
||||||
currentIndex: _selectedIndex,
|
currentIndex: _selectedIndex,
|
||||||
onTap: (index) => setState(() => _selectedIndex = index),
|
onTap: (index) => setState(() => _selectedIndex = index),
|
||||||
|
selectedItemColor: context.accent,
|
||||||
|
unselectedItemColor: context.secondaryText,
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.dashboard),
|
icon: Icon(Icons.dashboard),
|
||||||
@@ -93,7 +98,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header per la Sidebar (Logo o Icona)
|
|
||||||
Widget _buildRailHeader(bool isExtended) {
|
Widget _buildRailHeader(bool isExtended) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||||
@@ -110,19 +114,17 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMainContent(
|
// Switch tra le sottopagine
|
||||||
BuildContext context,
|
Widget _buildPageContent(int index, bool isLargeScreen) {
|
||||||
SessionState state,
|
switch (index) {
|
||||||
bool isLargeScreen,
|
|
||||||
) {
|
|
||||||
// Qui gestiamo lo switch tra le pagine
|
|
||||||
switch (_selectedIndex) {
|
|
||||||
case 0:
|
case 0:
|
||||||
return DashboardContent(isLargeScreen: isLargeScreen);
|
return DashboardContent(isLargeScreen: isLargeScreen);
|
||||||
case 1:
|
case 1:
|
||||||
return const Center(child: Text('Pagina Clienti')); // La faremo!
|
return const Center(child: Text('Pagina Clienti (Coming Soon)'));
|
||||||
|
case 2:
|
||||||
|
return const Center(child: Text('Pagina Operazioni (Coming Soon)'));
|
||||||
default:
|
default:
|
||||||
return const DashboardContent();
|
return DashboardContent(isLargeScreen: isLargeScreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,12 @@ import 'package:flux/core/routes/app_router.dart';
|
|||||||
import 'package:flux/core/theme/theme.dart';
|
import 'package:flux/core/theme/theme.dart';
|
||||||
import 'package:flux/core/theme/bloc/theme_bloc.dart';
|
import 'package:flux/core/theme/bloc/theme_bloc.dart';
|
||||||
import 'package:flux/features/auth/bloc/auth_bloc.dart';
|
import 'package:flux/features/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:flux/features/auth/ui/auth_screen.dart';
|
|
||||||
import 'package:flux/features/company/bloc/company_bloc.dart';
|
import 'package:flux/features/company/bloc/company_bloc.dart';
|
||||||
import 'package:flux/features/company/data/company_repository.dart';
|
import 'package:flux/features/company/data/company_repository.dart';
|
||||||
import 'package:flux/features/company/ui/create_company_screen.dart';
|
import 'package:flux/features/customers/blocs/customer_bloc.dart';
|
||||||
|
import 'package:flux/features/customers/data/customer_repository.dart';
|
||||||
import 'package:flux/features/store/bloc/store_bloc.dart';
|
import 'package:flux/features/store/bloc/store_bloc.dart';
|
||||||
import 'package:flux/features/store/data/store_repository.dart';
|
import 'package:flux/features/store/data/store_repository.dart';
|
||||||
import 'package:flux/features/store/ui/create_store_screen.dart';
|
|
||||||
import 'package:flux/ui/home_screen.dart';
|
|
||||||
import 'package:flux/features/settings/settings.dart';
|
import 'package:flux/features/settings/settings.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@@ -34,6 +32,7 @@ void main() async {
|
|||||||
BlocProvider<AuthBloc>(create: (context) => AuthBloc()),
|
BlocProvider<AuthBloc>(create: (context) => AuthBloc()),
|
||||||
BlocProvider<CompanyBloc>(create: (context) => CompanyBloc()),
|
BlocProvider<CompanyBloc>(create: (context) => CompanyBloc()),
|
||||||
BlocProvider<StoreBloc>(create: (context) => StoreBloc()),
|
BlocProvider<StoreBloc>(create: (context) => StoreBloc()),
|
||||||
|
BlocProvider<CustomerBloc>(create: (context) => CustomerBloc()),
|
||||||
],
|
],
|
||||||
child: const FluxApp(),
|
child: const FluxApp(),
|
||||||
),
|
),
|
||||||
@@ -55,6 +54,7 @@ Future<void> setupLocator() async {
|
|||||||
getIt.registerLazySingleton<AppSettings>(() => AppSettings());
|
getIt.registerLazySingleton<AppSettings>(() => AppSettings());
|
||||||
getIt.registerLazySingleton<CompanyRepository>(() => CompanyRepository());
|
getIt.registerLazySingleton<CompanyRepository>(() => CompanyRepository());
|
||||||
getIt.registerLazySingleton<StoreRepository>(() => StoreRepository());
|
getIt.registerLazySingleton<StoreRepository>(() => StoreRepository());
|
||||||
|
getIt.registerLazySingleton<CustomerRepository>(() => CustomerRepository());
|
||||||
}
|
}
|
||||||
|
|
||||||
class FluxApp extends StatelessWidget {
|
class FluxApp extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flux/core/theme/theme.dart';
|
|
||||||
|
|
||||||
class AnagraficheMainView extends StatelessWidget {
|
|
||||||
const AnagraficheMainView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// Controller locale per gestire i Tab
|
|
||||||
return DefaultTabController(
|
|
||||||
length: 4,
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Anagrafiche'),
|
|
||||||
bottom: TabBar(
|
|
||||||
isScrollable: true,
|
|
||||||
indicatorColor: FluxColors.accentTurquoise,
|
|
||||||
labelColor: FluxColors.accentTurquoise,
|
|
||||||
unselectedLabelColor: Theme.of(context).textTheme.bodyMedium?.color,
|
|
||||||
tabs: [
|
|
||||||
Tab(icon: Icon(Icons.storefront), text: 'Negozi'),
|
|
||||||
Tab(icon: Icon(Icons.support_agent), text: 'Gestori'),
|
|
||||||
Tab(icon: Icon(Icons.assignment_ind), text: 'Clienti'),
|
|
||||||
Tab(icon: Icon(Icons.phone_android), text: 'Prodotti'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: const TabBarView(
|
|
||||||
children: [
|
|
||||||
// Esempi di view iniettate con Bloc dedicati (da implementare)
|
|
||||||
/* ElencoEntitaView(tipoEntita: 'Negozi'), // Provider<AnagraficaBloc>...
|
|
||||||
ElencoEntitaView(tipoEntita: 'Gestori'),
|
|
||||||
ElencoEntitaView(tipoEntita: 'Clienti'),
|
|
||||||
ElencoEntitaView(tipoEntita: 'Prodotti'), */
|
|
||||||
Placeholder(),
|
|
||||||
Placeholder(),
|
|
||||||
Placeholder(),
|
|
||||||
Placeholder(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flux/core/theme/theme.dart';
|
|
||||||
|
|
||||||
class DashboardView extends StatelessWidget {
|
|
||||||
const DashboardView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// Iniezione del Bloc per la creazione di operazioni (da implementare)
|
|
||||||
return /* BlocProvider(
|
|
||||||
create: (context) => OperazioneBloc(), // Implementa la logica nel Bloc
|
|
||||||
child: */ Scaffold(
|
|
||||||
appBar: AppBar(title: Text('FLUX')),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
padding: EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_WelcomeHeader(),
|
|
||||||
SizedBox(height: 24),
|
|
||||||
_QuickActions(), // Contiene "Nuova Operazione"
|
|
||||||
SizedBox(height: 24),
|
|
||||||
_RecentActivityPreview(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WelcomeHeader extends StatelessWidget {
|
|
||||||
const _WelcomeHeader();
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('Benvenuto,', style: Theme.of(context).textTheme.bodyMedium),
|
|
||||||
Text(
|
|
||||||
'Negozio Piacenza Centro',
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _QuickActions extends StatelessWidget {
|
|
||||||
const _QuickActions();
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'AZIONI RAPIDE',
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.titleSmall?.copyWith(color: FluxColors.accentTurquoise),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
ElevatedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Emetti evento al Bloc: BlocProvider.of<OperazioneBloc>(context).add(IniziaNuovaOperazione());
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Avvio Nuova Operazione...')),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: const Text('NUOVA OPERAZIONE TELCO'),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
minimumSize: const Size(double.infinity, 50),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecentActivityPreview extends StatelessWidget {
|
|
||||||
const _RecentActivityPreview();
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Attività Recenti',
|
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Divider(color: Theme.of(context).textTheme.bodyMedium?.color),
|
|
||||||
// Sostituire con BlocBuilder
|
|
||||||
_activityTile('Nuova Linea', 'Mario Rossi', '10 min fa', context),
|
|
||||||
_activityTile('Assistenza Tech', 'iPhone 13', '45 min fa', context),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _activityTile(
|
|
||||||
String title,
|
|
||||||
String subtitle,
|
|
||||||
String time,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
return ListTile(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
leading: const Icon(Icons.history, color: FluxColors.accentTurquoise),
|
|
||||||
title: Text(title, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
subtitle: Text(subtitle),
|
|
||||||
trailing: Text(time, style: Theme.of(context).textTheme.bodyMedium),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flux/core/theme/theme.dart';
|
|
||||||
import 'package:flux/ui/anagrafiche/anagrafiche_main_view.dart';
|
|
||||||
import 'package:flux/ui/dashboard/dashboard_view.dart';
|
|
||||||
import 'package:flux/features/settings/settings_view.dart';
|
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
|
||||||
const HomeScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<HomeScreen> createState() => _HomeScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
|
||||||
int _selectedIndex = 0;
|
|
||||||
|
|
||||||
static const List<Widget> _widgetOptions = <Widget>[
|
|
||||||
DashboardView(), // Contiene Nuova Operazione
|
|
||||||
Placeholder(),
|
|
||||||
AnagraficheMainView(), // Gestisce [negozi, gestori, clienti, prodotti]
|
|
||||||
SettingsView(),
|
|
||||||
];
|
|
||||||
|
|
||||||
void _onItemTapped(int index) {
|
|
||||||
setState(() {
|
|
||||||
_selectedIndex = index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
|
||||||
return Scaffold(
|
|
||||||
body: Center(child: _widgetOptions.elementAt(_selectedIndex)),
|
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
|
||||||
items: const <BottomNavigationBarItem>[
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Icon(Icons.dashboard_outlined),
|
|
||||||
activeIcon: Icon(Icons.dashboard),
|
|
||||||
label: 'Dashboard',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Icon(Icons.history_edu_outlined),
|
|
||||||
activeIcon: Icon(Icons.history_edu),
|
|
||||||
label: 'Operazioni',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Icon(Icons.people_alt_outlined),
|
|
||||||
activeIcon: Icon(Icons.people_alt),
|
|
||||||
label: 'Anagrafiche',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Icon(Icons.settings_outlined),
|
|
||||||
activeIcon: Icon(Icons.settings),
|
|
||||||
label: 'Impostazioni',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
currentIndex: _selectedIndex,
|
|
||||||
selectedItemColor: FluxColors.accentTurquoise,
|
|
||||||
unselectedItemColor: Theme.of(context).textTheme.bodyMedium?.color,
|
|
||||||
backgroundColor: surfaceColor,
|
|
||||||
type: BottomNavigationBarType.fixed,
|
|
||||||
onTap: _onItemTapped,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user