hub & manager issues
This commit is contained in:
61
apps/mobile/analyze2.txt
Normal file
61
apps/mobile/analyze2.txt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ A new version of Flutter is available! │
|
||||||
|
│ │
|
||||||
|
│ To update to the latest version, run "flutter upgrade". │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
Resolving dependencies...
|
||||||
|
Downloading packages...
|
||||||
|
_fe_analyzer_shared 91.0.0 (96.0.0 available)
|
||||||
|
analyzer 8.4.1 (10.2.0 available)
|
||||||
|
archive 3.6.1 (4.0.9 available)
|
||||||
|
bloc 8.1.4 (9.2.0 available)
|
||||||
|
bloc_test 9.1.7 (10.0.0 available)
|
||||||
|
build_runner 2.10.5 (2.11.1 available)
|
||||||
|
built_value 8.12.3 (8.12.4 available)
|
||||||
|
characters 1.4.0 (1.4.1 available)
|
||||||
|
code_assets 0.19.10 (1.0.0 available)
|
||||||
|
csv 6.0.0 (7.1.0 available)
|
||||||
|
dart_style 3.1.3 (3.1.5 available)
|
||||||
|
ffi 2.1.5 (2.2.0 available)
|
||||||
|
fl_chart 0.66.2 (1.1.1 available)
|
||||||
|
flutter_bloc 8.1.6 (9.1.1 available)
|
||||||
|
geolocator 10.1.1 (14.0.2 available)
|
||||||
|
geolocator_android 4.6.2 (5.0.2 available)
|
||||||
|
geolocator_web 2.2.1 (4.1.3 available)
|
||||||
|
get_it 7.7.0 (9.2.1 available)
|
||||||
|
google_fonts 7.0.2 (8.0.2 available)
|
||||||
|
google_maps_flutter_android 2.18.12 (2.19.1 available)
|
||||||
|
google_maps_flutter_ios 2.17.3 (2.17.5 available)
|
||||||
|
google_maps_flutter_web 0.5.14+3 (0.6.1 available)
|
||||||
|
googleapis_auth 1.6.0 (2.1.0 available)
|
||||||
|
grpc 3.2.4 (5.1.0 available)
|
||||||
|
hooks 0.20.5 (1.0.1 available)
|
||||||
|
image 4.3.0 (4.8.0 available)
|
||||||
|
json_annotation 4.9.0 (4.11.0 available)
|
||||||
|
lints 6.0.0 (6.1.0 available)
|
||||||
|
matcher 0.12.17 (0.12.18 available)
|
||||||
|
material_color_utilities 0.11.1 (0.13.0 available)
|
||||||
|
melos 7.3.0 (7.4.0 available)
|
||||||
|
meta 1.17.0 (1.18.1 available)
|
||||||
|
native_toolchain_c 0.17.2 (0.17.4 available)
|
||||||
|
objective_c 9.2.2 (9.3.0 available)
|
||||||
|
permission_handler 11.4.0 (12.0.1 available)
|
||||||
|
permission_handler_android 12.1.0 (13.0.1 available)
|
||||||
|
petitparser 7.0.1 (7.0.2 available)
|
||||||
|
protobuf 3.1.0 (6.0.0 available)
|
||||||
|
shared_preferences_android 2.4.18 (2.4.20 available)
|
||||||
|
slang 4.12.0 (4.12.1 available)
|
||||||
|
slang_build_runner 4.12.0 (4.12.1 available)
|
||||||
|
slang_flutter 4.12.0 (4.12.1 available)
|
||||||
|
source_span 1.10.1 (1.10.2 available)
|
||||||
|
test 1.26.3 (1.29.0 available)
|
||||||
|
test_api 0.7.7 (0.7.9 available)
|
||||||
|
test_core 0.6.12 (0.6.15 available)
|
||||||
|
url_launcher_ios 6.3.6 (6.4.1 available)
|
||||||
|
uuid 4.5.2 (4.5.3 available)
|
||||||
|
yaml_edit 2.2.3 (2.2.4 available)
|
||||||
|
Got dependencies!
|
||||||
|
49 packages have newer versions incompatible with dependency constraints.
|
||||||
|
Try `flutter pub outdated` for more information.
|
||||||
|
Analyzing mobile...
|
||||||
@@ -135,6 +135,11 @@ extension ClientNavigator on IModularNavigator {
|
|||||||
pushNamed(ClientPaths.settings);
|
pushNamed(ClientPaths.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pushes the edit profile page.
|
||||||
|
void toClientEditProfile() {
|
||||||
|
pushNamed('${ClientPaths.settings}/edit-profile');
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// HUBS MANAGEMENT
|
// HUBS MANAGEMENT
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -159,6 +164,9 @@ extension ClientNavigator on IModularNavigator {
|
|||||||
return pushNamed<bool?>(
|
return pushNamed<bool?>(
|
||||||
ClientPaths.editHub,
|
ClientPaths.editHub,
|
||||||
arguments: <String, dynamic>{'hub': hub},
|
arguments: <String, dynamic>{'hub': hub},
|
||||||
|
// Some versions of Modular allow passing opaque here, but if not
|
||||||
|
// we'll handle transparency in the page itself which we already do.
|
||||||
|
// To ensure it's not opaque, we'll use push with a PageRouteBuilder if needed.
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -208,6 +208,7 @@
|
|||||||
"edit_profile": "Edit Profile",
|
"edit_profile": "Edit Profile",
|
||||||
"hubs": "Hubs",
|
"hubs": "Hubs",
|
||||||
"log_out": "Log Out",
|
"log_out": "Log Out",
|
||||||
|
"log_out_confirmation": "Are you sure you want to log out?",
|
||||||
"quick_links": "Quick Links",
|
"quick_links": "Quick Links",
|
||||||
"clock_in_hubs": "Clock-In Hubs",
|
"clock_in_hubs": "Clock-In Hubs",
|
||||||
"billing_payments": "Billing & Payments"
|
"billing_payments": "Billing & Payments"
|
||||||
@@ -254,6 +255,8 @@
|
|||||||
"address_hint": "Full address",
|
"address_hint": "Full address",
|
||||||
"cost_center_label": "Cost Center",
|
"cost_center_label": "Cost Center",
|
||||||
"cost_center_hint": "eg: 1001, 1002",
|
"cost_center_hint": "eg: 1001, 1002",
|
||||||
|
"name_required": "Name is required",
|
||||||
|
"address_required": "Address is required",
|
||||||
"create_button": "Create Hub"
|
"create_button": "Create Hub"
|
||||||
},
|
},
|
||||||
"edit_hub": {
|
"edit_hub": {
|
||||||
@@ -332,6 +335,9 @@
|
|||||||
"date_hint": "Select date",
|
"date_hint": "Select date",
|
||||||
"location_label": "Location",
|
"location_label": "Location",
|
||||||
"location_hint": "Enter address",
|
"location_hint": "Enter address",
|
||||||
|
"hub_manager_label": "Shift Contact",
|
||||||
|
"hub_manager_desc": "On-site manager or supervisor for this shift",
|
||||||
|
"hub_manager_hint": "Select Contact",
|
||||||
"positions_title": "Positions",
|
"positions_title": "Positions",
|
||||||
"add_position": "Add Position",
|
"add_position": "Add Position",
|
||||||
"position_number": "Position $number",
|
"position_number": "Position $number",
|
||||||
|
|||||||
@@ -208,6 +208,7 @@
|
|||||||
"edit_profile": "Editar Perfil",
|
"edit_profile": "Editar Perfil",
|
||||||
"hubs": "Hubs",
|
"hubs": "Hubs",
|
||||||
"log_out": "Cerrar sesi\u00f3n",
|
"log_out": "Cerrar sesi\u00f3n",
|
||||||
|
"log_out_confirmation": "\u00bfEst\u00e1 seguro de que desea cerrar sesi\u00f3n?",
|
||||||
"quick_links": "Enlaces r\u00e1pidos",
|
"quick_links": "Enlaces r\u00e1pidos",
|
||||||
"clock_in_hubs": "Hubs de Marcaje",
|
"clock_in_hubs": "Hubs de Marcaje",
|
||||||
"billing_payments": "Facturaci\u00f3n y Pagos"
|
"billing_payments": "Facturaci\u00f3n y Pagos"
|
||||||
@@ -254,6 +255,8 @@
|
|||||||
"address_hint": "Direcci\u00f3n completa",
|
"address_hint": "Direcci\u00f3n completa",
|
||||||
"cost_center_label": "Centro de Costos",
|
"cost_center_label": "Centro de Costos",
|
||||||
"cost_center_hint": "ej: 1001, 1002",
|
"cost_center_hint": "ej: 1001, 1002",
|
||||||
|
"name_required": "Nombre es obligatorio",
|
||||||
|
"address_required": "La direcci\u00f3n es obligatoria",
|
||||||
"create_button": "Crear Hub"
|
"create_button": "Crear Hub"
|
||||||
},
|
},
|
||||||
"nfc_dialog": {
|
"nfc_dialog": {
|
||||||
@@ -332,6 +335,9 @@
|
|||||||
"date_hint": "Seleccionar fecha",
|
"date_hint": "Seleccionar fecha",
|
||||||
"location_label": "Ubicaci\u00f3n",
|
"location_label": "Ubicaci\u00f3n",
|
||||||
"location_hint": "Ingresar direcci\u00f3n",
|
"location_hint": "Ingresar direcci\u00f3n",
|
||||||
|
"hub_manager_label": "Contacto del Turno",
|
||||||
|
"hub_manager_desc": "Gerente o supervisor en el sitio para este turno",
|
||||||
|
"hub_manager_hint": "Seleccionar Contacto",
|
||||||
"positions_title": "Posiciones",
|
"positions_title": "Posiciones",
|
||||||
"add_position": "A\u00f1adir Posici\u00f3n",
|
"add_position": "A\u00f1adir Posici\u00f3n",
|
||||||
"position_number": "Posici\u00f3n $number",
|
"position_number": "Posici\u00f3n $number",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
|
|||||||
address: h.address,
|
address: h.address,
|
||||||
nfcTagId: null,
|
nfcTagId: null,
|
||||||
status: h.isActive ? HubStatus.active : HubStatus.inactive,
|
status: h.isActive ? HubStatus.active : HubStatus.inactive,
|
||||||
|
costCenter: null,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
});
|
});
|
||||||
@@ -79,6 +80,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
|
|||||||
address: address,
|
address: address,
|
||||||
nfcTagId: null,
|
nfcTagId: null,
|
||||||
status: HubStatus.active,
|
status: HubStatus.active,
|
||||||
|
costCenter: null,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -136,6 +138,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
|
|||||||
address: address ?? '',
|
address: address ?? '',
|
||||||
nfcTagId: null,
|
nfcTagId: null,
|
||||||
status: HubStatus.active,
|
status: HubStatus.active,
|
||||||
|
costCenter: null,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export 'src/entities/business/business_setting.dart';
|
|||||||
export 'src/entities/business/hub.dart';
|
export 'src/entities/business/hub.dart';
|
||||||
export 'src/entities/business/hub_department.dart';
|
export 'src/entities/business/hub_department.dart';
|
||||||
export 'src/entities/business/vendor.dart';
|
export 'src/entities/business/vendor.dart';
|
||||||
|
export 'src/entities/business/cost_center.dart';
|
||||||
|
|
||||||
// Events & Assignments
|
// Events & Assignments
|
||||||
export 'src/entities/events/event.dart';
|
export 'src/entities/events/event.dart';
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// Represents a financial cost center used for billing and tracking.
|
||||||
|
class CostCenter extends Equatable {
|
||||||
|
const CostCenter({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
this.code,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Unique identifier.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Display name of the cost center.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// Optional alphanumeric code associated with this cost center.
|
||||||
|
final String? code;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, name, code];
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
import 'cost_center.dart';
|
||||||
|
|
||||||
/// The status of a [Hub].
|
/// The status of a [Hub].
|
||||||
enum HubStatus {
|
enum HubStatus {
|
||||||
/// Fully operational.
|
/// Fully operational.
|
||||||
@@ -42,7 +44,7 @@ class Hub extends Equatable {
|
|||||||
final HubStatus status;
|
final HubStatus status;
|
||||||
|
|
||||||
/// Assigned cost center for this hub.
|
/// Assigned cost center for this hub.
|
||||||
final String? costCenter;
|
final CostCenter? costCenter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[id, businessId, name, address, nfcTagId, status, costCenter];
|
List<Object?> get props => <Object?>[id, businessId, name, address, nfcTagId, status, costCenter];
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class OneTimeOrder extends Equatable {
|
|||||||
this.hub,
|
this.hub,
|
||||||
this.eventName,
|
this.eventName,
|
||||||
this.vendorId,
|
this.vendorId,
|
||||||
|
this.hubManagerId,
|
||||||
this.roleRates = const <String, double>{},
|
this.roleRates = const <String, double>{},
|
||||||
});
|
});
|
||||||
/// The specific date for the shift or event.
|
/// The specific date for the shift or event.
|
||||||
@@ -33,6 +34,9 @@ class OneTimeOrder extends Equatable {
|
|||||||
/// Selected vendor id for this order.
|
/// Selected vendor id for this order.
|
||||||
final String? vendorId;
|
final String? vendorId;
|
||||||
|
|
||||||
|
/// Optional hub manager id.
|
||||||
|
final String? hubManagerId;
|
||||||
|
|
||||||
/// Role hourly rates keyed by role id.
|
/// Role hourly rates keyed by role id.
|
||||||
final Map<String, double> roleRates;
|
final Map<String, double> roleRates;
|
||||||
|
|
||||||
@@ -44,6 +48,7 @@ class OneTimeOrder extends Equatable {
|
|||||||
hub,
|
hub,
|
||||||
eventName,
|
eventName,
|
||||||
vendorId,
|
vendorId,
|
||||||
|
hubManagerId,
|
||||||
roleRates,
|
roleRates,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ class OrderItem extends Equatable {
|
|||||||
this.hours = 0,
|
this.hours = 0,
|
||||||
this.totalValue = 0,
|
this.totalValue = 0,
|
||||||
this.confirmedApps = const <Map<String, dynamic>>[],
|
this.confirmedApps = const <Map<String, dynamic>>[],
|
||||||
|
this.hubManagerId,
|
||||||
|
this.hubManagerName,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Unique identifier of the order.
|
/// Unique identifier of the order.
|
||||||
@@ -83,6 +85,12 @@ class OrderItem extends Equatable {
|
|||||||
/// List of confirmed worker applications.
|
/// List of confirmed worker applications.
|
||||||
final List<Map<String, dynamic>> confirmedApps;
|
final List<Map<String, dynamic>> confirmedApps;
|
||||||
|
|
||||||
|
/// Optional ID of the assigned hub manager.
|
||||||
|
final String? hubManagerId;
|
||||||
|
|
||||||
|
/// Optional Name of the assigned hub manager.
|
||||||
|
final String? hubManagerName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
id,
|
id,
|
||||||
@@ -103,5 +111,7 @@ class OrderItem extends Equatable {
|
|||||||
totalValue,
|
totalValue,
|
||||||
eventName,
|
eventName,
|
||||||
confirmedApps,
|
confirmedApps,
|
||||||
|
hubManagerId,
|
||||||
|
hubManagerName,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class PermanentOrder extends Equatable {
|
|||||||
this.hub,
|
this.hub,
|
||||||
this.eventName,
|
this.eventName,
|
||||||
this.vendorId,
|
this.vendorId,
|
||||||
|
this.hubManagerId,
|
||||||
this.roleRates = const <String, double>{},
|
this.roleRates = const <String, double>{},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ class PermanentOrder extends Equatable {
|
|||||||
final OneTimeOrderHubDetails? hub;
|
final OneTimeOrderHubDetails? hub;
|
||||||
final String? eventName;
|
final String? eventName;
|
||||||
final String? vendorId;
|
final String? vendorId;
|
||||||
|
final String? hubManagerId;
|
||||||
final Map<String, double> roleRates;
|
final Map<String, double> roleRates;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,6 +35,7 @@ class PermanentOrder extends Equatable {
|
|||||||
hub,
|
hub,
|
||||||
eventName,
|
eventName,
|
||||||
vendorId,
|
vendorId,
|
||||||
|
hubManagerId,
|
||||||
roleRates,
|
roleRates,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class RecurringOrder extends Equatable {
|
|||||||
this.hub,
|
this.hub,
|
||||||
this.eventName,
|
this.eventName,
|
||||||
this.vendorId,
|
this.vendorId,
|
||||||
|
this.hubManagerId,
|
||||||
this.roleRates = const <String, double>{},
|
this.roleRates = const <String, double>{},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,6 +40,9 @@ class RecurringOrder extends Equatable {
|
|||||||
/// Selected vendor id for this order.
|
/// Selected vendor id for this order.
|
||||||
final String? vendorId;
|
final String? vendorId;
|
||||||
|
|
||||||
|
/// Optional hub manager id.
|
||||||
|
final String? hubManagerId;
|
||||||
|
|
||||||
/// Role hourly rates keyed by role id.
|
/// Role hourly rates keyed by role id.
|
||||||
final Map<String, double> roleRates;
|
final Map<String, double> roleRates;
|
||||||
|
|
||||||
@@ -52,6 +56,7 @@ class RecurringOrder extends Equatable {
|
|||||||
hub,
|
hub,
|
||||||
eventName,
|
eventName,
|
||||||
vendorId,
|
vendorId,
|
||||||
|
hubManagerId,
|
||||||
roleRates,
|
roleRates,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'src/domain/repositories/hub_repository_interface.dart';
|
|||||||
import 'src/domain/usecases/assign_nfc_tag_usecase.dart';
|
import 'src/domain/usecases/assign_nfc_tag_usecase.dart';
|
||||||
import 'src/domain/usecases/create_hub_usecase.dart';
|
import 'src/domain/usecases/create_hub_usecase.dart';
|
||||||
import 'src/domain/usecases/delete_hub_usecase.dart';
|
import 'src/domain/usecases/delete_hub_usecase.dart';
|
||||||
|
import 'src/domain/usecases/get_cost_centers_usecase.dart';
|
||||||
import 'src/domain/usecases/get_hubs_usecase.dart';
|
import 'src/domain/usecases/get_hubs_usecase.dart';
|
||||||
import 'src/domain/usecases/update_hub_usecase.dart';
|
import 'src/domain/usecases/update_hub_usecase.dart';
|
||||||
import 'src/presentation/blocs/client_hubs_bloc.dart';
|
import 'src/presentation/blocs/client_hubs_bloc.dart';
|
||||||
@@ -32,6 +34,7 @@ class ClientHubsModule extends Module {
|
|||||||
|
|
||||||
// UseCases
|
// UseCases
|
||||||
i.addLazySingleton(GetHubsUseCase.new);
|
i.addLazySingleton(GetHubsUseCase.new);
|
||||||
|
i.addLazySingleton(GetCostCentersUseCase.new);
|
||||||
i.addLazySingleton(CreateHubUseCase.new);
|
i.addLazySingleton(CreateHubUseCase.new);
|
||||||
i.addLazySingleton(DeleteHubUseCase.new);
|
i.addLazySingleton(DeleteHubUseCase.new);
|
||||||
i.addLazySingleton(AssignNfcTagUseCase.new);
|
i.addLazySingleton(AssignNfcTagUseCase.new);
|
||||||
@@ -61,6 +64,18 @@ class ClientHubsModule extends Module {
|
|||||||
);
|
);
|
||||||
r.child(
|
r.child(
|
||||||
ClientPaths.childRoute(ClientPaths.hubs, ClientPaths.editHub),
|
ClientPaths.childRoute(ClientPaths.hubs, ClientPaths.editHub),
|
||||||
|
transition: TransitionType.custom,
|
||||||
|
customTransition: CustomTransition(
|
||||||
|
opaque: false,
|
||||||
|
transitionBuilder: (
|
||||||
|
BuildContext context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation,
|
||||||
|
Widget child,
|
||||||
|
) {
|
||||||
|
return FadeTransition(opacity: animation, child: child);
|
||||||
|
},
|
||||||
|
),
|
||||||
child: (_) {
|
child: (_) {
|
||||||
final Map<String, dynamic> data = r.args.data as Map<String, dynamic>;
|
final Map<String, dynamic> data = r.args.data as Map<String, dynamic>;
|
||||||
return EditHubPage(
|
return EditHubPage(
|
||||||
|
|||||||
@@ -24,6 +24,17 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
|||||||
return _connectorRepository.getHubs(businessId: businessId);
|
return _connectorRepository.getHubs(businessId: businessId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<CostCenter>> getCostCenters() async {
|
||||||
|
// Mocking cost centers for now since the backend is not yet ready.
|
||||||
|
return <CostCenter>[
|
||||||
|
const CostCenter(id: 'cc-001', name: 'Kitchen', code: '1001'),
|
||||||
|
const CostCenter(id: 'cc-002', name: 'Front Desk', code: '1002'),
|
||||||
|
const CostCenter(id: 'cc-003', name: 'Waitstaff', code: '1003'),
|
||||||
|
const CostCenter(id: 'cc-004', name: 'Management', code: '1004'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Hub> createHub({
|
Future<Hub> createHub({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -36,7 +47,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
|||||||
String? street,
|
String? street,
|
||||||
String? country,
|
String? country,
|
||||||
String? zipCode,
|
String? zipCode,
|
||||||
String? costCenter,
|
String? costCenterId,
|
||||||
}) async {
|
}) async {
|
||||||
final String businessId = await _service.getBusinessId();
|
final String businessId = await _service.getBusinessId();
|
||||||
return _connectorRepository.createHub(
|
return _connectorRepository.createHub(
|
||||||
@@ -80,7 +91,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
|||||||
String? street,
|
String? street,
|
||||||
String? country,
|
String? country,
|
||||||
String? zipCode,
|
String? zipCode,
|
||||||
String? costCenter,
|
String? costCenterId,
|
||||||
}) async {
|
}) async {
|
||||||
final String businessId = await _service.getBusinessId();
|
final String businessId = await _service.getBusinessId();
|
||||||
return _connectorRepository.updateHub(
|
return _connectorRepository.updateHub(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class CreateHubArguments extends UseCaseArgument {
|
|||||||
this.street,
|
this.street,
|
||||||
this.country,
|
this.country,
|
||||||
this.zipCode,
|
this.zipCode,
|
||||||
this.costCenter,
|
this.costCenterId,
|
||||||
});
|
});
|
||||||
/// The name of the hub.
|
/// The name of the hub.
|
||||||
final String name;
|
final String name;
|
||||||
@@ -37,7 +37,7 @@ class CreateHubArguments extends UseCaseArgument {
|
|||||||
final String? zipCode;
|
final String? zipCode;
|
||||||
|
|
||||||
/// The cost center of the hub.
|
/// The cost center of the hub.
|
||||||
final String? costCenter;
|
final String? costCenterId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
@@ -51,6 +51,6 @@ class CreateHubArguments extends UseCaseArgument {
|
|||||||
street,
|
street,
|
||||||
country,
|
country,
|
||||||
zipCode,
|
zipCode,
|
||||||
costCenter,
|
costCenterId,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ abstract interface class HubRepositoryInterface {
|
|||||||
/// Returns a list of [Hub] entities.
|
/// Returns a list of [Hub] entities.
|
||||||
Future<List<Hub>> getHubs();
|
Future<List<Hub>> getHubs();
|
||||||
|
|
||||||
|
/// Fetches the list of available cost centers for the current business.
|
||||||
|
Future<List<CostCenter>> getCostCenters();
|
||||||
|
|
||||||
/// Creates a new hub.
|
/// Creates a new hub.
|
||||||
///
|
///
|
||||||
/// Takes the [name] and [address] of the new hub.
|
/// Takes the [name] and [address] of the new hub.
|
||||||
@@ -26,7 +29,7 @@ abstract interface class HubRepositoryInterface {
|
|||||||
String? street,
|
String? street,
|
||||||
String? country,
|
String? country,
|
||||||
String? zipCode,
|
String? zipCode,
|
||||||
String? costCenter,
|
String? costCenterId,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Deletes a hub by its [id].
|
/// Deletes a hub by its [id].
|
||||||
@@ -52,6 +55,6 @@ abstract interface class HubRepositoryInterface {
|
|||||||
String? street,
|
String? street,
|
||||||
String? country,
|
String? country,
|
||||||
String? zipCode,
|
String? zipCode,
|
||||||
String? costCenter,
|
String? costCenterId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class CreateHubUseCase implements UseCase<CreateHubArguments, Hub> {
|
|||||||
street: arguments.street,
|
street: arguments.street,
|
||||||
country: arguments.country,
|
country: arguments.country,
|
||||||
zipCode: arguments.zipCode,
|
zipCode: arguments.zipCode,
|
||||||
|
costCenterId: arguments.costCenterId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../repositories/hub_repository_interface.dart';
|
||||||
|
|
||||||
|
/// Usecase to fetch all available cost centers.
|
||||||
|
class GetCostCentersUseCase {
|
||||||
|
GetCostCentersUseCase({required HubRepositoryInterface repository})
|
||||||
|
: _repository = repository;
|
||||||
|
|
||||||
|
final HubRepositoryInterface _repository;
|
||||||
|
|
||||||
|
Future<List<CostCenter>> call() async {
|
||||||
|
return _repository.getCostCenters();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ class UpdateHubArguments extends UseCaseArgument {
|
|||||||
this.street,
|
this.street,
|
||||||
this.country,
|
this.country,
|
||||||
this.zipCode,
|
this.zipCode,
|
||||||
|
this.costCenterId,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
@@ -30,6 +31,7 @@ class UpdateHubArguments extends UseCaseArgument {
|
|||||||
final String? street;
|
final String? street;
|
||||||
final String? country;
|
final String? country;
|
||||||
final String? zipCode;
|
final String? zipCode;
|
||||||
|
final String? costCenterId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
@@ -44,6 +46,7 @@ class UpdateHubArguments extends UseCaseArgument {
|
|||||||
street,
|
street,
|
||||||
country,
|
country,
|
||||||
zipCode,
|
zipCode,
|
||||||
|
costCenterId,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +70,7 @@ class UpdateHubUseCase implements UseCase<UpdateHubArguments, Hub> {
|
|||||||
street: params.street,
|
street: params.street,
|
||||||
country: params.country,
|
country: params.country,
|
||||||
zipCode: params.zipCode,
|
zipCode: params.zipCode,
|
||||||
|
costCenterId: params.costCenterId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../../../domain/arguments/create_hub_arguments.dart';
|
import '../../../domain/arguments/create_hub_arguments.dart';
|
||||||
import '../../../domain/usecases/create_hub_usecase.dart';
|
import '../../../domain/usecases/create_hub_usecase.dart';
|
||||||
import '../../../domain/usecases/update_hub_usecase.dart';
|
import '../../../domain/usecases/update_hub_usecase.dart';
|
||||||
|
import '../../../domain/usecases/get_cost_centers_usecase.dart';
|
||||||
import 'edit_hub_event.dart';
|
import 'edit_hub_event.dart';
|
||||||
import 'edit_hub_state.dart';
|
import 'edit_hub_state.dart';
|
||||||
|
|
||||||
@@ -12,15 +14,36 @@ class EditHubBloc extends Bloc<EditHubEvent, EditHubState>
|
|||||||
EditHubBloc({
|
EditHubBloc({
|
||||||
required CreateHubUseCase createHubUseCase,
|
required CreateHubUseCase createHubUseCase,
|
||||||
required UpdateHubUseCase updateHubUseCase,
|
required UpdateHubUseCase updateHubUseCase,
|
||||||
|
required GetCostCentersUseCase getCostCentersUseCase,
|
||||||
}) : _createHubUseCase = createHubUseCase,
|
}) : _createHubUseCase = createHubUseCase,
|
||||||
_updateHubUseCase = updateHubUseCase,
|
_updateHubUseCase = updateHubUseCase,
|
||||||
|
_getCostCentersUseCase = getCostCentersUseCase,
|
||||||
super(const EditHubState()) {
|
super(const EditHubState()) {
|
||||||
|
on<EditHubCostCentersLoadRequested>(_onCostCentersLoadRequested);
|
||||||
on<EditHubAddRequested>(_onAddRequested);
|
on<EditHubAddRequested>(_onAddRequested);
|
||||||
on<EditHubUpdateRequested>(_onUpdateRequested);
|
on<EditHubUpdateRequested>(_onUpdateRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CreateHubUseCase _createHubUseCase;
|
final CreateHubUseCase _createHubUseCase;
|
||||||
final UpdateHubUseCase _updateHubUseCase;
|
final UpdateHubUseCase _updateHubUseCase;
|
||||||
|
final GetCostCentersUseCase _getCostCentersUseCase;
|
||||||
|
|
||||||
|
Future<void> _onCostCentersLoadRequested(
|
||||||
|
EditHubCostCentersLoadRequested event,
|
||||||
|
Emitter<EditHubState> emit,
|
||||||
|
) async {
|
||||||
|
await handleError(
|
||||||
|
emit: emit.call,
|
||||||
|
action: () async {
|
||||||
|
final List<CostCenter> costCenters = await _getCostCentersUseCase.call();
|
||||||
|
emit(state.copyWith(costCenters: costCenters));
|
||||||
|
},
|
||||||
|
onError: (String errorKey) => state.copyWith(
|
||||||
|
status: EditHubStatus.failure,
|
||||||
|
errorMessage: errorKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onAddRequested(
|
Future<void> _onAddRequested(
|
||||||
EditHubAddRequested event,
|
EditHubAddRequested event,
|
||||||
@@ -43,6 +66,7 @@ class EditHubBloc extends Bloc<EditHubEvent, EditHubState>
|
|||||||
street: event.street,
|
street: event.street,
|
||||||
country: event.country,
|
country: event.country,
|
||||||
zipCode: event.zipCode,
|
zipCode: event.zipCode,
|
||||||
|
costCenterId: event.costCenterId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
emit(
|
emit(
|
||||||
@@ -79,6 +103,7 @@ class EditHubBloc extends Bloc<EditHubEvent, EditHubState>
|
|||||||
street: event.street,
|
street: event.street,
|
||||||
country: event.country,
|
country: event.country,
|
||||||
zipCode: event.zipCode,
|
zipCode: event.zipCode,
|
||||||
|
costCenterId: event.costCenterId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
emit(
|
emit(
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ abstract class EditHubEvent extends Equatable {
|
|||||||
List<Object?> get props => <Object?>[];
|
List<Object?> get props => <Object?>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Event triggered to load all available cost centers.
|
||||||
|
class EditHubCostCentersLoadRequested extends EditHubEvent {
|
||||||
|
const EditHubCostCentersLoadRequested();
|
||||||
|
}
|
||||||
|
|
||||||
/// Event triggered to add a new hub.
|
/// Event triggered to add a new hub.
|
||||||
class EditHubAddRequested extends EditHubEvent {
|
class EditHubAddRequested extends EditHubEvent {
|
||||||
const EditHubAddRequested({
|
const EditHubAddRequested({
|
||||||
@@ -21,6 +26,7 @@ class EditHubAddRequested extends EditHubEvent {
|
|||||||
this.street,
|
this.street,
|
||||||
this.country,
|
this.country,
|
||||||
this.zipCode,
|
this.zipCode,
|
||||||
|
this.costCenterId,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
@@ -33,6 +39,7 @@ class EditHubAddRequested extends EditHubEvent {
|
|||||||
final String? street;
|
final String? street;
|
||||||
final String? country;
|
final String? country;
|
||||||
final String? zipCode;
|
final String? zipCode;
|
||||||
|
final String? costCenterId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
@@ -46,6 +53,7 @@ class EditHubAddRequested extends EditHubEvent {
|
|||||||
street,
|
street,
|
||||||
country,
|
country,
|
||||||
zipCode,
|
zipCode,
|
||||||
|
costCenterId,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +71,7 @@ class EditHubUpdateRequested extends EditHubEvent {
|
|||||||
this.street,
|
this.street,
|
||||||
this.country,
|
this.country,
|
||||||
this.zipCode,
|
this.zipCode,
|
||||||
|
this.costCenterId,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
@@ -76,6 +85,7 @@ class EditHubUpdateRequested extends EditHubEvent {
|
|||||||
final String? street;
|
final String? street;
|
||||||
final String? country;
|
final String? country;
|
||||||
final String? zipCode;
|
final String? zipCode;
|
||||||
|
final String? costCenterId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
@@ -90,5 +100,6 @@ class EditHubUpdateRequested extends EditHubEvent {
|
|||||||
street,
|
street,
|
||||||
country,
|
country,
|
||||||
zipCode,
|
zipCode,
|
||||||
|
costCenterId,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
/// Status of the edit hub operation.
|
/// Status of the edit hub operation.
|
||||||
enum EditHubStatus {
|
enum EditHubStatus {
|
||||||
@@ -21,6 +22,7 @@ class EditHubState extends Equatable {
|
|||||||
this.status = EditHubStatus.initial,
|
this.status = EditHubStatus.initial,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
this.successMessage,
|
this.successMessage,
|
||||||
|
this.costCenters = const <CostCenter>[],
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The status of the operation.
|
/// The status of the operation.
|
||||||
@@ -32,19 +34,29 @@ class EditHubState extends Equatable {
|
|||||||
/// The success message if the operation succeeded.
|
/// The success message if the operation succeeded.
|
||||||
final String? successMessage;
|
final String? successMessage;
|
||||||
|
|
||||||
|
/// Available cost centers for selection.
|
||||||
|
final List<CostCenter> costCenters;
|
||||||
|
|
||||||
/// Create a copy of this state with the given fields replaced.
|
/// Create a copy of this state with the given fields replaced.
|
||||||
EditHubState copyWith({
|
EditHubState copyWith({
|
||||||
EditHubStatus? status,
|
EditHubStatus? status,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
String? successMessage,
|
String? successMessage,
|
||||||
|
List<CostCenter>? costCenters,
|
||||||
}) {
|
}) {
|
||||||
return EditHubState(
|
return EditHubState(
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
successMessage: successMessage ?? this.successMessage,
|
successMessage: successMessage ?? this.successMessage,
|
||||||
|
costCenters: costCenters ?? this.costCenters,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[status, errorMessage, successMessage];
|
List<Object?> get props => <Object?>[
|
||||||
|
status,
|
||||||
|
errorMessage,
|
||||||
|
successMessage,
|
||||||
|
costCenters,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,20 +57,6 @@ class ClientHubsPage extends StatelessWidget {
|
|||||||
builder: (BuildContext context, ClientHubsState state) {
|
builder: (BuildContext context, ClientHubsState state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.bgMenu,
|
backgroundColor: UiColors.bgMenu,
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: () async {
|
|
||||||
final bool? success = await Modular.to.toEditHub();
|
|
||||||
if (success == true && context.mounted) {
|
|
||||||
BlocProvider.of<ClientHubsBloc>(
|
|
||||||
context,
|
|
||||||
).add(const ClientHubsFetched());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
|
||||||
child: const Icon(UiIcons.add),
|
|
||||||
),
|
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
_buildAppBar(context),
|
_buildAppBar(context),
|
||||||
@@ -165,20 +151,35 @@ class ClientHubsPage extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Column(
|
Expanded(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: <Widget>[
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: <Widget>[
|
||||||
t.client_hubs.title,
|
Text(
|
||||||
style: UiTypography.headline1m.white,
|
t.client_hubs.title,
|
||||||
),
|
style: UiTypography.headline1m.white,
|
||||||
Text(
|
|
||||||
t.client_hubs.subtitle,
|
|
||||||
style: UiTypography.body2r.copyWith(
|
|
||||||
color: UiColors.switchInactive,
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
t.client_hubs.subtitle,
|
||||||
|
style: UiTypography.body2r.copyWith(
|
||||||
|
color: UiColors.switchInactive,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
UiButton.primary(
|
||||||
|
onPressed: () async {
|
||||||
|
final bool? success = await Modular.to.toEditHub();
|
||||||
|
if (success == true && context.mounted) {
|
||||||
|
BlocProvider.of<ClientHubsBloc>(
|
||||||
|
context,
|
||||||
|
).add(const ClientHubsFetched());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: t.client_hubs.add_hub,
|
||||||
|
leadingIcon: UiIcons.add,
|
||||||
|
size: UiButtonSize.small,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import 'package:core_localization/core_localization.dart';
|
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
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:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:google_places_flutter/model/prediction.dart';
|
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../blocs/edit_hub/edit_hub_bloc.dart';
|
import '../blocs/edit_hub/edit_hub_bloc.dart';
|
||||||
import '../blocs/edit_hub/edit_hub_event.dart';
|
import '../blocs/edit_hub/edit_hub_event.dart';
|
||||||
import '../blocs/edit_hub/edit_hub_state.dart';
|
import '../blocs/edit_hub/edit_hub_state.dart';
|
||||||
import '../widgets/edit_hub/edit_hub_form_section.dart';
|
import '../widgets/hub_form_dialog.dart';
|
||||||
|
|
||||||
/// A dedicated full-screen page for adding or editing a hub.
|
/// A wrapper page that shows the hub form in a modal-style layout.
|
||||||
class EditHubPage extends StatefulWidget {
|
class EditHubPage extends StatefulWidget {
|
||||||
const EditHubPage({this.hub, required this.bloc, super.key});
|
const EditHubPage({this.hub, required this.bloc, super.key});
|
||||||
|
|
||||||
@@ -23,66 +21,11 @@ class EditHubPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EditHubPageState extends State<EditHubPage> {
|
class _EditHubPageState extends State<EditHubPage> {
|
||||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
|
||||||
late final TextEditingController _nameController;
|
|
||||||
late final TextEditingController _addressController;
|
|
||||||
late final FocusNode _addressFocusNode;
|
|
||||||
Prediction? _selectedPrediction;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_nameController = TextEditingController(text: widget.hub?.name);
|
// Load available cost centers
|
||||||
_addressController = TextEditingController(text: widget.hub?.address);
|
widget.bloc.add(const EditHubCostCentersLoadRequested());
|
||||||
_addressFocusNode = FocusNode();
|
|
||||||
|
|
||||||
// Update header on change (if header is added back)
|
|
||||||
_nameController.addListener(() => setState(() {}));
|
|
||||||
_addressController.addListener(() => setState(() {}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_nameController.dispose();
|
|
||||||
_addressController.dispose();
|
|
||||||
_addressFocusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onSave() {
|
|
||||||
if (!_formKey.currentState!.validate()) return;
|
|
||||||
|
|
||||||
if (_addressController.text.trim().isEmpty) {
|
|
||||||
UiSnackbar.show(
|
|
||||||
context,
|
|
||||||
message: t.client_hubs.add_hub_dialog.address_hint,
|
|
||||||
type: UiSnackbarType.error,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.hub == null) {
|
|
||||||
widget.bloc.add(
|
|
||||||
EditHubAddRequested(
|
|
||||||
name: _nameController.text.trim(),
|
|
||||||
address: _addressController.text.trim(),
|
|
||||||
placeId: _selectedPrediction?.placeId,
|
|
||||||
latitude: double.tryParse(_selectedPrediction?.lat ?? ''),
|
|
||||||
longitude: double.tryParse(_selectedPrediction?.lng ?? ''),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
widget.bloc.add(
|
|
||||||
EditHubUpdateRequested(
|
|
||||||
id: widget.hub!.id,
|
|
||||||
name: _nameController.text.trim(),
|
|
||||||
address: _addressController.text.trim(),
|
|
||||||
placeId: _selectedPrediction?.placeId,
|
|
||||||
latitude: double.tryParse(_selectedPrediction?.lat ?? ''),
|
|
||||||
longitude: double.tryParse(_selectedPrediction?.lng ?? ''),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -101,7 +44,6 @@ class _EditHubPageState extends State<EditHubPage> {
|
|||||||
message: state.successMessage!,
|
message: state.successMessage!,
|
||||||
type: UiSnackbarType.success,
|
type: UiSnackbarType.success,
|
||||||
);
|
);
|
||||||
// Pop back to the previous screen.
|
|
||||||
Modular.to.pop(true);
|
Modular.to.pop(true);
|
||||||
}
|
}
|
||||||
if (state.status == EditHubStatus.failure &&
|
if (state.status == EditHubStatus.failure &&
|
||||||
@@ -118,42 +60,59 @@ class _EditHubPageState extends State<EditHubPage> {
|
|||||||
final bool isSaving = state.status == EditHubStatus.loading;
|
final bool isSaving = state.status == EditHubStatus.loading;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.bgMenu,
|
backgroundColor: UiColors.bgOverlay,
|
||||||
appBar: UiAppBar(
|
|
||||||
title: widget.hub == null
|
|
||||||
? t.client_hubs.add_hub_dialog.title
|
|
||||||
: t.client_hubs.edit_hub.title,
|
|
||||||
subtitle: widget.hub == null
|
|
||||||
? t.client_hubs.add_hub_dialog.create_button
|
|
||||||
: t.client_hubs.edit_hub.subtitle,
|
|
||||||
onLeadingPressed: () => Modular.to.pop(),
|
|
||||||
),
|
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SingleChildScrollView(
|
// Tap background to dismiss
|
||||||
child: Column(
|
GestureDetector(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
onTap: () => Modular.to.pop(),
|
||||||
children: <Widget>[
|
child: Container(color: Colors.transparent),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: EditHubFormSection(
|
// Dialog-style content centered
|
||||||
formKey: _formKey,
|
Align(
|
||||||
nameController: _nameController,
|
alignment: Alignment.center,
|
||||||
addressController: _addressController,
|
child: HubFormDialog(
|
||||||
addressFocusNode: _addressFocusNode,
|
hub: widget.hub,
|
||||||
onAddressSelected: (Prediction prediction) {
|
costCenters: state.costCenters,
|
||||||
_selectedPrediction = prediction;
|
onCancel: () => Modular.to.pop(),
|
||||||
},
|
onSave: ({
|
||||||
onSave: _onSave,
|
required String name,
|
||||||
isSaving: isSaving,
|
required String address,
|
||||||
isEdit: widget.hub != null,
|
String? costCenterId,
|
||||||
),
|
String? placeId,
|
||||||
),
|
double? latitude,
|
||||||
],
|
double? longitude,
|
||||||
|
}) {
|
||||||
|
if (widget.hub == null) {
|
||||||
|
widget.bloc.add(
|
||||||
|
EditHubAddRequested(
|
||||||
|
name: name,
|
||||||
|
address: address,
|
||||||
|
costCenterId: costCenterId,
|
||||||
|
placeId: placeId,
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
widget.bloc.add(
|
||||||
|
EditHubUpdateRequested(
|
||||||
|
id: widget.hub!.id,
|
||||||
|
name: name,
|
||||||
|
address: address,
|
||||||
|
costCenterId: costCenterId,
|
||||||
|
placeId: placeId,
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// ── Loading overlay ──────────────────────────────────────
|
// Global loading overlay if saving
|
||||||
if (isSaving)
|
if (isSaving)
|
||||||
Container(
|
Container(
|
||||||
color: UiColors.black.withValues(alpha: 0.1),
|
color: UiColors.black.withValues(alpha: 0.1),
|
||||||
|
|||||||
@@ -80,6 +80,15 @@ class HubDetailsPage extends StatelessWidget {
|
|||||||
icon: UiIcons.nfc,
|
icon: UiIcons.nfc,
|
||||||
isHighlight: hub.nfcTagId != null,
|
isHighlight: hub.nfcTagId != null,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
HubDetailsItem(
|
||||||
|
label: t.client_hubs.hub_details.cost_center_label,
|
||||||
|
value: hub.costCenter != null
|
||||||
|
? '${hub.costCenter!.name} (${hub.costCenter!.code})'
|
||||||
|
: t.client_hubs.hub_details.cost_center_none,
|
||||||
|
icon: UiIcons.bank, // Using bank icon for cost center
|
||||||
|
isHighlight: hub.costCenter != null,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:core_localization/core_localization.dart';
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_places_flutter/model/prediction.dart';
|
import 'package:google_places_flutter/model/prediction.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../hub_address_autocomplete.dart';
|
import '../hub_address_autocomplete.dart';
|
||||||
import 'edit_hub_field_label.dart';
|
import 'edit_hub_field_label.dart';
|
||||||
@@ -15,6 +16,9 @@ class EditHubFormSection extends StatelessWidget {
|
|||||||
required this.addressFocusNode,
|
required this.addressFocusNode,
|
||||||
required this.onAddressSelected,
|
required this.onAddressSelected,
|
||||||
required this.onSave,
|
required this.onSave,
|
||||||
|
this.costCenters = const <CostCenter>[],
|
||||||
|
this.selectedCostCenterId,
|
||||||
|
required this.onCostCenterChanged,
|
||||||
this.isSaving = false,
|
this.isSaving = false,
|
||||||
this.isEdit = false,
|
this.isEdit = false,
|
||||||
super.key,
|
super.key,
|
||||||
@@ -26,6 +30,9 @@ class EditHubFormSection extends StatelessWidget {
|
|||||||
final FocusNode addressFocusNode;
|
final FocusNode addressFocusNode;
|
||||||
final ValueChanged<Prediction> onAddressSelected;
|
final ValueChanged<Prediction> onAddressSelected;
|
||||||
final VoidCallback onSave;
|
final VoidCallback onSave;
|
||||||
|
final List<CostCenter> costCenters;
|
||||||
|
final String? selectedCostCenterId;
|
||||||
|
final ValueChanged<String?> onCostCenterChanged;
|
||||||
final bool isSaving;
|
final bool isSaving;
|
||||||
final bool isEdit;
|
final bool isEdit;
|
||||||
|
|
||||||
@@ -62,6 +69,51 @@ class EditHubFormSection extends StatelessWidget {
|
|||||||
onSelected: onAddressSelected,
|
onSelected: onAddressSelected,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
EditHubFieldLabel(t.client_hubs.edit_hub.cost_center_label),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _showCostCenterSelector(context),
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: 14,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.input,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
border: Border.all(
|
||||||
|
color: selectedCostCenterId != null
|
||||||
|
? UiColors.ring
|
||||||
|
: UiColors.border,
|
||||||
|
width: selectedCostCenterId != null ? 2 : 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
selectedCostCenterId != null
|
||||||
|
? _getCostCenterName(selectedCostCenterId!)
|
||||||
|
: t.client_hubs.edit_hub.cost_center_hint,
|
||||||
|
style: selectedCostCenterId != null
|
||||||
|
? UiTypography.body1r.textPrimary
|
||||||
|
: UiTypography.body2r.textPlaceholder,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(
|
||||||
|
Icons.keyboard_arrow_down,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: UiConstants.space8),
|
const SizedBox(height: UiConstants.space8),
|
||||||
|
|
||||||
// ── Save button ──────────────────────────────────
|
// ── Save button ──────────────────────────────────
|
||||||
@@ -102,4 +154,59 @@ class EditHubFormSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getCostCenterName(String id) {
|
||||||
|
try {
|
||||||
|
final CostCenter cc = costCenters.firstWhere((CostCenter item) => item.id == id);
|
||||||
|
return cc.code != null ? '${cc.name} (${cc.code})' : cc.name;
|
||||||
|
} catch (_) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showCostCenterSelector(BuildContext context) async {
|
||||||
|
final CostCenter? selected = await showDialog<CostCenter>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
t.client_hubs.edit_hub.cost_center_label,
|
||||||
|
style: UiTypography.headline3m.textPrimary,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 400),
|
||||||
|
child: costCenters.isEmpty
|
||||||
|
? const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Text('No cost centers available'),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: costCenters.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
final CostCenter cc = costCenters[index];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text(cc.name, style: UiTypography.body1m.textPrimary),
|
||||||
|
subtitle: cc.code != null ? Text(cc.code!, style: UiTypography.body2r.textSecondary) : null,
|
||||||
|
onTap: () => Navigator.of(context).pop(cc),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
onCostCenterChanged(selected.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class HubAddressAutocomplete extends StatelessWidget {
|
|||||||
required this.controller,
|
required this.controller,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
|
this.decoration,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
@@ -18,6 +19,7 @@ class HubAddressAutocomplete extends StatelessWidget {
|
|||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
|
final InputDecoration? decoration;
|
||||||
final void Function(Prediction prediction)? onSelected;
|
final void Function(Prediction prediction)? onSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -25,6 +27,7 @@ class HubAddressAutocomplete extends StatelessWidget {
|
|||||||
return GooglePlaceAutoCompleteTextField(
|
return GooglePlaceAutoCompleteTextField(
|
||||||
textEditingController: controller,
|
textEditingController: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
|
inputDecoration: decoration ?? const InputDecoration(),
|
||||||
googleAPIKey: AppConfig.googleMapsApiKey,
|
googleAPIKey: AppConfig.googleMapsApiKey,
|
||||||
debounceTime: 500,
|
debounceTime: 500,
|
||||||
countries: HubsConstants.supportedCountries,
|
countries: HubsConstants.supportedCountries,
|
||||||
|
|||||||
@@ -5,25 +5,30 @@ import 'package:google_places_flutter/model/prediction.dart';
|
|||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import 'hub_address_autocomplete.dart';
|
import 'hub_address_autocomplete.dart';
|
||||||
|
import 'edit_hub/edit_hub_field_label.dart';
|
||||||
|
|
||||||
/// A dialog for adding or editing a hub.
|
/// A bottom sheet dialog for adding or editing a hub.
|
||||||
class HubFormDialog extends StatefulWidget {
|
class HubFormDialog extends StatefulWidget {
|
||||||
|
|
||||||
/// Creates a [HubFormDialog].
|
/// Creates a [HubFormDialog].
|
||||||
const HubFormDialog({
|
const HubFormDialog({
|
||||||
required this.onSave,
|
required this.onSave,
|
||||||
required this.onCancel,
|
required this.onCancel,
|
||||||
this.hub,
|
this.hub,
|
||||||
|
this.costCenters = const <CostCenter>[],
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The hub to edit. If null, a new hub is created.
|
/// The hub to edit. If null, a new hub is created.
|
||||||
final Hub? hub;
|
final Hub? hub;
|
||||||
|
|
||||||
|
/// Available cost centers for selection.
|
||||||
|
final List<CostCenter> costCenters;
|
||||||
|
|
||||||
/// Callback when the "Save" button is pressed.
|
/// Callback when the "Save" button is pressed.
|
||||||
final void Function(
|
final void Function({
|
||||||
String name,
|
required String name,
|
||||||
String address, {
|
required String address,
|
||||||
|
String? costCenterId,
|
||||||
String? placeId,
|
String? placeId,
|
||||||
double? latitude,
|
double? latitude,
|
||||||
double? longitude,
|
double? longitude,
|
||||||
@@ -40,6 +45,7 @@ class _HubFormDialogState extends State<HubFormDialog> {
|
|||||||
late final TextEditingController _nameController;
|
late final TextEditingController _nameController;
|
||||||
late final TextEditingController _addressController;
|
late final TextEditingController _addressController;
|
||||||
late final FocusNode _addressFocusNode;
|
late final FocusNode _addressFocusNode;
|
||||||
|
String? _selectedCostCenterId;
|
||||||
Prediction? _selectedPrediction;
|
Prediction? _selectedPrediction;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -48,6 +54,7 @@ class _HubFormDialogState extends State<HubFormDialog> {
|
|||||||
_nameController = TextEditingController(text: widget.hub?.name);
|
_nameController = TextEditingController(text: widget.hub?.name);
|
||||||
_addressController = TextEditingController(text: widget.hub?.address);
|
_addressController = TextEditingController(text: widget.hub?.address);
|
||||||
_addressFocusNode = FocusNode();
|
_addressFocusNode = FocusNode();
|
||||||
|
_selectedCostCenterId = widget.hub?.costCenter?.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -63,102 +70,193 @@ class _HubFormDialogState extends State<HubFormDialog> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool isEditing = widget.hub != null;
|
final bool isEditing = widget.hub != null;
|
||||||
final String title = isEditing
|
final String title = isEditing
|
||||||
? 'Edit Hub' // TODO: localize
|
? t.client_hubs.edit_hub.title
|
||||||
: t.client_hubs.add_hub_dialog.title;
|
: t.client_hubs.add_hub_dialog.title;
|
||||||
|
|
||||||
final String buttonText = isEditing
|
final String buttonText = isEditing
|
||||||
? 'Save Changes' // TODO: localize
|
? t.client_hubs.edit_hub.save_button
|
||||||
: t.client_hubs.add_hub_dialog.create_button;
|
: t.client_hubs.add_hub_dialog.create_button;
|
||||||
|
|
||||||
return Container(
|
return Center(
|
||||||
color: UiColors.bgOverlay,
|
child: Container(
|
||||||
child: Center(
|
margin: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
|
||||||
child: SingleChildScrollView(
|
decoration: BoxDecoration(
|
||||||
child: Container(
|
color: UiColors.bgPopup,
|
||||||
width: MediaQuery.of(context).size.width * 0.9,
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 3),
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
boxShadow: <BoxShadow>[
|
||||||
decoration: BoxDecoration(
|
BoxShadow(
|
||||||
color: UiColors.bgPopup,
|
color: UiColors.black.withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
blurRadius: 30,
|
||||||
boxShadow: const <BoxShadow>[
|
offset: const Offset(0, 10),
|
||||||
BoxShadow(color: UiColors.popupShadow, blurRadius: 20),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Form(
|
],
|
||||||
key: _formKey,
|
),
|
||||||
child: Column(
|
padding: const EdgeInsets.all(UiConstants.space6),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: SingleChildScrollView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: Form(
|
||||||
children: <Widget>[
|
key: _formKey,
|
||||||
Text(
|
child: Column(
|
||||||
title,
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: UiTypography.headline3m.textPrimary,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: UiTypography.headline3m.textPrimary.copyWith(
|
||||||
|
fontSize: 20,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space5),
|
),
|
||||||
_buildFieldLabel(t.client_hubs.add_hub_dialog.name_label),
|
const SizedBox(height: UiConstants.space5),
|
||||||
TextFormField(
|
|
||||||
controller: _nameController,
|
// ── Hub Name ────────────────────────────────
|
||||||
style: UiTypography.body1r.textPrimary,
|
EditHubFieldLabel(t.client_hubs.add_hub_dialog.name_label),
|
||||||
validator: (String? value) {
|
const SizedBox(height: UiConstants.space2),
|
||||||
if (value == null || value.trim().isEmpty) {
|
TextFormField(
|
||||||
return 'Name is required';
|
controller: _nameController,
|
||||||
}
|
style: UiTypography.body1r.textPrimary,
|
||||||
return null;
|
textInputAction: TextInputAction.next,
|
||||||
},
|
validator: (String? value) {
|
||||||
decoration: _buildInputDecoration(
|
if (value == null || value.trim().isEmpty) {
|
||||||
t.client_hubs.add_hub_dialog.name_hint,
|
return t.client_hubs.add_hub_dialog.name_required;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
decoration: _buildInputDecoration(
|
||||||
|
t.client_hubs.add_hub_dialog.name_hint,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// ── Cost Center ─────────────────────────────
|
||||||
|
EditHubFieldLabel(t.client_hubs.add_hub_dialog.cost_center_label),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
InkWell(
|
||||||
|
onTap: _showCostCenterSelector,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 1.5),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF8FAFD),
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 1.5),
|
||||||
|
border: Border.all(
|
||||||
|
color: _selectedCostCenterId != null
|
||||||
|
? UiColors.primary
|
||||||
|
: UiColors.primary.withValues(alpha: 0.1),
|
||||||
|
width: _selectedCostCenterId != null ? 2 : 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_selectedCostCenterId != null
|
||||||
|
? _getCostCenterName(_selectedCostCenterId!)
|
||||||
|
: t.client_hubs.add_hub_dialog.cost_center_hint,
|
||||||
|
style: _selectedCostCenterId != null
|
||||||
|
? UiTypography.body1r.textPrimary
|
||||||
|
: UiTypography.body2r.textPlaceholder.copyWith(
|
||||||
|
color: UiColors.textSecondary.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(
|
||||||
|
Icons.keyboard_arrow_down,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space4),
|
),
|
||||||
_buildFieldLabel(t.client_hubs.add_hub_dialog.address_label),
|
|
||||||
HubAddressAutocomplete(
|
const SizedBox(height: UiConstants.space4),
|
||||||
controller: _addressController,
|
|
||||||
hintText: t.client_hubs.add_hub_dialog.address_hint,
|
// ── Address ─────────────────────────────────
|
||||||
focusNode: _addressFocusNode,
|
EditHubFieldLabel(t.client_hubs.add_hub_dialog.address_label),
|
||||||
onSelected: (Prediction prediction) {
|
const SizedBox(height: UiConstants.space2),
|
||||||
_selectedPrediction = prediction;
|
HubAddressAutocomplete(
|
||||||
},
|
controller: _addressController,
|
||||||
|
hintText: t.client_hubs.add_hub_dialog.address_hint,
|
||||||
|
decoration: _buildInputDecoration(
|
||||||
|
t.client_hubs.add_hub_dialog.address_hint,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space8),
|
focusNode: _addressFocusNode,
|
||||||
Row(
|
onSelected: (Prediction prediction) {
|
||||||
children: <Widget>[
|
_selectedPrediction = prediction;
|
||||||
Expanded(
|
},
|
||||||
child: UiButton.secondary(
|
),
|
||||||
onPressed: widget.onCancel,
|
|
||||||
text: t.common.cancel,
|
const SizedBox(height: UiConstants.space8),
|
||||||
|
|
||||||
|
// ── Buttons ─────────────────────────────────
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: UiButton.secondary(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: BorderSide(
|
||||||
|
color: UiColors.primary.withValues(alpha: 0.1),
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
UiConstants.radiusBase * 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
onPressed: widget.onCancel,
|
||||||
|
text: t.common.cancel,
|
||||||
),
|
),
|
||||||
const SizedBox(width: UiConstants.space3),
|
),
|
||||||
Expanded(
|
const SizedBox(width: UiConstants.space3),
|
||||||
child: UiButton.primary(
|
Expanded(
|
||||||
onPressed: () {
|
child: UiButton.primary(
|
||||||
if (_formKey.currentState!.validate()) {
|
style: ElevatedButton.styleFrom(
|
||||||
if (_addressController.text.trim().isEmpty) {
|
backgroundColor: UiColors.accent,
|
||||||
UiSnackbar.show(context, message: 'Address is required', type: UiSnackbarType.error);
|
foregroundColor: UiColors.accentForeground,
|
||||||
return;
|
elevation: 0,
|
||||||
}
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
widget.onSave(
|
UiConstants.radiusBase * 1.5,
|
||||||
_nameController.text,
|
),
|
||||||
_addressController.text,
|
),
|
||||||
placeId: _selectedPrediction?.placeId,
|
),
|
||||||
latitude: double.tryParse(
|
onPressed: () {
|
||||||
_selectedPrediction?.lat ?? '',
|
if (_formKey.currentState!.validate()) {
|
||||||
),
|
if (_addressController.text.trim().isEmpty) {
|
||||||
longitude: double.tryParse(
|
UiSnackbar.show(
|
||||||
_selectedPrediction?.lng ?? '',
|
context,
|
||||||
),
|
message: t.client_hubs.add_hub_dialog.address_required,
|
||||||
|
type: UiSnackbarType.error,
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
text: buttonText,
|
widget.onSave(
|
||||||
),
|
name: _nameController.text.trim(),
|
||||||
|
address: _addressController.text.trim(),
|
||||||
|
costCenterId: _selectedCostCenterId,
|
||||||
|
placeId: _selectedPrediction?.placeId,
|
||||||
|
latitude: double.tryParse(
|
||||||
|
_selectedPrediction?.lat ?? '',
|
||||||
|
),
|
||||||
|
longitude: double.tryParse(
|
||||||
|
_selectedPrediction?.lng ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: buttonText,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -166,35 +264,87 @@ class _HubFormDialogState extends State<HubFormDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFieldLabel(String label) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: UiConstants.space2),
|
|
||||||
child: Text(label, style: UiTypography.body2m.textPrimary),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputDecoration _buildInputDecoration(String hint) {
|
InputDecoration _buildInputDecoration(String hint) {
|
||||||
return InputDecoration(
|
return InputDecoration(
|
||||||
hintText: hint,
|
hintText: hint,
|
||||||
hintStyle: UiTypography.body2r.textPlaceholder,
|
hintStyle: UiTypography.body2r.textPlaceholder.copyWith(
|
||||||
|
color: UiColors.textSecondary.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: UiColors.input,
|
fillColor: const Color(0xFFF8FAFD),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: UiConstants.space4,
|
horizontal: UiConstants.space4,
|
||||||
vertical: 14,
|
vertical: 16,
|
||||||
),
|
),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 1.5),
|
||||||
borderSide: const BorderSide(color: UiColors.border),
|
borderSide: BorderSide(color: UiColors.primary.withValues(alpha: 0.1)),
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 1.5),
|
||||||
borderSide: const BorderSide(color: UiColors.border),
|
borderSide: BorderSide(color: UiColors.primary.withValues(alpha: 0.1)),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 1.5),
|
||||||
borderSide: const BorderSide(color: UiColors.ring, width: 2),
|
borderSide: const BorderSide(color: UiColors.primary, width: 2),
|
||||||
),
|
),
|
||||||
|
errorStyle: UiTypography.footnote2r.textError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getCostCenterName(String id) {
|
||||||
|
try {
|
||||||
|
return widget.costCenters.firstWhere((CostCenter cc) => cc.id == id).name;
|
||||||
|
} catch (_) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showCostCenterSelector() async {
|
||||||
|
final CostCenter? selected = await showDialog<CostCenter>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
t.client_hubs.add_hub_dialog.cost_center_label,
|
||||||
|
style: UiTypography.headline3m.textPrimary,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 400),
|
||||||
|
child: widget.costCenters.isEmpty
|
||||||
|
? const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Text('No cost centers available'),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: widget.costCenters.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
final CostCenter cc = widget.costCenters[index];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text(cc.name, style: UiTypography.body1m.textPrimary),
|
||||||
|
subtitle: cc.code != null ? Text(cc.code!, style: UiTypography.body2r.textSecondary) : null,
|
||||||
|
onTap: () => Navigator.of(context).pop(cc),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedCostCenterId = selected.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
apps/mobile/packages/features/client/orders/analyze.txt
Normal file
BIN
apps/mobile/packages/features/client/orders/analyze.txt
Normal file
Binary file not shown.
BIN
apps/mobile/packages/features/client/orders/analyze_output.txt
Normal file
BIN
apps/mobile/packages/features/client/orders/analyze_output.txt
Normal file
Binary file not shown.
@@ -31,6 +31,8 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
on<OneTimeOrderPositionUpdated>(_onPositionUpdated);
|
on<OneTimeOrderPositionUpdated>(_onPositionUpdated);
|
||||||
on<OneTimeOrderSubmitted>(_onSubmitted);
|
on<OneTimeOrderSubmitted>(_onSubmitted);
|
||||||
on<OneTimeOrderInitialized>(_onInitialized);
|
on<OneTimeOrderInitialized>(_onInitialized);
|
||||||
|
on<OneTimeOrderHubManagerChanged>(_onHubManagerChanged);
|
||||||
|
on<OneTimeOrderManagersLoaded>(_onManagersLoaded);
|
||||||
|
|
||||||
_loadVendors();
|
_loadVendors();
|
||||||
_loadHubs();
|
_loadHubs();
|
||||||
@@ -134,6 +136,43 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadManagersForHub(
|
||||||
|
String hubId,
|
||||||
|
) async {
|
||||||
|
final List<OneTimeOrderManagerOption>? managers =
|
||||||
|
await handleErrorWithResult(
|
||||||
|
action: () async {
|
||||||
|
final fdc.QueryResult<dc.ListTeamMembersData, void> result =
|
||||||
|
await _service.connector.listTeamMembers().execute();
|
||||||
|
|
||||||
|
return result.data.teamMembers
|
||||||
|
.where(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
member.teamHubId == hubId &&
|
||||||
|
member.role is dc.Known<dc.TeamMemberRole> &&
|
||||||
|
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
||||||
|
dc.TeamMemberRole.MANAGER,
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
OneTimeOrderManagerOption(
|
||||||
|
id: member.id,
|
||||||
|
name: member.user.fullName ?? 'Unknown',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
onError: (_) {
|
||||||
|
add(const OneTimeOrderManagersLoaded(<OneTimeOrderManagerOption>[]));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (managers != null) {
|
||||||
|
add(OneTimeOrderManagersLoaded(managers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<void> _onVendorsLoaded(
|
Future<void> _onVendorsLoaded(
|
||||||
OneTimeOrderVendorsLoaded event,
|
OneTimeOrderVendorsLoaded event,
|
||||||
Emitter<OneTimeOrderState> emit,
|
Emitter<OneTimeOrderState> emit,
|
||||||
@@ -171,15 +210,36 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
location: selectedHub?.name ?? '',
|
location: selectedHub?.name ?? '',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (selectedHub != null) {
|
||||||
|
_loadManagersForHub(selectedHub.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void _onHubChanged(
|
void _onHubChanged(
|
||||||
OneTimeOrderHubChanged event,
|
OneTimeOrderHubChanged event,
|
||||||
Emitter<OneTimeOrderState> emit,
|
Emitter<OneTimeOrderState> emit,
|
||||||
) {
|
) {
|
||||||
emit(state.copyWith(selectedHub: event.hub, location: event.hub.name));
|
emit(state.copyWith(selectedHub: event.hub, location: event.hub.name));
|
||||||
|
_loadManagersForHub(event.hub.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onHubManagerChanged(
|
||||||
|
OneTimeOrderHubManagerChanged event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(selectedManager: event.manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onManagersLoaded(
|
||||||
|
OneTimeOrderManagersLoaded event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(managers: event.managers));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void _onEventNameChanged(
|
void _onEventNameChanged(
|
||||||
OneTimeOrderEventNameChanged event,
|
OneTimeOrderEventNameChanged event,
|
||||||
Emitter<OneTimeOrderState> emit,
|
Emitter<OneTimeOrderState> emit,
|
||||||
@@ -267,6 +327,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
|||||||
),
|
),
|
||||||
eventName: state.eventName,
|
eventName: state.eventName,
|
||||||
vendorId: state.selectedVendor?.id,
|
vendorId: state.selectedVendor?.id,
|
||||||
|
hubManagerId: state.selectedManager?.id,
|
||||||
roleRates: roleRates,
|
roleRates: roleRates,
|
||||||
);
|
);
|
||||||
await _createOneTimeOrderUseCase(OneTimeOrderArguments(order: order));
|
await _createOneTimeOrderUseCase(OneTimeOrderArguments(order: order));
|
||||||
|
|||||||
@@ -89,3 +89,21 @@ class OneTimeOrderInitialized extends OneTimeOrderEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[data];
|
List<Object?> get props => <Object?>[data];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderHubManagerChanged extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderHubManagerChanged(this.manager);
|
||||||
|
final OneTimeOrderManagerOption? manager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[manager];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderManagersLoaded extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderManagersLoaded(this.managers);
|
||||||
|
final List<OneTimeOrderManagerOption> managers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[managers];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class OneTimeOrderState extends Equatable {
|
|||||||
this.hubs = const <OneTimeOrderHubOption>[],
|
this.hubs = const <OneTimeOrderHubOption>[],
|
||||||
this.selectedHub,
|
this.selectedHub,
|
||||||
this.roles = const <OneTimeOrderRoleOption>[],
|
this.roles = const <OneTimeOrderRoleOption>[],
|
||||||
|
this.managers = const <OneTimeOrderManagerOption>[],
|
||||||
|
this.selectedManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory OneTimeOrderState.initial() {
|
factory OneTimeOrderState.initial() {
|
||||||
@@ -29,6 +31,7 @@ class OneTimeOrderState extends Equatable {
|
|||||||
vendors: const <Vendor>[],
|
vendors: const <Vendor>[],
|
||||||
hubs: const <OneTimeOrderHubOption>[],
|
hubs: const <OneTimeOrderHubOption>[],
|
||||||
roles: const <OneTimeOrderRoleOption>[],
|
roles: const <OneTimeOrderRoleOption>[],
|
||||||
|
managers: const <OneTimeOrderManagerOption>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
@@ -42,6 +45,8 @@ class OneTimeOrderState extends Equatable {
|
|||||||
final List<OneTimeOrderHubOption> hubs;
|
final List<OneTimeOrderHubOption> hubs;
|
||||||
final OneTimeOrderHubOption? selectedHub;
|
final OneTimeOrderHubOption? selectedHub;
|
||||||
final List<OneTimeOrderRoleOption> roles;
|
final List<OneTimeOrderRoleOption> roles;
|
||||||
|
final List<OneTimeOrderManagerOption> managers;
|
||||||
|
final OneTimeOrderManagerOption? selectedManager;
|
||||||
|
|
||||||
OneTimeOrderState copyWith({
|
OneTimeOrderState copyWith({
|
||||||
DateTime? date,
|
DateTime? date,
|
||||||
@@ -55,6 +60,8 @@ class OneTimeOrderState extends Equatable {
|
|||||||
List<OneTimeOrderHubOption>? hubs,
|
List<OneTimeOrderHubOption>? hubs,
|
||||||
OneTimeOrderHubOption? selectedHub,
|
OneTimeOrderHubOption? selectedHub,
|
||||||
List<OneTimeOrderRoleOption>? roles,
|
List<OneTimeOrderRoleOption>? roles,
|
||||||
|
List<OneTimeOrderManagerOption>? managers,
|
||||||
|
OneTimeOrderManagerOption? selectedManager,
|
||||||
}) {
|
}) {
|
||||||
return OneTimeOrderState(
|
return OneTimeOrderState(
|
||||||
date: date ?? this.date,
|
date: date ?? this.date,
|
||||||
@@ -68,6 +75,8 @@ class OneTimeOrderState extends Equatable {
|
|||||||
hubs: hubs ?? this.hubs,
|
hubs: hubs ?? this.hubs,
|
||||||
selectedHub: selectedHub ?? this.selectedHub,
|
selectedHub: selectedHub ?? this.selectedHub,
|
||||||
roles: roles ?? this.roles,
|
roles: roles ?? this.roles,
|
||||||
|
managers: managers ?? this.managers,
|
||||||
|
selectedManager: selectedManager ?? this.selectedManager,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +107,8 @@ class OneTimeOrderState extends Equatable {
|
|||||||
hubs,
|
hubs,
|
||||||
selectedHub,
|
selectedHub,
|
||||||
roles,
|
roles,
|
||||||
|
managers,
|
||||||
|
selectedManager,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,3 +169,17 @@ class OneTimeOrderRoleOption extends Equatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[id, name, costPerHour];
|
List<Object?> get props => <Object?>[id, name, costPerHour];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderManagerOption extends Equatable {
|
||||||
|
const OneTimeOrderManagerOption({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, name];
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
on<PermanentOrderPositionUpdated>(_onPositionUpdated);
|
on<PermanentOrderPositionUpdated>(_onPositionUpdated);
|
||||||
on<PermanentOrderSubmitted>(_onSubmitted);
|
on<PermanentOrderSubmitted>(_onSubmitted);
|
||||||
on<PermanentOrderInitialized>(_onInitialized);
|
on<PermanentOrderInitialized>(_onInitialized);
|
||||||
|
on<PermanentOrderHubManagerChanged>(_onHubManagerChanged);
|
||||||
|
on<PermanentOrderManagersLoaded>(_onManagersLoaded);
|
||||||
|
|
||||||
_loadVendors();
|
_loadVendors();
|
||||||
_loadHubs();
|
_loadHubs();
|
||||||
@@ -182,6 +184,10 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
location: selectedHub?.name ?? '',
|
location: selectedHub?.name ?? '',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (selectedHub != null) {
|
||||||
|
_loadManagersForHub(selectedHub.id, emit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onHubChanged(
|
void _onHubChanged(
|
||||||
@@ -189,8 +195,61 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
Emitter<PermanentOrderState> emit,
|
Emitter<PermanentOrderState> emit,
|
||||||
) {
|
) {
|
||||||
emit(state.copyWith(selectedHub: event.hub, location: event.hub.name));
|
emit(state.copyWith(selectedHub: event.hub, location: event.hub.name));
|
||||||
|
_loadManagersForHub(event.hub.id, emit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onHubManagerChanged(
|
||||||
|
PermanentOrderHubManagerChanged event,
|
||||||
|
Emitter<PermanentOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(selectedManager: event.manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onManagersLoaded(
|
||||||
|
PermanentOrderManagersLoaded event,
|
||||||
|
Emitter<PermanentOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(managers: event.managers));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadManagersForHub(
|
||||||
|
String hubId,
|
||||||
|
Emitter<PermanentOrderState> emit,
|
||||||
|
) async {
|
||||||
|
final List<PermanentOrderManagerOption>? managers =
|
||||||
|
await handleErrorWithResult(
|
||||||
|
action: () async {
|
||||||
|
final fdc.QueryResult<dc.ListTeamMembersData, void> result =
|
||||||
|
await _service.connector.listTeamMembers().execute();
|
||||||
|
|
||||||
|
return result.data.teamMembers
|
||||||
|
.where(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
member.teamHubId == hubId &&
|
||||||
|
member.role is dc.Known<dc.TeamMemberRole> &&
|
||||||
|
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
||||||
|
dc.TeamMemberRole.MANAGER,
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
PermanentOrderManagerOption(
|
||||||
|
id: member.id,
|
||||||
|
name: member.user.fullName ?? 'Unknown',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
onError: (_) => emit(
|
||||||
|
state.copyWith(managers: const <PermanentOrderManagerOption>[]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (managers != null) {
|
||||||
|
emit(state.copyWith(managers: managers, selectedManager: null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void _onEventNameChanged(
|
void _onEventNameChanged(
|
||||||
PermanentOrderEventNameChanged event,
|
PermanentOrderEventNameChanged event,
|
||||||
Emitter<PermanentOrderState> emit,
|
Emitter<PermanentOrderState> emit,
|
||||||
@@ -330,6 +389,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
|||||||
),
|
),
|
||||||
eventName: state.eventName,
|
eventName: state.eventName,
|
||||||
vendorId: state.selectedVendor?.id,
|
vendorId: state.selectedVendor?.id,
|
||||||
|
hubManagerId: state.selectedManager?.id,
|
||||||
roleRates: roleRates,
|
roleRates: roleRates,
|
||||||
);
|
);
|
||||||
await _createPermanentOrderUseCase(order);
|
await _createPermanentOrderUseCase(order);
|
||||||
|
|||||||
@@ -106,3 +106,20 @@ class PermanentOrderInitialized extends PermanentOrderEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[data];
|
List<Object?> get props => <Object?>[data];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PermanentOrderHubManagerChanged extends PermanentOrderEvent {
|
||||||
|
const PermanentOrderHubManagerChanged(this.manager);
|
||||||
|
final PermanentOrderManagerOption? manager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[manager];
|
||||||
|
}
|
||||||
|
|
||||||
|
class PermanentOrderManagersLoaded extends PermanentOrderEvent {
|
||||||
|
const PermanentOrderManagersLoaded(this.managers);
|
||||||
|
final List<PermanentOrderManagerOption> managers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[managers];
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class PermanentOrderState extends Equatable {
|
|||||||
this.hubs = const <PermanentOrderHubOption>[],
|
this.hubs = const <PermanentOrderHubOption>[],
|
||||||
this.selectedHub,
|
this.selectedHub,
|
||||||
this.roles = const <PermanentOrderRoleOption>[],
|
this.roles = const <PermanentOrderRoleOption>[],
|
||||||
|
this.managers = const <PermanentOrderManagerOption>[],
|
||||||
|
this.selectedManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PermanentOrderState.initial() {
|
factory PermanentOrderState.initial() {
|
||||||
@@ -45,6 +47,7 @@ class PermanentOrderState extends Equatable {
|
|||||||
vendors: const <Vendor>[],
|
vendors: const <Vendor>[],
|
||||||
hubs: const <PermanentOrderHubOption>[],
|
hubs: const <PermanentOrderHubOption>[],
|
||||||
roles: const <PermanentOrderRoleOption>[],
|
roles: const <PermanentOrderRoleOption>[],
|
||||||
|
managers: const <PermanentOrderManagerOption>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +64,8 @@ class PermanentOrderState extends Equatable {
|
|||||||
final List<PermanentOrderHubOption> hubs;
|
final List<PermanentOrderHubOption> hubs;
|
||||||
final PermanentOrderHubOption? selectedHub;
|
final PermanentOrderHubOption? selectedHub;
|
||||||
final List<PermanentOrderRoleOption> roles;
|
final List<PermanentOrderRoleOption> roles;
|
||||||
|
final List<PermanentOrderManagerOption> managers;
|
||||||
|
final PermanentOrderManagerOption? selectedManager;
|
||||||
|
|
||||||
PermanentOrderState copyWith({
|
PermanentOrderState copyWith({
|
||||||
DateTime? startDate,
|
DateTime? startDate,
|
||||||
@@ -76,6 +81,8 @@ class PermanentOrderState extends Equatable {
|
|||||||
List<PermanentOrderHubOption>? hubs,
|
List<PermanentOrderHubOption>? hubs,
|
||||||
PermanentOrderHubOption? selectedHub,
|
PermanentOrderHubOption? selectedHub,
|
||||||
List<PermanentOrderRoleOption>? roles,
|
List<PermanentOrderRoleOption>? roles,
|
||||||
|
List<PermanentOrderManagerOption>? managers,
|
||||||
|
PermanentOrderManagerOption? selectedManager,
|
||||||
}) {
|
}) {
|
||||||
return PermanentOrderState(
|
return PermanentOrderState(
|
||||||
startDate: startDate ?? this.startDate,
|
startDate: startDate ?? this.startDate,
|
||||||
@@ -91,6 +98,8 @@ class PermanentOrderState extends Equatable {
|
|||||||
hubs: hubs ?? this.hubs,
|
hubs: hubs ?? this.hubs,
|
||||||
selectedHub: selectedHub ?? this.selectedHub,
|
selectedHub: selectedHub ?? this.selectedHub,
|
||||||
roles: roles ?? this.roles,
|
roles: roles ?? this.roles,
|
||||||
|
managers: managers ?? this.managers,
|
||||||
|
selectedManager: selectedManager ?? this.selectedManager,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +133,8 @@ class PermanentOrderState extends Equatable {
|
|||||||
hubs,
|
hubs,
|
||||||
selectedHub,
|
selectedHub,
|
||||||
roles,
|
roles,
|
||||||
|
managers,
|
||||||
|
selectedManager,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +196,20 @@ class PermanentOrderRoleOption extends Equatable {
|
|||||||
List<Object?> get props => <Object?>[id, name, costPerHour];
|
List<Object?> get props => <Object?>[id, name, costPerHour];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PermanentOrderManagerOption extends Equatable {
|
||||||
|
const PermanentOrderManagerOption({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, name];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PermanentOrderPosition extends Equatable {
|
class PermanentOrderPosition extends Equatable {
|
||||||
const PermanentOrderPosition({
|
const PermanentOrderPosition({
|
||||||
required this.role,
|
required this.role,
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
on<RecurringOrderPositionUpdated>(_onPositionUpdated);
|
on<RecurringOrderPositionUpdated>(_onPositionUpdated);
|
||||||
on<RecurringOrderSubmitted>(_onSubmitted);
|
on<RecurringOrderSubmitted>(_onSubmitted);
|
||||||
on<RecurringOrderInitialized>(_onInitialized);
|
on<RecurringOrderInitialized>(_onInitialized);
|
||||||
|
on<RecurringOrderHubManagerChanged>(_onHubManagerChanged);
|
||||||
|
on<RecurringOrderManagersLoaded>(_onManagersLoaded);
|
||||||
|
|
||||||
_loadVendors();
|
_loadVendors();
|
||||||
_loadHubs();
|
_loadHubs();
|
||||||
@@ -183,6 +185,10 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
location: selectedHub?.name ?? '',
|
location: selectedHub?.name ?? '',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (selectedHub != null) {
|
||||||
|
_loadManagersForHub(selectedHub.id, emit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onHubChanged(
|
void _onHubChanged(
|
||||||
@@ -190,6 +196,58 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
Emitter<RecurringOrderState> emit,
|
Emitter<RecurringOrderState> emit,
|
||||||
) {
|
) {
|
||||||
emit(state.copyWith(selectedHub: event.hub, location: event.hub.name));
|
emit(state.copyWith(selectedHub: event.hub, location: event.hub.name));
|
||||||
|
_loadManagersForHub(event.hub.id, emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onHubManagerChanged(
|
||||||
|
RecurringOrderHubManagerChanged event,
|
||||||
|
Emitter<RecurringOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(selectedManager: event.manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onManagersLoaded(
|
||||||
|
RecurringOrderManagersLoaded event,
|
||||||
|
Emitter<RecurringOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(managers: event.managers));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadManagersForHub(
|
||||||
|
String hubId,
|
||||||
|
Emitter<RecurringOrderState> emit,
|
||||||
|
) async {
|
||||||
|
final List<RecurringOrderManagerOption>? managers =
|
||||||
|
await handleErrorWithResult(
|
||||||
|
action: () async {
|
||||||
|
final fdc.QueryResult<dc.ListTeamMembersData, void> result =
|
||||||
|
await _service.connector.listTeamMembers().execute();
|
||||||
|
|
||||||
|
return result.data.teamMembers
|
||||||
|
.where(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
member.teamHubId == hubId &&
|
||||||
|
member.role is dc.Known<dc.TeamMemberRole> &&
|
||||||
|
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
||||||
|
dc.TeamMemberRole.MANAGER,
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
RecurringOrderManagerOption(
|
||||||
|
id: member.id,
|
||||||
|
name: member.user.fullName ?? 'Unknown',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
onError: (_) => emit(
|
||||||
|
state.copyWith(managers: const <RecurringOrderManagerOption>[]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (managers != null) {
|
||||||
|
emit(state.copyWith(managers: managers, selectedManager: null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onEventNameChanged(
|
void _onEventNameChanged(
|
||||||
@@ -349,6 +407,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
|||||||
),
|
),
|
||||||
eventName: state.eventName,
|
eventName: state.eventName,
|
||||||
vendorId: state.selectedVendor?.id,
|
vendorId: state.selectedVendor?.id,
|
||||||
|
hubManagerId: state.selectedManager?.id,
|
||||||
roleRates: roleRates,
|
roleRates: roleRates,
|
||||||
);
|
);
|
||||||
await _createRecurringOrderUseCase(order);
|
await _createRecurringOrderUseCase(order);
|
||||||
|
|||||||
@@ -115,3 +115,20 @@ class RecurringOrderInitialized extends RecurringOrderEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[data];
|
List<Object?> get props => <Object?>[data];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RecurringOrderHubManagerChanged extends RecurringOrderEvent {
|
||||||
|
const RecurringOrderHubManagerChanged(this.manager);
|
||||||
|
final RecurringOrderManagerOption? manager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[manager];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecurringOrderManagersLoaded extends RecurringOrderEvent {
|
||||||
|
const RecurringOrderManagersLoaded(this.managers);
|
||||||
|
final List<RecurringOrderManagerOption> managers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[managers];
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class RecurringOrderState extends Equatable {
|
|||||||
this.hubs = const <RecurringOrderHubOption>[],
|
this.hubs = const <RecurringOrderHubOption>[],
|
||||||
this.selectedHub,
|
this.selectedHub,
|
||||||
this.roles = const <RecurringOrderRoleOption>[],
|
this.roles = const <RecurringOrderRoleOption>[],
|
||||||
|
this.managers = const <RecurringOrderManagerOption>[],
|
||||||
|
this.selectedManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory RecurringOrderState.initial() {
|
factory RecurringOrderState.initial() {
|
||||||
@@ -47,6 +49,7 @@ class RecurringOrderState extends Equatable {
|
|||||||
vendors: const <Vendor>[],
|
vendors: const <Vendor>[],
|
||||||
hubs: const <RecurringOrderHubOption>[],
|
hubs: const <RecurringOrderHubOption>[],
|
||||||
roles: const <RecurringOrderRoleOption>[],
|
roles: const <RecurringOrderRoleOption>[],
|
||||||
|
managers: const <RecurringOrderManagerOption>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +67,8 @@ class RecurringOrderState extends Equatable {
|
|||||||
final List<RecurringOrderHubOption> hubs;
|
final List<RecurringOrderHubOption> hubs;
|
||||||
final RecurringOrderHubOption? selectedHub;
|
final RecurringOrderHubOption? selectedHub;
|
||||||
final List<RecurringOrderRoleOption> roles;
|
final List<RecurringOrderRoleOption> roles;
|
||||||
|
final List<RecurringOrderManagerOption> managers;
|
||||||
|
final RecurringOrderManagerOption? selectedManager;
|
||||||
|
|
||||||
RecurringOrderState copyWith({
|
RecurringOrderState copyWith({
|
||||||
DateTime? startDate,
|
DateTime? startDate,
|
||||||
@@ -80,6 +85,8 @@ class RecurringOrderState extends Equatable {
|
|||||||
List<RecurringOrderHubOption>? hubs,
|
List<RecurringOrderHubOption>? hubs,
|
||||||
RecurringOrderHubOption? selectedHub,
|
RecurringOrderHubOption? selectedHub,
|
||||||
List<RecurringOrderRoleOption>? roles,
|
List<RecurringOrderRoleOption>? roles,
|
||||||
|
List<RecurringOrderManagerOption>? managers,
|
||||||
|
RecurringOrderManagerOption? selectedManager,
|
||||||
}) {
|
}) {
|
||||||
return RecurringOrderState(
|
return RecurringOrderState(
|
||||||
startDate: startDate ?? this.startDate,
|
startDate: startDate ?? this.startDate,
|
||||||
@@ -96,6 +103,8 @@ class RecurringOrderState extends Equatable {
|
|||||||
hubs: hubs ?? this.hubs,
|
hubs: hubs ?? this.hubs,
|
||||||
selectedHub: selectedHub ?? this.selectedHub,
|
selectedHub: selectedHub ?? this.selectedHub,
|
||||||
roles: roles ?? this.roles,
|
roles: roles ?? this.roles,
|
||||||
|
managers: managers ?? this.managers,
|
||||||
|
selectedManager: selectedManager ?? this.selectedManager,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +141,8 @@ class RecurringOrderState extends Equatable {
|
|||||||
hubs,
|
hubs,
|
||||||
selectedHub,
|
selectedHub,
|
||||||
roles,
|
roles,
|
||||||
|
managers,
|
||||||
|
selectedManager,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +204,20 @@ class RecurringOrderRoleOption extends Equatable {
|
|||||||
List<Object?> get props => <Object?>[id, name, costPerHour];
|
List<Object?> get props => <Object?>[id, name, costPerHour];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RecurringOrderManagerOption extends Equatable {
|
||||||
|
const RecurringOrderManagerOption({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, name];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RecurringOrderPosition extends Equatable {
|
class RecurringOrderPosition extends Equatable {
|
||||||
const RecurringOrderPosition({
|
const RecurringOrderPosition({
|
||||||
required this.role,
|
required this.role,
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ class OneTimeOrderPage extends StatelessWidget {
|
|||||||
hubs: state.hubs.map(_mapHub).toList(),
|
hubs: state.hubs.map(_mapHub).toList(),
|
||||||
positions: state.positions.map(_mapPosition).toList(),
|
positions: state.positions.map(_mapPosition).toList(),
|
||||||
roles: state.roles.map(_mapRole).toList(),
|
roles: state.roles.map(_mapRole).toList(),
|
||||||
|
selectedHubManager: state.selectedManager != null
|
||||||
|
? _mapManager(state.selectedManager!)
|
||||||
|
: null,
|
||||||
|
hubManagers: state.managers.map(_mapManager).toList(),
|
||||||
isValid: state.isValid,
|
isValid: state.isValid,
|
||||||
onEventNameChanged: (String val) =>
|
onEventNameChanged: (String val) =>
|
||||||
bloc.add(OneTimeOrderEventNameChanged(val)),
|
bloc.add(OneTimeOrderEventNameChanged(val)),
|
||||||
@@ -61,6 +65,17 @@ class OneTimeOrderPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
bloc.add(OneTimeOrderHubChanged(originalHub));
|
bloc.add(OneTimeOrderHubChanged(originalHub));
|
||||||
},
|
},
|
||||||
|
onHubManagerChanged: (OrderManagerUiModel? val) {
|
||||||
|
if (val == null) {
|
||||||
|
bloc.add(const OneTimeOrderHubManagerChanged(null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final OneTimeOrderManagerOption original =
|
||||||
|
state.managers.firstWhere(
|
||||||
|
(OneTimeOrderManagerOption m) => m.id == val.id,
|
||||||
|
);
|
||||||
|
bloc.add(OneTimeOrderHubManagerChanged(original));
|
||||||
|
},
|
||||||
onPositionAdded: () => bloc.add(const OneTimeOrderPositionAdded()),
|
onPositionAdded: () => bloc.add(const OneTimeOrderPositionAdded()),
|
||||||
onPositionUpdated: (int index, OrderPositionUiModel val) {
|
onPositionUpdated: (int index, OrderPositionUiModel val) {
|
||||||
final OneTimeOrderPosition original = state.positions[index];
|
final OneTimeOrderPosition original = state.positions[index];
|
||||||
@@ -130,4 +145,9 @@ class OneTimeOrderPage extends StatelessWidget {
|
|||||||
lunchBreak: pos.lunchBreak,
|
lunchBreak: pos.lunchBreak,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OrderManagerUiModel _mapManager(OneTimeOrderManagerOption manager) {
|
||||||
|
return OrderManagerUiModel(id: manager.id, name: manager.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ class PermanentOrderPage extends StatelessWidget {
|
|||||||
? _mapHub(state.selectedHub!)
|
? _mapHub(state.selectedHub!)
|
||||||
: null,
|
: null,
|
||||||
hubs: state.hubs.map(_mapHub).toList(),
|
hubs: state.hubs.map(_mapHub).toList(),
|
||||||
|
hubManagers: state.managers.map(_mapManager).toList(),
|
||||||
|
selectedHubManager: state.selectedManager != null
|
||||||
|
? _mapManager(state.selectedManager!)
|
||||||
|
: null,
|
||||||
positions: state.positions.map(_mapPosition).toList(),
|
positions: state.positions.map(_mapPosition).toList(),
|
||||||
roles: state.roles.map(_mapRole).toList(),
|
roles: state.roles.map(_mapRole).toList(),
|
||||||
isValid: state.isValid,
|
isValid: state.isValid,
|
||||||
@@ -59,6 +63,17 @@ class PermanentOrderPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
bloc.add(PermanentOrderHubChanged(originalHub));
|
bloc.add(PermanentOrderHubChanged(originalHub));
|
||||||
},
|
},
|
||||||
|
onHubManagerChanged: (OrderManagerUiModel? val) {
|
||||||
|
if (val == null) {
|
||||||
|
bloc.add(const PermanentOrderHubManagerChanged(null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final PermanentOrderManagerOption original =
|
||||||
|
state.managers.firstWhere(
|
||||||
|
(PermanentOrderManagerOption m) => m.id == val.id,
|
||||||
|
);
|
||||||
|
bloc.add(PermanentOrderHubManagerChanged(original));
|
||||||
|
},
|
||||||
onPositionAdded: () =>
|
onPositionAdded: () =>
|
||||||
bloc.add(const PermanentOrderPositionAdded()),
|
bloc.add(const PermanentOrderPositionAdded()),
|
||||||
onPositionUpdated: (int index, OrderPositionUiModel val) {
|
onPositionUpdated: (int index, OrderPositionUiModel val) {
|
||||||
@@ -181,4 +196,8 @@ class PermanentOrderPage extends StatelessWidget {
|
|||||||
lunchBreak: pos.lunchBreak ?? 'NO_BREAK',
|
lunchBreak: pos.lunchBreak ?? 'NO_BREAK',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OrderManagerUiModel _mapManager(PermanentOrderManagerOption manager) {
|
||||||
|
return OrderManagerUiModel(id: manager.id, name: manager.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ class RecurringOrderPage extends StatelessWidget {
|
|||||||
? _mapHub(state.selectedHub!)
|
? _mapHub(state.selectedHub!)
|
||||||
: null,
|
: null,
|
||||||
hubs: state.hubs.map(_mapHub).toList(),
|
hubs: state.hubs.map(_mapHub).toList(),
|
||||||
|
hubManagers: state.managers.map(_mapManager).toList(),
|
||||||
|
selectedHubManager: state.selectedManager != null
|
||||||
|
? _mapManager(state.selectedManager!)
|
||||||
|
: null,
|
||||||
positions: state.positions.map(_mapPosition).toList(),
|
positions: state.positions.map(_mapPosition).toList(),
|
||||||
roles: state.roles.map(_mapRole).toList(),
|
roles: state.roles.map(_mapRole).toList(),
|
||||||
isValid: state.isValid,
|
isValid: state.isValid,
|
||||||
@@ -62,6 +66,17 @@ class RecurringOrderPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
bloc.add(RecurringOrderHubChanged(originalHub));
|
bloc.add(RecurringOrderHubChanged(originalHub));
|
||||||
},
|
},
|
||||||
|
onHubManagerChanged: (OrderManagerUiModel? val) {
|
||||||
|
if (val == null) {
|
||||||
|
bloc.add(const RecurringOrderHubManagerChanged(null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final RecurringOrderManagerOption original =
|
||||||
|
state.managers.firstWhere(
|
||||||
|
(RecurringOrderManagerOption m) => m.id == val.id,
|
||||||
|
);
|
||||||
|
bloc.add(RecurringOrderHubManagerChanged(original));
|
||||||
|
},
|
||||||
onPositionAdded: () =>
|
onPositionAdded: () =>
|
||||||
bloc.add(const RecurringOrderPositionAdded()),
|
bloc.add(const RecurringOrderPositionAdded()),
|
||||||
onPositionUpdated: (int index, OrderPositionUiModel val) {
|
onPositionUpdated: (int index, OrderPositionUiModel val) {
|
||||||
@@ -193,4 +208,8 @@ class RecurringOrderPage extends StatelessWidget {
|
|||||||
lunchBreak: pos.lunchBreak ?? 'NO_BREAK',
|
lunchBreak: pos.lunchBreak ?? 'NO_BREAK',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OrderManagerUiModel _mapManager(RecurringOrderManagerOption manager) {
|
||||||
|
return OrderManagerUiModel(id: manager.id, name: manager.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'order_ui_models.dart';
|
||||||
|
|
||||||
|
class HubManagerSelector extends StatelessWidget {
|
||||||
|
const HubManagerSelector({
|
||||||
|
required this.managers,
|
||||||
|
required this.selectedManager,
|
||||||
|
required this.onChanged,
|
||||||
|
required this.hintText,
|
||||||
|
required this.label,
|
||||||
|
this.description,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<OrderManagerUiModel> managers;
|
||||||
|
final OrderManagerUiModel? selectedManager;
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onChanged;
|
||||||
|
final String hintText;
|
||||||
|
final String label;
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: UiTypography.body1m.textPrimary,
|
||||||
|
),
|
||||||
|
if (description != null) ...<Widget>[
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Text(description!, style: UiTypography.body2r.textSecondary),
|
||||||
|
],
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _showSelector(context),
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: 14,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
border: Border.all(
|
||||||
|
color: selectedManager != null ? UiColors.primary : UiColors.border,
|
||||||
|
width: selectedManager != null ? 2 : 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(
|
||||||
|
UiIcons.user,
|
||||||
|
color: selectedManager != null
|
||||||
|
? UiColors.primary
|
||||||
|
: UiColors.iconSecondary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Text(
|
||||||
|
selectedManager?.name ?? hintText,
|
||||||
|
style: selectedManager != null
|
||||||
|
? UiTypography.body1r.textPrimary
|
||||||
|
: UiTypography.body2r.textPlaceholder,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Icon(
|
||||||
|
Icons.keyboard_arrow_down,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showSelector(BuildContext context) async {
|
||||||
|
final OrderManagerUiModel? selected = await showDialog<OrderManagerUiModel>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
label,
|
||||||
|
style: UiTypography.headline3m.textPrimary,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 400),
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: managers.isEmpty ? 2 : managers.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (managers.isEmpty) {
|
||||||
|
if (index == 0) {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
|
child: Text('No hub managers available'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('None', style: UiTypography.body1m.textSecondary),
|
||||||
|
onTap: () => Navigator.of(context).pop(
|
||||||
|
const OrderManagerUiModel(id: 'NONE', name: 'None'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == managers.length) {
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('None', style: UiTypography.body1m.textSecondary),
|
||||||
|
onTap: () => Navigator.of(context).pop(
|
||||||
|
const OrderManagerUiModel(id: 'NONE', name: 'None'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final OrderManagerUiModel manager = managers[index];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
|
title: Text(manager.name, style: UiTypography.body1m.textPrimary),
|
||||||
|
subtitle: manager.phone != null
|
||||||
|
? Text(manager.phone!, style: UiTypography.body2r.textSecondary)
|
||||||
|
: null,
|
||||||
|
onTap: () => Navigator.of(context).pop(manager),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
if (selected.id == 'NONE') {
|
||||||
|
onChanged(null);
|
||||||
|
} else {
|
||||||
|
onChanged(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../order_ui_models.dart';
|
import '../order_ui_models.dart';
|
||||||
|
import '../hub_manager_selector.dart';
|
||||||
import 'one_time_order_date_picker.dart';
|
import 'one_time_order_date_picker.dart';
|
||||||
import 'one_time_order_event_name_input.dart';
|
import 'one_time_order_event_name_input.dart';
|
||||||
import 'one_time_order_header.dart';
|
import 'one_time_order_header.dart';
|
||||||
@@ -23,11 +24,14 @@ class OneTimeOrderView extends StatelessWidget {
|
|||||||
required this.hubs,
|
required this.hubs,
|
||||||
required this.positions,
|
required this.positions,
|
||||||
required this.roles,
|
required this.roles,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.selectedHubManager,
|
||||||
required this.isValid,
|
required this.isValid,
|
||||||
required this.onEventNameChanged,
|
required this.onEventNameChanged,
|
||||||
required this.onVendorChanged,
|
required this.onVendorChanged,
|
||||||
required this.onDateChanged,
|
required this.onDateChanged,
|
||||||
required this.onHubChanged,
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
required this.onPositionAdded,
|
required this.onPositionAdded,
|
||||||
required this.onPositionUpdated,
|
required this.onPositionUpdated,
|
||||||
required this.onPositionRemoved,
|
required this.onPositionRemoved,
|
||||||
@@ -47,12 +51,15 @@ class OneTimeOrderView extends StatelessWidget {
|
|||||||
final List<OrderHubUiModel> hubs;
|
final List<OrderHubUiModel> hubs;
|
||||||
final List<OrderPositionUiModel> positions;
|
final List<OrderPositionUiModel> positions;
|
||||||
final List<OrderRoleUiModel> roles;
|
final List<OrderRoleUiModel> roles;
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
final bool isValid;
|
final bool isValid;
|
||||||
|
|
||||||
final ValueChanged<String> onEventNameChanged;
|
final ValueChanged<String> onEventNameChanged;
|
||||||
final ValueChanged<Vendor> onVendorChanged;
|
final ValueChanged<Vendor> onVendorChanged;
|
||||||
final ValueChanged<DateTime> onDateChanged;
|
final ValueChanged<DateTime> onDateChanged;
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
@@ -143,12 +150,15 @@ class OneTimeOrderView extends StatelessWidget {
|
|||||||
date: date,
|
date: date,
|
||||||
selectedHub: selectedHub,
|
selectedHub: selectedHub,
|
||||||
hubs: hubs,
|
hubs: hubs,
|
||||||
|
selectedHubManager: selectedHubManager,
|
||||||
|
hubManagers: hubManagers,
|
||||||
positions: positions,
|
positions: positions,
|
||||||
roles: roles,
|
roles: roles,
|
||||||
onEventNameChanged: onEventNameChanged,
|
onEventNameChanged: onEventNameChanged,
|
||||||
onVendorChanged: onVendorChanged,
|
onVendorChanged: onVendorChanged,
|
||||||
onDateChanged: onDateChanged,
|
onDateChanged: onDateChanged,
|
||||||
onHubChanged: onHubChanged,
|
onHubChanged: onHubChanged,
|
||||||
|
onHubManagerChanged: onHubManagerChanged,
|
||||||
onPositionAdded: onPositionAdded,
|
onPositionAdded: onPositionAdded,
|
||||||
onPositionUpdated: onPositionUpdated,
|
onPositionUpdated: onPositionUpdated,
|
||||||
onPositionRemoved: onPositionRemoved,
|
onPositionRemoved: onPositionRemoved,
|
||||||
@@ -179,12 +189,15 @@ class _OneTimeOrderForm extends StatelessWidget {
|
|||||||
required this.date,
|
required this.date,
|
||||||
required this.selectedHub,
|
required this.selectedHub,
|
||||||
required this.hubs,
|
required this.hubs,
|
||||||
|
required this.selectedHubManager,
|
||||||
|
required this.hubManagers,
|
||||||
required this.positions,
|
required this.positions,
|
||||||
required this.roles,
|
required this.roles,
|
||||||
required this.onEventNameChanged,
|
required this.onEventNameChanged,
|
||||||
required this.onVendorChanged,
|
required this.onVendorChanged,
|
||||||
required this.onDateChanged,
|
required this.onDateChanged,
|
||||||
required this.onHubChanged,
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
required this.onPositionAdded,
|
required this.onPositionAdded,
|
||||||
required this.onPositionUpdated,
|
required this.onPositionUpdated,
|
||||||
required this.onPositionRemoved,
|
required this.onPositionRemoved,
|
||||||
@@ -196,6 +209,8 @@ class _OneTimeOrderForm extends StatelessWidget {
|
|||||||
final DateTime date;
|
final DateTime date;
|
||||||
final OrderHubUiModel? selectedHub;
|
final OrderHubUiModel? selectedHub;
|
||||||
final List<OrderHubUiModel> hubs;
|
final List<OrderHubUiModel> hubs;
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
final List<OrderPositionUiModel> positions;
|
final List<OrderPositionUiModel> positions;
|
||||||
final List<OrderRoleUiModel> roles;
|
final List<OrderRoleUiModel> roles;
|
||||||
|
|
||||||
@@ -203,6 +218,7 @@ class _OneTimeOrderForm extends StatelessWidget {
|
|||||||
final ValueChanged<Vendor> onVendorChanged;
|
final ValueChanged<Vendor> onVendorChanged;
|
||||||
final ValueChanged<DateTime> onDateChanged;
|
final ValueChanged<DateTime> onDateChanged;
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
@@ -310,6 +326,16 @@ class _OneTimeOrderForm extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
HubManagerSelector(
|
||||||
|
label: labels.hub_manager_label,
|
||||||
|
description: labels.hub_manager_desc,
|
||||||
|
hintText: labels.hub_manager_hint,
|
||||||
|
managers: hubManagers,
|
||||||
|
selectedManager: selectedHubManager,
|
||||||
|
onChanged: onHubManagerChanged,
|
||||||
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
OneTimeOrderSectionHeader(
|
OneTimeOrderSectionHeader(
|
||||||
|
|||||||
@@ -94,3 +94,19 @@ class OrderPositionUiModel extends Equatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[role, count, startTime, endTime, lunchBreak];
|
List<Object?> get props => <Object?>[role, count, startTime, endTime, lunchBreak];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OrderManagerUiModel extends Equatable {
|
||||||
|
const OrderManagerUiModel({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
this.phone,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String? phone;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, name, phone];
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
import 'package:krow_domain/krow_domain.dart' show Vendor;
|
||||||
import '../order_ui_models.dart';
|
import '../order_ui_models.dart';
|
||||||
|
import '../hub_manager_selector.dart';
|
||||||
import 'permanent_order_date_picker.dart';
|
import 'permanent_order_date_picker.dart';
|
||||||
import 'permanent_order_event_name_input.dart';
|
import 'permanent_order_event_name_input.dart';
|
||||||
import 'permanent_order_header.dart';
|
import 'permanent_order_header.dart';
|
||||||
@@ -24,12 +25,15 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
required this.hubs,
|
required this.hubs,
|
||||||
required this.positions,
|
required this.positions,
|
||||||
required this.roles,
|
required this.roles,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.selectedHubManager,
|
||||||
required this.isValid,
|
required this.isValid,
|
||||||
required this.onEventNameChanged,
|
required this.onEventNameChanged,
|
||||||
required this.onVendorChanged,
|
required this.onVendorChanged,
|
||||||
required this.onStartDateChanged,
|
required this.onStartDateChanged,
|
||||||
required this.onDayToggled,
|
required this.onDayToggled,
|
||||||
required this.onHubChanged,
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
required this.onPositionAdded,
|
required this.onPositionAdded,
|
||||||
required this.onPositionUpdated,
|
required this.onPositionUpdated,
|
||||||
required this.onPositionRemoved,
|
required this.onPositionRemoved,
|
||||||
@@ -48,6 +52,8 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
final List<String> permanentDays;
|
final List<String> permanentDays;
|
||||||
final OrderHubUiModel? selectedHub;
|
final OrderHubUiModel? selectedHub;
|
||||||
final List<OrderHubUiModel> hubs;
|
final List<OrderHubUiModel> hubs;
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
final List<OrderPositionUiModel> positions;
|
final List<OrderPositionUiModel> positions;
|
||||||
final List<OrderRoleUiModel> roles;
|
final List<OrderRoleUiModel> roles;
|
||||||
final bool isValid;
|
final bool isValid;
|
||||||
@@ -57,6 +63,7 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
final ValueChanged<DateTime> onStartDateChanged;
|
final ValueChanged<DateTime> onStartDateChanged;
|
||||||
final ValueChanged<int> onDayToggled;
|
final ValueChanged<int> onDayToggled;
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
@@ -156,9 +163,12 @@ class PermanentOrderView extends StatelessWidget {
|
|||||||
onStartDateChanged: onStartDateChanged,
|
onStartDateChanged: onStartDateChanged,
|
||||||
onDayToggled: onDayToggled,
|
onDayToggled: onDayToggled,
|
||||||
onHubChanged: onHubChanged,
|
onHubChanged: onHubChanged,
|
||||||
|
onHubManagerChanged: onHubManagerChanged,
|
||||||
onPositionAdded: onPositionAdded,
|
onPositionAdded: onPositionAdded,
|
||||||
onPositionUpdated: onPositionUpdated,
|
onPositionUpdated: onPositionUpdated,
|
||||||
onPositionRemoved: onPositionRemoved,
|
onPositionRemoved: onPositionRemoved,
|
||||||
|
hubManagers: hubManagers,
|
||||||
|
selectedHubManager: selectedHubManager,
|
||||||
),
|
),
|
||||||
if (status == OrderFormStatus.loading)
|
if (status == OrderFormStatus.loading)
|
||||||
const Center(child: CircularProgressIndicator()),
|
const Center(child: CircularProgressIndicator()),
|
||||||
@@ -194,9 +204,12 @@ class _PermanentOrderForm extends StatelessWidget {
|
|||||||
required this.onStartDateChanged,
|
required this.onStartDateChanged,
|
||||||
required this.onDayToggled,
|
required this.onDayToggled,
|
||||||
required this.onHubChanged,
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
required this.onPositionAdded,
|
required this.onPositionAdded,
|
||||||
required this.onPositionUpdated,
|
required this.onPositionUpdated,
|
||||||
required this.onPositionRemoved,
|
required this.onPositionRemoved,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.selectedHubManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String eventName;
|
final String eventName;
|
||||||
@@ -214,10 +227,14 @@ class _PermanentOrderForm extends StatelessWidget {
|
|||||||
final ValueChanged<DateTime> onStartDateChanged;
|
final ValueChanged<DateTime> onStartDateChanged;
|
||||||
final ValueChanged<int> onDayToggled;
|
final ValueChanged<int> onDayToggled;
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
|
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final TranslationsClientCreateOrderPermanentEn labels =
|
final TranslationsClientCreateOrderPermanentEn labels =
|
||||||
@@ -331,6 +348,16 @@ class _PermanentOrderForm extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
HubManagerSelector(
|
||||||
|
label: oneTimeLabels.hub_manager_label,
|
||||||
|
description: oneTimeLabels.hub_manager_desc,
|
||||||
|
hintText: oneTimeLabels.hub_manager_hint,
|
||||||
|
managers: hubManagers,
|
||||||
|
selectedManager: selectedHubManager,
|
||||||
|
onChanged: onHubManagerChanged,
|
||||||
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
PermanentOrderSectionHeader(
|
PermanentOrderSectionHeader(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:krow_domain/krow_domain.dart' show Vendor;
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../order_ui_models.dart';
|
import '../order_ui_models.dart';
|
||||||
|
import '../hub_manager_selector.dart';
|
||||||
import 'recurring_order_date_picker.dart';
|
import 'recurring_order_date_picker.dart';
|
||||||
import 'recurring_order_event_name_input.dart';
|
import 'recurring_order_event_name_input.dart';
|
||||||
import 'recurring_order_header.dart';
|
import 'recurring_order_header.dart';
|
||||||
@@ -25,6 +26,8 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
required this.hubs,
|
required this.hubs,
|
||||||
required this.positions,
|
required this.positions,
|
||||||
required this.roles,
|
required this.roles,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.selectedHubManager,
|
||||||
required this.isValid,
|
required this.isValid,
|
||||||
required this.onEventNameChanged,
|
required this.onEventNameChanged,
|
||||||
required this.onVendorChanged,
|
required this.onVendorChanged,
|
||||||
@@ -32,6 +35,7 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
required this.onEndDateChanged,
|
required this.onEndDateChanged,
|
||||||
required this.onDayToggled,
|
required this.onDayToggled,
|
||||||
required this.onHubChanged,
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
required this.onPositionAdded,
|
required this.onPositionAdded,
|
||||||
required this.onPositionUpdated,
|
required this.onPositionUpdated,
|
||||||
required this.onPositionRemoved,
|
required this.onPositionRemoved,
|
||||||
@@ -51,6 +55,8 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
final List<String> recurringDays;
|
final List<String> recurringDays;
|
||||||
final OrderHubUiModel? selectedHub;
|
final OrderHubUiModel? selectedHub;
|
||||||
final List<OrderHubUiModel> hubs;
|
final List<OrderHubUiModel> hubs;
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
final List<OrderPositionUiModel> positions;
|
final List<OrderPositionUiModel> positions;
|
||||||
final List<OrderRoleUiModel> roles;
|
final List<OrderRoleUiModel> roles;
|
||||||
final bool isValid;
|
final bool isValid;
|
||||||
@@ -61,6 +67,7 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
final ValueChanged<DateTime> onEndDateChanged;
|
final ValueChanged<DateTime> onEndDateChanged;
|
||||||
final ValueChanged<int> onDayToggled;
|
final ValueChanged<int> onDayToggled;
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
@@ -165,9 +172,12 @@ class RecurringOrderView extends StatelessWidget {
|
|||||||
onEndDateChanged: onEndDateChanged,
|
onEndDateChanged: onEndDateChanged,
|
||||||
onDayToggled: onDayToggled,
|
onDayToggled: onDayToggled,
|
||||||
onHubChanged: onHubChanged,
|
onHubChanged: onHubChanged,
|
||||||
|
onHubManagerChanged: onHubManagerChanged,
|
||||||
onPositionAdded: onPositionAdded,
|
onPositionAdded: onPositionAdded,
|
||||||
onPositionUpdated: onPositionUpdated,
|
onPositionUpdated: onPositionUpdated,
|
||||||
onPositionRemoved: onPositionRemoved,
|
onPositionRemoved: onPositionRemoved,
|
||||||
|
hubManagers: hubManagers,
|
||||||
|
selectedHubManager: selectedHubManager,
|
||||||
),
|
),
|
||||||
if (status == OrderFormStatus.loading)
|
if (status == OrderFormStatus.loading)
|
||||||
const Center(child: CircularProgressIndicator()),
|
const Center(child: CircularProgressIndicator()),
|
||||||
@@ -205,9 +215,12 @@ class _RecurringOrderForm extends StatelessWidget {
|
|||||||
required this.onEndDateChanged,
|
required this.onEndDateChanged,
|
||||||
required this.onDayToggled,
|
required this.onDayToggled,
|
||||||
required this.onHubChanged,
|
required this.onHubChanged,
|
||||||
|
required this.onHubManagerChanged,
|
||||||
required this.onPositionAdded,
|
required this.onPositionAdded,
|
||||||
required this.onPositionUpdated,
|
required this.onPositionUpdated,
|
||||||
required this.onPositionRemoved,
|
required this.onPositionRemoved,
|
||||||
|
required this.hubManagers,
|
||||||
|
required this.selectedHubManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String eventName;
|
final String eventName;
|
||||||
@@ -227,10 +240,15 @@ class _RecurringOrderForm extends StatelessWidget {
|
|||||||
final ValueChanged<DateTime> onEndDateChanged;
|
final ValueChanged<DateTime> onEndDateChanged;
|
||||||
final ValueChanged<int> onDayToggled;
|
final ValueChanged<int> onDayToggled;
|
||||||
final ValueChanged<OrderHubUiModel> onHubChanged;
|
final ValueChanged<OrderHubUiModel> onHubChanged;
|
||||||
|
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
|
||||||
final VoidCallback onPositionAdded;
|
final VoidCallback onPositionAdded;
|
||||||
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
|
||||||
final void Function(int index) onPositionRemoved;
|
final void Function(int index) onPositionRemoved;
|
||||||
|
|
||||||
|
final List<OrderManagerUiModel> hubManagers;
|
||||||
|
final OrderManagerUiModel? selectedHubManager;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final TranslationsClientCreateOrderRecurringEn labels =
|
final TranslationsClientCreateOrderRecurringEn labels =
|
||||||
@@ -351,6 +369,16 @@ class _RecurringOrderForm extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
HubManagerSelector(
|
||||||
|
label: oneTimeLabels.hub_manager_label,
|
||||||
|
description: oneTimeLabels.hub_manager_desc,
|
||||||
|
hintText: oneTimeLabels.hub_manager_hint,
|
||||||
|
managers: hubManagers,
|
||||||
|
selectedManager: selectedHubManager,
|
||||||
|
onChanged: onHubManagerChanged,
|
||||||
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
RecurringOrderSectionHeader(
|
RecurringOrderSectionHeader(
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
|||||||
const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
|
const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
|
||||||
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
|
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
|
||||||
|
|
||||||
|
List<dc.ListTeamMembersTeamMembers> _managers = const <dc.ListTeamMembersTeamMembers>[];
|
||||||
|
dc.ListTeamMembersTeamMembers? _selectedManager;
|
||||||
|
|
||||||
String? _shiftId;
|
String? _shiftId;
|
||||||
List<_ShiftRoleKey> _originalShiftRoles = const <_ShiftRoleKey>[];
|
List<_ShiftRoleKey> _originalShiftRoles = const <_ShiftRoleKey>[];
|
||||||
|
|
||||||
@@ -246,6 +249,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (selected != null) {
|
||||||
|
await _loadManagersForHub(selected.id, widget.order.hubManagerId);
|
||||||
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -331,6 +337,47 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadManagersForHub(String hubId, [String? preselectedId]) async {
|
||||||
|
try {
|
||||||
|
final QueryResult<dc.ListTeamMembersData, void> result =
|
||||||
|
await _dataConnect.listTeamMembers().execute();
|
||||||
|
|
||||||
|
final List<dc.ListTeamMembersTeamMembers> hubManagers = result.data.teamMembers
|
||||||
|
.where(
|
||||||
|
(dc.ListTeamMembersTeamMembers member) =>
|
||||||
|
member.teamHubId == hubId &&
|
||||||
|
member.role is dc.Known<dc.TeamMemberRole> &&
|
||||||
|
(member.role as dc.Known<dc.TeamMemberRole>).value ==
|
||||||
|
dc.TeamMemberRole.MANAGER,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
dc.ListTeamMembersTeamMembers? selected;
|
||||||
|
if (preselectedId != null && preselectedId.isNotEmpty) {
|
||||||
|
for (final dc.ListTeamMembersTeamMembers m in hubManagers) {
|
||||||
|
if (m.id == preselectedId) {
|
||||||
|
selected = m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_managers = hubManagers;
|
||||||
|
_selectedManager = selected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_managers = const <dc.ListTeamMembersTeamMembers>[];
|
||||||
|
_selectedManager = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _emptyPosition() {
|
Map<String, dynamic> _emptyPosition() {
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'shiftId': _shiftId,
|
'shiftId': _shiftId,
|
||||||
@@ -744,6 +791,10 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
_buildHubManagerSelector(),
|
||||||
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -807,6 +858,130 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildHubManagerSelector() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
_buildSectionHeader('SHIFT CONTACT'),
|
||||||
|
Text('On-site manager or supervisor for this shift', style: UiTypography.body2r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _showHubManagerSelector(),
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: 14,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
border: Border.all(
|
||||||
|
color: _selectedManager != null ? UiColors.primary : UiColors.border,
|
||||||
|
width: _selectedManager != null ? 2 : 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(
|
||||||
|
UiIcons.user,
|
||||||
|
color: _selectedManager != null
|
||||||
|
? UiColors.primary
|
||||||
|
: UiColors.iconSecondary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Text(
|
||||||
|
_selectedManager?.user.fullName ?? 'Select Contact',
|
||||||
|
style: _selectedManager != null
|
||||||
|
? UiTypography.body1r.textPrimary
|
||||||
|
: UiTypography.body2r.textPlaceholder,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Icon(
|
||||||
|
Icons.keyboard_arrow_down,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showHubManagerSelector() async {
|
||||||
|
final dc.ListTeamMembersTeamMembers? selected = await showDialog<dc.ListTeamMembersTeamMembers?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'Shift Contact',
|
||||||
|
style: UiTypography.headline3m.textPrimary,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 400),
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: _managers.isEmpty ? 2 : _managers.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (_managers.isEmpty) {
|
||||||
|
if (index == 0) {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
|
child: Text('No hub managers available'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('None', style: UiTypography.body1m.textSecondary),
|
||||||
|
onTap: () => Navigator.of(context).pop(null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == _managers.length) {
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('None', style: UiTypography.body1m.textSecondary),
|
||||||
|
onTap: () => Navigator.of(context).pop(null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final dc.ListTeamMembersTeamMembers manager = _managers[index];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
|
title: Text(manager.user.fullName ?? 'Unknown', style: UiTypography.body1m.textPrimary),
|
||||||
|
onTap: () => Navigator.of(context).pop(manager),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
if (selected == null && _managers.isEmpty) {
|
||||||
|
// Tapped outside or selected None
|
||||||
|
setState(() => _selectedManager = null);
|
||||||
|
} else {
|
||||||
|
setState(() => _selectedManager = selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
|
||||||
@@ -938,7 +1113,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
|||||||
context: context,
|
context: context,
|
||||||
initialTime: TimeOfDay.now(),
|
initialTime: TimeOfDay.now(),
|
||||||
);
|
);
|
||||||
if (picked != null && context.mounted) {
|
if (picked != null && mounted) {
|
||||||
_updatePosition(
|
_updatePosition(
|
||||||
index,
|
index,
|
||||||
'start_time',
|
'start_time',
|
||||||
@@ -958,7 +1133,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
|||||||
context: context,
|
context: context,
|
||||||
initialTime: TimeOfDay.now(),
|
initialTime: TimeOfDay.now(),
|
||||||
);
|
);
|
||||||
if (picked != null && context.mounted) {
|
if (picked != null && mounted) {
|
||||||
_updatePosition(
|
_updatePosition(
|
||||||
index,
|
index,
|
||||||
'end_time',
|
'end_time',
|
||||||
|
|||||||
@@ -259,6 +259,31 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (order.hubManagerName != null) ...<Widget>[
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 2),
|
||||||
|
child: Icon(
|
||||||
|
UiIcons.user,
|
||||||
|
size: 14,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space2),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
order.hubManagerName!,
|
||||||
|
style: UiTypography.footnote2r.textSecondary,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -24,15 +24,52 @@ class SettingsActions extends StatelessWidget {
|
|||||||
delegate: SliverChildListDelegate(<Widget>[
|
delegate: SliverChildListDelegate(<Widget>[
|
||||||
const SizedBox(height: UiConstants.space5),
|
const SizedBox(height: UiConstants.space5),
|
||||||
|
|
||||||
|
// Edit Profile button (Yellow)
|
||||||
|
UiButton.primary(
|
||||||
|
text: labels.edit_profile,
|
||||||
|
fullWidth: true,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: UiColors.accent,
|
||||||
|
foregroundColor: UiColors.accentForeground,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => Modular.to.toClientEditProfile(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Hubs button (Yellow)
|
||||||
|
UiButton.primary(
|
||||||
|
text: labels.hubs,
|
||||||
|
fullWidth: true,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: UiColors.accent,
|
||||||
|
foregroundColor: UiColors.accentForeground,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => Modular.to.toClientHubs(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space5),
|
||||||
|
|
||||||
// Quick Links card
|
// Quick Links card
|
||||||
_QuickLinksCard(labels: labels),
|
_QuickLinksCard(labels: labels),
|
||||||
const SizedBox(height: UiConstants.space4),
|
const SizedBox(height: UiConstants.space5),
|
||||||
|
|
||||||
// Log Out button (outlined)
|
// Log Out button (outlined)
|
||||||
BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
|
BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
|
||||||
builder: (BuildContext context, ClientSettingsState state) {
|
builder: (BuildContext context, ClientSettingsState state) {
|
||||||
return UiButton.secondary(
|
return UiButton.secondary(
|
||||||
text: labels.log_out,
|
text: labels.log_out,
|
||||||
|
fullWidth: true,
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: const BorderSide(color: UiColors.black),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase * 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: state is ClientSettingsLoading
|
onPressed: state is ClientSettingsLoading
|
||||||
? null
|
? null
|
||||||
: () => _showSignOutDialog(context),
|
: () => _showSignOutDialog(context),
|
||||||
@@ -113,7 +150,7 @@ class _QuickLinksCard extends StatelessWidget {
|
|||||||
onTap: () => Modular.to.toClientHubs(),
|
onTap: () => Modular.to.toClientHubs(),
|
||||||
),
|
),
|
||||||
_QuickLinkItem(
|
_QuickLinkItem(
|
||||||
icon: UiIcons.building,
|
icon: UiIcons.file,
|
||||||
title: labels.billing_payments,
|
title: labels.billing_payments,
|
||||||
onTap: () => Modular.to.toClientBilling(),
|
onTap: () => Modular.to.toClientBilling(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class SettingsProfileHeader extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// ── Top bar: back arrow + title ──────────────────
|
// ── Top bar: back arrow + centered title ─────────
|
||||||
SafeArea(
|
SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -39,21 +39,25 @@ class SettingsProfileHeader extends StatelessWidget {
|
|||||||
horizontal: UiConstants.space4,
|
horizontal: UiConstants.space4,
|
||||||
vertical: UiConstants.space2,
|
vertical: UiConstants.space2,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
GestureDetector(
|
Align(
|
||||||
onTap: () => Modular.to.toClientHome(),
|
alignment: Alignment.centerLeft,
|
||||||
child: const Icon(
|
child: GestureDetector(
|
||||||
UiIcons.arrowLeft,
|
onTap: () => Modular.to.toClientHome(),
|
||||||
color: UiColors.white,
|
child: const Icon(
|
||||||
size: 22,
|
UiIcons.arrowLeft,
|
||||||
|
color: UiColors.white,
|
||||||
|
size: 22,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Text(
|
Text(
|
||||||
labels.title,
|
labels.title,
|
||||||
style: UiTypography.body1b.copyWith(
|
style: UiTypography.body1b.copyWith(
|
||||||
color: UiColors.white,
|
color: UiColors.white,
|
||||||
|
fontSize: 18,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ mutation createOrder(
|
|||||||
$shifts: Any
|
$shifts: Any
|
||||||
$requested: Int
|
$requested: Int
|
||||||
$teamHubId: UUID!
|
$teamHubId: UUID!
|
||||||
|
$hubManagerId: UUID
|
||||||
$recurringDays: [String!]
|
$recurringDays: [String!]
|
||||||
$permanentStartDate: Timestamp
|
$permanentStartDate: Timestamp
|
||||||
$permanentDays: [String!]
|
$permanentDays: [String!]
|
||||||
@@ -40,6 +41,7 @@ mutation createOrder(
|
|||||||
shifts: $shifts
|
shifts: $shifts
|
||||||
requested: $requested
|
requested: $requested
|
||||||
teamHubId: $teamHubId
|
teamHubId: $teamHubId
|
||||||
|
hubManagerId: $hubManagerId
|
||||||
recurringDays: $recurringDays
|
recurringDays: $recurringDays
|
||||||
permanentDays: $permanentDays
|
permanentDays: $permanentDays
|
||||||
notes: $notes
|
notes: $notes
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ type Order @table(name: "orders", key: ["id"]) {
|
|||||||
teamHubId: UUID!
|
teamHubId: UUID!
|
||||||
teamHub: TeamHub! @ref(fields: "teamHubId", references: "id")
|
teamHub: TeamHub! @ref(fields: "teamHubId", references: "id")
|
||||||
|
|
||||||
|
hubManagerId: UUID
|
||||||
|
hubManager: TeamMember @ref(fields: "hubManagerId", references: "id")
|
||||||
|
|
||||||
date: Timestamp
|
date: Timestamp
|
||||||
|
|
||||||
startDate: Timestamp #for recurring and permanent
|
startDate: Timestamp #for recurring and permanent
|
||||||
|
|||||||
Reference in New Issue
Block a user