feat-ultimi_servizi-contratti_in_scadenza #12
@@ -12,6 +12,7 @@ import 'package:flux/features/customers/models/customer_model.dart';
|
|||||||
import 'package:flux/features/customers/ui/customer_detail_screen.dart';
|
import 'package:flux/features/customers/ui/customer_detail_screen.dart';
|
||||||
import 'package:flux/features/customers/ui/customer_mobile_upload_screen.dart';
|
import 'package:flux/features/customers/ui/customer_mobile_upload_screen.dart';
|
||||||
import 'package:flux/features/customers/ui/customers_content.dart';
|
import 'package:flux/features/customers/ui/customers_content.dart';
|
||||||
|
import 'package:flux/features/home/latest_store_services/bloc/latest_store_services_bloc.dart';
|
||||||
import 'package:flux/features/home/ui/home_screen.dart';
|
import 'package:flux/features/home/ui/home_screen.dart';
|
||||||
import 'package:flux/features/master_data/master_data_hub_content.dart';
|
import 'package:flux/features/master_data/master_data_hub_content.dart';
|
||||||
import 'package:flux/features/master_data/products/ui/products_screen.dart';
|
import 'package:flux/features/master_data/products/ui/products_screen.dart';
|
||||||
@@ -24,6 +25,7 @@ import 'package:flux/features/services/blocs/service_files_bloc.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:flux/features/services/ui/service_form_screen/service_mobile_upload_screen.dart';
|
import 'package:flux/features/services/ui/service_form_screen/service_mobile_upload_screen.dart';
|
||||||
|
import 'package:flux/features/services/ui/services_screen.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@@ -128,15 +130,19 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/services',
|
||||||
|
builder: (context, state) => const ServicesScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/customers',
|
||||||
|
builder: (context, state) =>
|
||||||
|
const CustomersContent(), // O come si chiama il tuo widget della lista!
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// --- DETTAGLI E OPERATIVITÀ (FUORI DALLA SHELL - TUTTO SCHERMO) ---
|
// --- DETTAGLI E OPERATIVITÀ (FUORI DALLA SHELL - TUTTO SCHERMO) ---
|
||||||
GoRoute(
|
|
||||||
path: '/customers',
|
|
||||||
builder: (context, state) =>
|
|
||||||
const CustomersContent(), // O come si chiama il tuo widget della lista!
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/customer/:id',
|
path: '/customer/:id',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flux/features/services/data/services_repository.dart';
|
import 'package:flux/features/services/data/services_repository.dart';
|
||||||
@@ -17,19 +19,38 @@ class LatestStoreServicesBloc
|
|||||||
status: LatestStoreServicesStatus.initial,
|
status: LatestStoreServicesStatus.initial,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
on<InitLastServicesEvent>((event, emit) async {
|
on<InitLastStoreServicesEvent>((event, emit) async {
|
||||||
emit(state.copyWith(status: LatestStoreServicesStatus.loading));
|
emit(state.copyWith(status: LatestStoreServicesStatus.loading));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 1. Creiamo uno stream "intermedio" che idrata i dati
|
||||||
|
final hydratedStream = _repository
|
||||||
|
.getLastStoreServicesStream(storeId: event.storeId, limit: 5)
|
||||||
|
.asyncMap((List<ServiceModel> rawServices) async {
|
||||||
|
// Questo gira ad ogni "scatto" dello stream di Supabase
|
||||||
|
List<ServiceModel> fullyHydratedServices = [];
|
||||||
|
|
||||||
|
for (ServiceModel service in rawServices) {
|
||||||
|
// Peschiamo i dati completi (incluso il cliente)
|
||||||
|
ServiceModel fullService = await _repository.fetchServiceById(
|
||||||
|
service.id!,
|
||||||
|
);
|
||||||
|
fullyHydratedServices.add(fullService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passiamo la lista completa allo step successivo
|
||||||
|
return fullyHydratedServices;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Ora passiamo lo stream idratato all'emit.forEach
|
||||||
await emit.forEach(
|
await emit.forEach(
|
||||||
_repository.getLastStoreServicesStream(
|
hydratedStream, // Usiamo lo stream modificato!
|
||||||
storeId: event.storeId,
|
onData: (List<ServiceModel> fullyHydratedServices) {
|
||||||
limit: 5,
|
// Qui ora è tutto sincrono e bellissimo
|
||||||
),
|
return state.copyWith(
|
||||||
onData: (List<ServiceModel> data) => state.copyWith(
|
services: fullyHydratedServices,
|
||||||
status: LatestStoreServicesStatus.success,
|
status: LatestStoreServicesStatus.success,
|
||||||
services: data,
|
);
|
||||||
),
|
},
|
||||||
onError: (error, stackTrace) => state.copyWith(
|
onError: (error, stackTrace) => state.copyWith(
|
||||||
status: LatestStoreServicesStatus.failure,
|
status: LatestStoreServicesStatus.failure,
|
||||||
error: error.toString(),
|
error: error.toString(),
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ sealed class LatestStoreServicesEvent extends Equatable {
|
|||||||
List<Object> get props => [];
|
List<Object> get props => [];
|
||||||
}
|
}
|
||||||
|
|
||||||
class InitLastServicesEvent extends LatestStoreServicesEvent {
|
class InitLastStoreServicesEvent extends LatestStoreServicesEvent {
|
||||||
final String storeId;
|
final String storeId;
|
||||||
|
|
||||||
const InitLastServicesEvent(this.storeId);
|
const InitLastStoreServicesEvent(this.storeId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [storeId];
|
List<Object> get props => [storeId];
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ 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/theme/theme.dart';
|
import 'package:flux/core/theme/theme.dart';
|
||||||
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/features/home/latest_store_services/bloc/latest_store_services_bloc.dart';
|
import 'package:flux/features/home/latest_store_services/bloc/latest_store_services_bloc.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class LatestStoreServicesCard extends StatelessWidget {
|
class LatestStoreServicesCard extends StatelessWidget {
|
||||||
const LatestStoreServicesCard({super.key});
|
const LatestStoreServicesCard({super.key});
|
||||||
@@ -15,7 +17,7 @@ class LatestStoreServicesCard extends StatelessWidget {
|
|||||||
// 1. Creiamo il Bloc e facciamo partire subito la query
|
// 1. Creiamo il Bloc e facciamo partire subito la query
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
LatestStoreServicesBloc()
|
LatestStoreServicesBloc()
|
||||||
..add(InitLastServicesEvent(currentStoreId ?? '')),
|
..add(InitLastStoreServicesEvent(currentStoreId ?? '')),
|
||||||
child: BlocListener<SessionCubit, SessionState>(
|
child: BlocListener<SessionCubit, SessionState>(
|
||||||
// 2. MAGIA: Se l'utente cambia negozio dalla barra in alto, riavviamo lo stream!
|
// 2. MAGIA: Se l'utente cambia negozio dalla barra in alto, riavviamo lo stream!
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
@@ -23,7 +25,7 @@ class LatestStoreServicesCard extends StatelessWidget {
|
|||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.currentStore?.id != null) {
|
if (state.currentStore?.id != null) {
|
||||||
context.read<LatestStoreServicesBloc>().add(
|
context.read<LatestStoreServicesBloc>().add(
|
||||||
InitLastServicesEvent(state.currentStore!.id!),
|
InitLastStoreServicesEvent(state.currentStore!.id!),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -67,15 +69,18 @@ class _LatestServicesCardContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: TextButton(
|
||||||
"Ultimi Servizi",
|
onPressed: () => context.push('/services'),
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
fontWeight: FontWeight.bold,
|
context.l10n.homeLatestServices,
|
||||||
fontSize: 16,
|
style: TextStyle(
|
||||||
color: context.primaryText,
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: context.primaryText,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -120,31 +125,46 @@ class _LatestServicesCardContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final service = state.services[index];
|
final service = state.services[index];
|
||||||
return Padding(
|
return InkWell(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
onTap: () =>
|
||||||
child: Row(
|
context.push('/service-form', extra: service),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
Expanded(
|
child: Row(
|
||||||
child: Text(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
service.number,
|
children: [
|
||||||
style: TextStyle(
|
Expanded(
|
||||||
fontWeight: FontWeight.w600,
|
flex: 5,
|
||||||
color: context.primaryText,
|
child: Text(
|
||||||
|
service.customerDisplayName ??
|
||||||
|
'Cliente sconosciuto',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: context.primaryText,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
// Se hai formattato la data, stampala qui (es. 12/04/2026)
|
flex: 5,
|
||||||
Text(
|
child: Text(
|
||||||
"${service.createdAt?.day}/${service.createdAt?.month}",
|
service.number,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: context.secondaryText,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 12,
|
color: context.primaryText,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
"${service.createdAt?.day}/${service.createdAt?.month}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.secondaryText,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/core/theme/theme.dart';
|
import 'package:flux/core/theme/theme.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
|
import 'package:flux/features/home/latest_store_services/ui/latest_store_services_card.dart';
|
||||||
import 'package:flux/features/home/ui/quick_actions_widget.dart';
|
import 'package:flux/features/home/ui/quick_actions_widget.dart';
|
||||||
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -75,12 +76,8 @@ class HomeScreen extends StatelessWidget {
|
|||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
context: context,
|
context: context,
|
||||||
),
|
),
|
||||||
_buildDashboardWidget(
|
LatestStoreServicesCard(),
|
||||||
title: context.l10n.homeLatestServices,
|
|
||||||
icon: Icons.design_services_outlined,
|
|
||||||
color: Colors.blue,
|
|
||||||
context: context,
|
|
||||||
),
|
|
||||||
_buildDashboardWidget(
|
_buildDashboardWidget(
|
||||||
title: context.l10n.homeLatestServiceTickets,
|
title: context.l10n.homeLatestServiceTickets,
|
||||||
icon: Icons.support_agent_outlined,
|
icon: Icons.support_agent_outlined,
|
||||||
|
|||||||
13
lib/l10n/app_en.arb
Normal file
13
lib/l10n/app_en.arb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "en",
|
||||||
|
"welcomeBack": "Welcome back, {name}! 👋",
|
||||||
|
"latestServices": "Latest Services",
|
||||||
|
"masterData": "Master Data",
|
||||||
|
"settings": "Settings",
|
||||||
|
"newService": "Service",
|
||||||
|
"expiring_contracts": "Expiring Contracts",
|
||||||
|
"sticky_notes": "Sticky Notes",
|
||||||
|
"my_tasks": "My Tasks",
|
||||||
|
"latest_service_tickets": "Latest service tickets"
|
||||||
|
|
||||||
|
}
|
||||||
57
lib/l10n/app_localizations_en.dart
Normal file
57
lib/l10n/app_localizations_en.dart
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// ignore: unused_import
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// The translations for English (`en`).
|
||||||
|
class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeExpiringContracts => 'Contratti in scadenza';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeLatestServiceTickets => 'Ultime assistenze';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeLatestServices => 'Ultimi Servizi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeMasterData => 'Anagrafiche';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeMyTasks => 'Mie Attività';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeNewService => 'Servizio';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeSettings => 'Impostazioni';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeStickyNotes => 'Sticky Notes';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String homeWelcomeBack(String name) {
|
||||||
|
return 'Bentornato, $name! 👋';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeNoStoreFound => 'Nessun negozio trovato';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeNewServiceTicket => 'Nuova assistenza';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeNewNote => 'Nota';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeNewTask => 'Attività';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get commonComingSoon => 'Coming soon';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get commonDashboard => 'Panoramica';
|
||||||
|
}
|
||||||
1
lib/l10n/intl_en.arb
Normal file
1
lib/l10n/intl_en.arb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
||||||
|
import 'package:flux/features/home/latest_store_services/bloc/latest_store_services_bloc.dart';
|
||||||
import 'package:flux/l10n/app_localizations.dart';
|
import 'package:flux/l10n/app_localizations.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user