hub & manager issues

This commit is contained in:
2026-02-25 19:58:28 +05:30
parent 27754524f5
commit eeb8c28a61
53 changed files with 1571 additions and 245 deletions

View File

@@ -31,6 +31,8 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
on<OneTimeOrderPositionUpdated>(_onPositionUpdated);
on<OneTimeOrderSubmitted>(_onSubmitted);
on<OneTimeOrderInitialized>(_onInitialized);
on<OneTimeOrderHubManagerChanged>(_onHubManagerChanged);
on<OneTimeOrderManagersLoaded>(_onManagersLoaded);
_loadVendors();
_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(
OneTimeOrderVendorsLoaded event,
Emitter<OneTimeOrderState> emit,
@@ -171,15 +210,36 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
location: selectedHub?.name ?? '',
),
);
if (selectedHub != null) {
_loadManagersForHub(selectedHub.id);
}
}
void _onHubChanged(
OneTimeOrderHubChanged event,
Emitter<OneTimeOrderState> emit,
) {
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(
OneTimeOrderEventNameChanged event,
Emitter<OneTimeOrderState> emit,
@@ -267,6 +327,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
),
eventName: state.eventName,
vendorId: state.selectedVendor?.id,
hubManagerId: state.selectedManager?.id,
roleRates: roleRates,
);
await _createOneTimeOrderUseCase(OneTimeOrderArguments(order: order));

View File

@@ -89,3 +89,21 @@ class OneTimeOrderInitialized extends OneTimeOrderEvent {
@override
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];
}

View File

@@ -16,6 +16,8 @@ class OneTimeOrderState extends Equatable {
this.hubs = const <OneTimeOrderHubOption>[],
this.selectedHub,
this.roles = const <OneTimeOrderRoleOption>[],
this.managers = const <OneTimeOrderManagerOption>[],
this.selectedManager,
});
factory OneTimeOrderState.initial() {
@@ -29,6 +31,7 @@ class OneTimeOrderState extends Equatable {
vendors: const <Vendor>[],
hubs: const <OneTimeOrderHubOption>[],
roles: const <OneTimeOrderRoleOption>[],
managers: const <OneTimeOrderManagerOption>[],
);
}
final DateTime date;
@@ -42,6 +45,8 @@ class OneTimeOrderState extends Equatable {
final List<OneTimeOrderHubOption> hubs;
final OneTimeOrderHubOption? selectedHub;
final List<OneTimeOrderRoleOption> roles;
final List<OneTimeOrderManagerOption> managers;
final OneTimeOrderManagerOption? selectedManager;
OneTimeOrderState copyWith({
DateTime? date,
@@ -55,6 +60,8 @@ class OneTimeOrderState extends Equatable {
List<OneTimeOrderHubOption>? hubs,
OneTimeOrderHubOption? selectedHub,
List<OneTimeOrderRoleOption>? roles,
List<OneTimeOrderManagerOption>? managers,
OneTimeOrderManagerOption? selectedManager,
}) {
return OneTimeOrderState(
date: date ?? this.date,
@@ -68,6 +75,8 @@ class OneTimeOrderState extends Equatable {
hubs: hubs ?? this.hubs,
selectedHub: selectedHub ?? this.selectedHub,
roles: roles ?? this.roles,
managers: managers ?? this.managers,
selectedManager: selectedManager ?? this.selectedManager,
);
}
@@ -98,6 +107,8 @@ class OneTimeOrderState extends Equatable {
hubs,
selectedHub,
roles,
managers,
selectedManager,
];
}
@@ -158,3 +169,17 @@ class OneTimeOrderRoleOption extends Equatable {
@override
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];
}

View File

@@ -31,6 +31,8 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
on<PermanentOrderPositionUpdated>(_onPositionUpdated);
on<PermanentOrderSubmitted>(_onSubmitted);
on<PermanentOrderInitialized>(_onInitialized);
on<PermanentOrderHubManagerChanged>(_onHubManagerChanged);
on<PermanentOrderManagersLoaded>(_onManagersLoaded);
_loadVendors();
_loadHubs();
@@ -182,6 +184,10 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
location: selectedHub?.name ?? '',
),
);
if (selectedHub != null) {
_loadManagersForHub(selectedHub.id, emit);
}
}
void _onHubChanged(
@@ -189,8 +195,61 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
Emitter<PermanentOrderState> emit,
) {
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(
PermanentOrderEventNameChanged event,
Emitter<PermanentOrderState> emit,
@@ -330,6 +389,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
),
eventName: state.eventName,
vendorId: state.selectedVendor?.id,
hubManagerId: state.selectedManager?.id,
roleRates: roleRates,
);
await _createPermanentOrderUseCase(order);

View File

@@ -106,3 +106,20 @@ class PermanentOrderInitialized extends PermanentOrderEvent {
@override
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];
}

View File

@@ -18,6 +18,8 @@ class PermanentOrderState extends Equatable {
this.hubs = const <PermanentOrderHubOption>[],
this.selectedHub,
this.roles = const <PermanentOrderRoleOption>[],
this.managers = const <PermanentOrderManagerOption>[],
this.selectedManager,
});
factory PermanentOrderState.initial() {
@@ -45,6 +47,7 @@ class PermanentOrderState extends Equatable {
vendors: const <Vendor>[],
hubs: const <PermanentOrderHubOption>[],
roles: const <PermanentOrderRoleOption>[],
managers: const <PermanentOrderManagerOption>[],
);
}
@@ -61,6 +64,8 @@ class PermanentOrderState extends Equatable {
final List<PermanentOrderHubOption> hubs;
final PermanentOrderHubOption? selectedHub;
final List<PermanentOrderRoleOption> roles;
final List<PermanentOrderManagerOption> managers;
final PermanentOrderManagerOption? selectedManager;
PermanentOrderState copyWith({
DateTime? startDate,
@@ -76,6 +81,8 @@ class PermanentOrderState extends Equatable {
List<PermanentOrderHubOption>? hubs,
PermanentOrderHubOption? selectedHub,
List<PermanentOrderRoleOption>? roles,
List<PermanentOrderManagerOption>? managers,
PermanentOrderManagerOption? selectedManager,
}) {
return PermanentOrderState(
startDate: startDate ?? this.startDate,
@@ -91,6 +98,8 @@ class PermanentOrderState extends Equatable {
hubs: hubs ?? this.hubs,
selectedHub: selectedHub ?? this.selectedHub,
roles: roles ?? this.roles,
managers: managers ?? this.managers,
selectedManager: selectedManager ?? this.selectedManager,
);
}
@@ -124,6 +133,8 @@ class PermanentOrderState extends Equatable {
hubs,
selectedHub,
roles,
managers,
selectedManager,
];
}
@@ -185,6 +196,20 @@ class PermanentOrderRoleOption extends Equatable {
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 {
const PermanentOrderPosition({
required this.role,

View File

@@ -32,6 +32,8 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
on<RecurringOrderPositionUpdated>(_onPositionUpdated);
on<RecurringOrderSubmitted>(_onSubmitted);
on<RecurringOrderInitialized>(_onInitialized);
on<RecurringOrderHubManagerChanged>(_onHubManagerChanged);
on<RecurringOrderManagersLoaded>(_onManagersLoaded);
_loadVendors();
_loadHubs();
@@ -183,6 +185,10 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
location: selectedHub?.name ?? '',
),
);
if (selectedHub != null) {
_loadManagersForHub(selectedHub.id, emit);
}
}
void _onHubChanged(
@@ -190,6 +196,58 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
Emitter<RecurringOrderState> emit,
) {
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(
@@ -349,6 +407,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
),
eventName: state.eventName,
vendorId: state.selectedVendor?.id,
hubManagerId: state.selectedManager?.id,
roleRates: roleRates,
);
await _createRecurringOrderUseCase(order);

View File

@@ -115,3 +115,20 @@ class RecurringOrderInitialized extends RecurringOrderEvent {
@override
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];
}

View File

@@ -19,6 +19,8 @@ class RecurringOrderState extends Equatable {
this.hubs = const <RecurringOrderHubOption>[],
this.selectedHub,
this.roles = const <RecurringOrderRoleOption>[],
this.managers = const <RecurringOrderManagerOption>[],
this.selectedManager,
});
factory RecurringOrderState.initial() {
@@ -47,6 +49,7 @@ class RecurringOrderState extends Equatable {
vendors: const <Vendor>[],
hubs: const <RecurringOrderHubOption>[],
roles: const <RecurringOrderRoleOption>[],
managers: const <RecurringOrderManagerOption>[],
);
}
@@ -64,6 +67,8 @@ class RecurringOrderState extends Equatable {
final List<RecurringOrderHubOption> hubs;
final RecurringOrderHubOption? selectedHub;
final List<RecurringOrderRoleOption> roles;
final List<RecurringOrderManagerOption> managers;
final RecurringOrderManagerOption? selectedManager;
RecurringOrderState copyWith({
DateTime? startDate,
@@ -80,6 +85,8 @@ class RecurringOrderState extends Equatable {
List<RecurringOrderHubOption>? hubs,
RecurringOrderHubOption? selectedHub,
List<RecurringOrderRoleOption>? roles,
List<RecurringOrderManagerOption>? managers,
RecurringOrderManagerOption? selectedManager,
}) {
return RecurringOrderState(
startDate: startDate ?? this.startDate,
@@ -96,6 +103,8 @@ class RecurringOrderState extends Equatable {
hubs: hubs ?? this.hubs,
selectedHub: selectedHub ?? this.selectedHub,
roles: roles ?? this.roles,
managers: managers ?? this.managers,
selectedManager: selectedManager ?? this.selectedManager,
);
}
@@ -132,6 +141,8 @@ class RecurringOrderState extends Equatable {
hubs,
selectedHub,
roles,
managers,
selectedManager,
];
}
@@ -193,6 +204,20 @@ class RecurringOrderRoleOption extends Equatable {
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 {
const RecurringOrderPosition({
required this.role,

View File

@@ -48,6 +48,10 @@ class OneTimeOrderPage extends StatelessWidget {
hubs: state.hubs.map(_mapHub).toList(),
positions: state.positions.map(_mapPosition).toList(),
roles: state.roles.map(_mapRole).toList(),
selectedHubManager: state.selectedManager != null
? _mapManager(state.selectedManager!)
: null,
hubManagers: state.managers.map(_mapManager).toList(),
isValid: state.isValid,
onEventNameChanged: (String val) =>
bloc.add(OneTimeOrderEventNameChanged(val)),
@@ -61,6 +65,17 @@ class OneTimeOrderPage extends StatelessWidget {
);
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()),
onPositionUpdated: (int index, OrderPositionUiModel val) {
final OneTimeOrderPosition original = state.positions[index];
@@ -130,4 +145,9 @@ class OneTimeOrderPage extends StatelessWidget {
lunchBreak: pos.lunchBreak,
);
}
OrderManagerUiModel _mapManager(OneTimeOrderManagerOption manager) {
return OrderManagerUiModel(id: manager.id, name: manager.name);
}
}

View File

@@ -42,6 +42,10 @@ class PermanentOrderPage extends StatelessWidget {
? _mapHub(state.selectedHub!)
: null,
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(),
roles: state.roles.map(_mapRole).toList(),
isValid: state.isValid,
@@ -59,6 +63,17 @@ class PermanentOrderPage extends StatelessWidget {
);
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: () =>
bloc.add(const PermanentOrderPositionAdded()),
onPositionUpdated: (int index, OrderPositionUiModel val) {
@@ -181,4 +196,8 @@ class PermanentOrderPage extends StatelessWidget {
lunchBreak: pos.lunchBreak ?? 'NO_BREAK',
);
}
OrderManagerUiModel _mapManager(PermanentOrderManagerOption manager) {
return OrderManagerUiModel(id: manager.id, name: manager.name);
}
}

View File

@@ -43,6 +43,10 @@ class RecurringOrderPage extends StatelessWidget {
? _mapHub(state.selectedHub!)
: null,
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(),
roles: state.roles.map(_mapRole).toList(),
isValid: state.isValid,
@@ -62,6 +66,17 @@ class RecurringOrderPage extends StatelessWidget {
);
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: () =>
bloc.add(const RecurringOrderPositionAdded()),
onPositionUpdated: (int index, OrderPositionUiModel val) {
@@ -193,4 +208,8 @@ class RecurringOrderPage extends StatelessWidget {
lunchBreak: pos.lunchBreak ?? 'NO_BREAK',
);
}
OrderManagerUiModel _mapManager(RecurringOrderManagerOption manager) {
return OrderManagerUiModel(id: manager.id, name: manager.name);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart';
import '../order_ui_models.dart';
import '../hub_manager_selector.dart';
import 'one_time_order_date_picker.dart';
import 'one_time_order_event_name_input.dart';
import 'one_time_order_header.dart';
@@ -23,11 +24,14 @@ class OneTimeOrderView extends StatelessWidget {
required this.hubs,
required this.positions,
required this.roles,
required this.hubManagers,
required this.selectedHubManager,
required this.isValid,
required this.onEventNameChanged,
required this.onVendorChanged,
required this.onDateChanged,
required this.onHubChanged,
required this.onHubManagerChanged,
required this.onPositionAdded,
required this.onPositionUpdated,
required this.onPositionRemoved,
@@ -47,12 +51,15 @@ class OneTimeOrderView extends StatelessWidget {
final List<OrderHubUiModel> hubs;
final List<OrderPositionUiModel> positions;
final List<OrderRoleUiModel> roles;
final List<OrderManagerUiModel> hubManagers;
final OrderManagerUiModel? selectedHubManager;
final bool isValid;
final ValueChanged<String> onEventNameChanged;
final ValueChanged<Vendor> onVendorChanged;
final ValueChanged<DateTime> onDateChanged;
final ValueChanged<OrderHubUiModel> onHubChanged;
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
final VoidCallback onPositionAdded;
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
final void Function(int index) onPositionRemoved;
@@ -143,12 +150,15 @@ class OneTimeOrderView extends StatelessWidget {
date: date,
selectedHub: selectedHub,
hubs: hubs,
selectedHubManager: selectedHubManager,
hubManagers: hubManagers,
positions: positions,
roles: roles,
onEventNameChanged: onEventNameChanged,
onVendorChanged: onVendorChanged,
onDateChanged: onDateChanged,
onHubChanged: onHubChanged,
onHubManagerChanged: onHubManagerChanged,
onPositionAdded: onPositionAdded,
onPositionUpdated: onPositionUpdated,
onPositionRemoved: onPositionRemoved,
@@ -179,12 +189,15 @@ class _OneTimeOrderForm extends StatelessWidget {
required this.date,
required this.selectedHub,
required this.hubs,
required this.selectedHubManager,
required this.hubManagers,
required this.positions,
required this.roles,
required this.onEventNameChanged,
required this.onVendorChanged,
required this.onDateChanged,
required this.onHubChanged,
required this.onHubManagerChanged,
required this.onPositionAdded,
required this.onPositionUpdated,
required this.onPositionRemoved,
@@ -196,6 +209,8 @@ class _OneTimeOrderForm extends StatelessWidget {
final DateTime date;
final OrderHubUiModel? selectedHub;
final List<OrderHubUiModel> hubs;
final OrderManagerUiModel? selectedHubManager;
final List<OrderManagerUiModel> hubManagers;
final List<OrderPositionUiModel> positions;
final List<OrderRoleUiModel> roles;
@@ -203,6 +218,7 @@ class _OneTimeOrderForm extends StatelessWidget {
final ValueChanged<Vendor> onVendorChanged;
final ValueChanged<DateTime> onDateChanged;
final ValueChanged<OrderHubUiModel> onHubChanged;
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
final VoidCallback onPositionAdded;
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
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),
OneTimeOrderSectionHeader(

View File

@@ -94,3 +94,19 @@ class OrderPositionUiModel extends Equatable {
@override
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];
}

View File

@@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart' show Vendor;
import '../order_ui_models.dart';
import '../hub_manager_selector.dart';
import 'permanent_order_date_picker.dart';
import 'permanent_order_event_name_input.dart';
import 'permanent_order_header.dart';
@@ -24,12 +25,15 @@ class PermanentOrderView extends StatelessWidget {
required this.hubs,
required this.positions,
required this.roles,
required this.hubManagers,
required this.selectedHubManager,
required this.isValid,
required this.onEventNameChanged,
required this.onVendorChanged,
required this.onStartDateChanged,
required this.onDayToggled,
required this.onHubChanged,
required this.onHubManagerChanged,
required this.onPositionAdded,
required this.onPositionUpdated,
required this.onPositionRemoved,
@@ -48,6 +52,8 @@ class PermanentOrderView extends StatelessWidget {
final List<String> permanentDays;
final OrderHubUiModel? selectedHub;
final List<OrderHubUiModel> hubs;
final OrderManagerUiModel? selectedHubManager;
final List<OrderManagerUiModel> hubManagers;
final List<OrderPositionUiModel> positions;
final List<OrderRoleUiModel> roles;
final bool isValid;
@@ -57,6 +63,7 @@ class PermanentOrderView extends StatelessWidget {
final ValueChanged<DateTime> onStartDateChanged;
final ValueChanged<int> onDayToggled;
final ValueChanged<OrderHubUiModel> onHubChanged;
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
final VoidCallback onPositionAdded;
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
final void Function(int index) onPositionRemoved;
@@ -156,9 +163,12 @@ class PermanentOrderView extends StatelessWidget {
onStartDateChanged: onStartDateChanged,
onDayToggled: onDayToggled,
onHubChanged: onHubChanged,
onHubManagerChanged: onHubManagerChanged,
onPositionAdded: onPositionAdded,
onPositionUpdated: onPositionUpdated,
onPositionRemoved: onPositionRemoved,
hubManagers: hubManagers,
selectedHubManager: selectedHubManager,
),
if (status == OrderFormStatus.loading)
const Center(child: CircularProgressIndicator()),
@@ -194,9 +204,12 @@ class _PermanentOrderForm extends StatelessWidget {
required this.onStartDateChanged,
required this.onDayToggled,
required this.onHubChanged,
required this.onHubManagerChanged,
required this.onPositionAdded,
required this.onPositionUpdated,
required this.onPositionRemoved,
required this.hubManagers,
required this.selectedHubManager,
});
final String eventName;
@@ -214,10 +227,14 @@ class _PermanentOrderForm extends StatelessWidget {
final ValueChanged<DateTime> onStartDateChanged;
final ValueChanged<int> onDayToggled;
final ValueChanged<OrderHubUiModel> onHubChanged;
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
final VoidCallback onPositionAdded;
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
final void Function(int index) onPositionRemoved;
final List<OrderManagerUiModel> hubManagers;
final OrderManagerUiModel? selectedHubManager;
@override
Widget build(BuildContext context) {
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),
PermanentOrderSectionHeader(

View File

@@ -3,6 +3,7 @@ import 'package:krow_domain/krow_domain.dart' show Vendor;
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import '../order_ui_models.dart';
import '../hub_manager_selector.dart';
import 'recurring_order_date_picker.dart';
import 'recurring_order_event_name_input.dart';
import 'recurring_order_header.dart';
@@ -25,6 +26,8 @@ class RecurringOrderView extends StatelessWidget {
required this.hubs,
required this.positions,
required this.roles,
required this.hubManagers,
required this.selectedHubManager,
required this.isValid,
required this.onEventNameChanged,
required this.onVendorChanged,
@@ -32,6 +35,7 @@ class RecurringOrderView extends StatelessWidget {
required this.onEndDateChanged,
required this.onDayToggled,
required this.onHubChanged,
required this.onHubManagerChanged,
required this.onPositionAdded,
required this.onPositionUpdated,
required this.onPositionRemoved,
@@ -51,6 +55,8 @@ class RecurringOrderView extends StatelessWidget {
final List<String> recurringDays;
final OrderHubUiModel? selectedHub;
final List<OrderHubUiModel> hubs;
final OrderManagerUiModel? selectedHubManager;
final List<OrderManagerUiModel> hubManagers;
final List<OrderPositionUiModel> positions;
final List<OrderRoleUiModel> roles;
final bool isValid;
@@ -61,6 +67,7 @@ class RecurringOrderView extends StatelessWidget {
final ValueChanged<DateTime> onEndDateChanged;
final ValueChanged<int> onDayToggled;
final ValueChanged<OrderHubUiModel> onHubChanged;
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
final VoidCallback onPositionAdded;
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
final void Function(int index) onPositionRemoved;
@@ -165,9 +172,12 @@ class RecurringOrderView extends StatelessWidget {
onEndDateChanged: onEndDateChanged,
onDayToggled: onDayToggled,
onHubChanged: onHubChanged,
onHubManagerChanged: onHubManagerChanged,
onPositionAdded: onPositionAdded,
onPositionUpdated: onPositionUpdated,
onPositionRemoved: onPositionRemoved,
hubManagers: hubManagers,
selectedHubManager: selectedHubManager,
),
if (status == OrderFormStatus.loading)
const Center(child: CircularProgressIndicator()),
@@ -205,9 +215,12 @@ class _RecurringOrderForm extends StatelessWidget {
required this.onEndDateChanged,
required this.onDayToggled,
required this.onHubChanged,
required this.onHubManagerChanged,
required this.onPositionAdded,
required this.onPositionUpdated,
required this.onPositionRemoved,
required this.hubManagers,
required this.selectedHubManager,
});
final String eventName;
@@ -227,10 +240,15 @@ class _RecurringOrderForm extends StatelessWidget {
final ValueChanged<DateTime> onEndDateChanged;
final ValueChanged<int> onDayToggled;
final ValueChanged<OrderHubUiModel> onHubChanged;
final ValueChanged<OrderManagerUiModel?> onHubManagerChanged;
final VoidCallback onPositionAdded;
final void Function(int index, OrderPositionUiModel position) onPositionUpdated;
final void Function(int index) onPositionRemoved;
final List<OrderManagerUiModel> hubManagers;
final OrderManagerUiModel? selectedHubManager;
@override
Widget build(BuildContext context) {
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),
RecurringOrderSectionHeader(

View File

@@ -57,6 +57,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
List<dc.ListTeamMembersTeamMembers> _managers = const <dc.ListTeamMembersTeamMembers>[];
dc.ListTeamMembersTeamMembers? _selectedManager;
String? _shiftId;
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 (_) {
if (mounted) {
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() {
return <String, dynamic>{
'shiftId': _shiftId,
@@ -744,6 +791,10 @@ class OrderEditSheetState extends State<OrderEditSheet> {
),
),
),
const SizedBox(height: UiConstants.space4),
_buildHubManagerSelector(),
const SizedBox(height: UiConstants.space6),
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() {
return Container(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
@@ -938,7 +1113,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null && context.mounted) {
if (picked != null && mounted) {
_updatePosition(
index,
'start_time',
@@ -958,7 +1133,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null && context.mounted) {
if (picked != null && mounted) {
_updatePosition(
index,
'end_time',

View File

@@ -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,
),
),
],
),
],
],
),
),