reoder and creation of reaoder
This commit is contained in:
@@ -1,6 +1,27 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||||
|
|
||||||
|
class _RoleOption {
|
||||||
|
const _RoleOption({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.costPerHour,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final double costPerHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VendorOption {
|
||||||
|
const _VendorOption({required this.id, required this.name});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
}
|
||||||
|
|
||||||
/// A bottom sheet form for creating or reordering shifts.
|
/// A bottom sheet form for creating or reordering shifts.
|
||||||
///
|
///
|
||||||
@@ -34,63 +55,18 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
|
|
||||||
late List<Map<String, dynamic>> _positions;
|
late List<Map<String, dynamic>> _positions;
|
||||||
|
|
||||||
final List<String> _roles = <String>[
|
final dc.ExampleConnector _dataConnect = dc.ExampleConnector.instance;
|
||||||
'Server',
|
List<_VendorOption> _vendors = const <_VendorOption>[];
|
||||||
'Bartender',
|
List<_RoleOption> _roles = const <_RoleOption>[];
|
||||||
'Cook',
|
|
||||||
'Busser',
|
|
||||||
'Host',
|
|
||||||
'Barista',
|
|
||||||
'Dishwasher',
|
|
||||||
'Event Staff',
|
|
||||||
'Warehouse Worker',
|
|
||||||
'Retail Associate',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Vendor options
|
|
||||||
final List<Map<String, dynamic>> _vendors = <Map<String, dynamic>>[
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': 'v1',
|
|
||||||
'name': 'Elite Staffing',
|
|
||||||
'rates': <String, double>{
|
|
||||||
'Server': 25.0,
|
|
||||||
'Bartender': 30.0,
|
|
||||||
'Cook': 28.0,
|
|
||||||
'Busser': 18.0,
|
|
||||||
'Host': 20.0,
|
|
||||||
'Barista': 22.0,
|
|
||||||
'Dishwasher': 17.0,
|
|
||||||
'Event Staff': 19.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': 'v2',
|
|
||||||
'name': 'Premier Workforce',
|
|
||||||
'rates': <String, double>{
|
|
||||||
'Server': 22.0,
|
|
||||||
'Bartender': 28.0,
|
|
||||||
'Cook': 25.0,
|
|
||||||
'Busser': 16.0,
|
|
||||||
'Host': 18.0,
|
|
||||||
'Barista': 20.0,
|
|
||||||
'Dishwasher': 15.0,
|
|
||||||
'Event Staff': 18.0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
String? _selectedVendorId;
|
String? _selectedVendorId;
|
||||||
|
|
||||||
final List<int> _lunchBreakOptions = <int>[0, 30, 45, 60];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// Initialize date controller
|
// Initialize date controller (always today for reorder sheet)
|
||||||
final DateTime tomorrow = DateTime.now().add(const Duration(days: 1));
|
final DateTime today = DateTime.now();
|
||||||
final String initialDate = widget.initialData?['date'] ??
|
final String initialDate = today.toIso8601String().split('T')[0];
|
||||||
tomorrow.toIso8601String().split('T')[0];
|
|
||||||
_dateController = TextEditingController(text: initialDate);
|
_dateController = TextEditingController(text: initialDate);
|
||||||
|
|
||||||
// Initialize location controller
|
// Initialize location controller
|
||||||
@@ -100,21 +76,27 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialize vendor selection
|
|
||||||
_selectedVendorId = _vendors.first['id'];
|
|
||||||
|
|
||||||
// Initialize positions
|
// Initialize positions
|
||||||
_positions = <Map<String, dynamic>>[
|
_positions = <Map<String, dynamic>>[
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'role': widget.initialData?['title'] ?? widget.initialData?['role'] ?? '',
|
'roleId': widget.initialData?['roleId'] ?? '',
|
||||||
'count': widget.initialData?['workersNeeded'] ?? widget.initialData?['workers_needed'] ?? 1,
|
'roleName': widget.initialData?['title'] ?? widget.initialData?['role'] ?? '',
|
||||||
'start_time': widget.initialData?['startTime'] ?? widget.initialData?['start_time'] ?? '09:00',
|
'count': widget.initialData?['workersNeeded'] ??
|
||||||
'end_time': widget.initialData?['endTime'] ?? widget.initialData?['end_time'] ?? '17:00',
|
widget.initialData?['workers_needed'] ??
|
||||||
'hourly_rate': widget.initialData?['hourlyRate']?.toDouble() ?? 18.0,
|
1,
|
||||||
'lunch_break': 0,
|
'start_time': widget.initialData?['startTime'] ??
|
||||||
|
widget.initialData?['start_time'] ??
|
||||||
|
'09:00',
|
||||||
|
'end_time': widget.initialData?['endTime'] ??
|
||||||
|
widget.initialData?['end_time'] ??
|
||||||
|
'17:00',
|
||||||
|
'lunch_break': 'NO_BREAK',
|
||||||
'location': null,
|
'location': null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
_loadVendors();
|
||||||
|
_loadOrderDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -127,12 +109,12 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
void _addPosition() {
|
void _addPosition() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_positions.add(<String, dynamic>{
|
_positions.add(<String, dynamic>{
|
||||||
'role': '',
|
'roleId': '',
|
||||||
|
'roleName': '',
|
||||||
'count': 1,
|
'count': 1,
|
||||||
'start_time': '09:00',
|
'start_time': '09:00',
|
||||||
'end_time': '17:00',
|
'end_time': '17:00',
|
||||||
'hourly_rate': 18.0,
|
'lunch_break': 'NO_BREAK',
|
||||||
'lunch_break': 0,
|
|
||||||
'location': null,
|
'location': null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -162,14 +144,27 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
hours = endH - startH;
|
hours = endH - startH;
|
||||||
if (hours < 0) hours += 24;
|
if (hours < 0) hours += 24;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
final String roleId = pos['roleId']?.toString() ?? '';
|
||||||
final double rate = pos['hourly_rate'] ?? 18.0;
|
final double rate = _rateForRole(roleId);
|
||||||
total += hours * rate * (pos['count'] as int);
|
total += hours * rate * (pos['count'] as int);
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getShiftType() {
|
String _getShiftType() {
|
||||||
|
final String? type = widget.initialData?['type']?.toString();
|
||||||
|
if (type != null && type.isNotEmpty) {
|
||||||
|
switch (type) {
|
||||||
|
case 'PERMANENT':
|
||||||
|
return 'Long Term';
|
||||||
|
case 'RECURRING':
|
||||||
|
return 'Multi-Day';
|
||||||
|
case 'RAPID':
|
||||||
|
return 'Rapid';
|
||||||
|
case 'ONE_TIME':
|
||||||
|
return 'One-Time Order';
|
||||||
|
}
|
||||||
|
}
|
||||||
// Determine shift type based on initial data
|
// Determine shift type based on initial data
|
||||||
final dynamic initialData = widget.initialData;
|
final dynamic initialData = widget.initialData;
|
||||||
if (initialData != null) {
|
if (initialData != null) {
|
||||||
@@ -183,14 +178,304 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
return 'One-Time Order';
|
return 'One-Time Order';
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSubmit() {
|
Future<void> _handleSubmit() async {
|
||||||
final Map<String, dynamic> formData = <String, dynamic>{
|
await _submitNewOrder();
|
||||||
'date': _dateController.text,
|
}
|
||||||
'location': _globalLocationController.text,
|
|
||||||
'positions': _positions,
|
Future<void> _submitNewOrder() async {
|
||||||
'total_cost': _calculateTotalCost(),
|
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||||
};
|
if (businessId == null || businessId.isEmpty) {
|
||||||
widget.onSubmit(formData);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DateTime date = DateTime.parse(_dateController.text);
|
||||||
|
final fdc.Timestamp orderTimestamp = _toTimestamp(date);
|
||||||
|
final dc.OrderType orderType =
|
||||||
|
_orderTypeFromValue(widget.initialData?['type']?.toString());
|
||||||
|
|
||||||
|
final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables>
|
||||||
|
orderResult = await _dataConnect
|
||||||
|
.createOrder(
|
||||||
|
businessId: businessId,
|
||||||
|
orderType: orderType,
|
||||||
|
)
|
||||||
|
.vendorId(_selectedVendorId)
|
||||||
|
.location(_globalLocationController.text)
|
||||||
|
.status(dc.OrderStatus.POSTED)
|
||||||
|
.date(orderTimestamp)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
final String? orderId = orderResult.data?.order_insert.id;
|
||||||
|
if (orderId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int workersNeeded = _positions.fold<int>(
|
||||||
|
0,
|
||||||
|
(int sum, Map<String, dynamic> pos) => sum + (pos['count'] as int),
|
||||||
|
);
|
||||||
|
final String shiftTitle =
|
||||||
|
'Shift 1 ${DateFormat('yyyy-MM-dd').format(date)}';
|
||||||
|
final double shiftCost = _calculateTotalCost();
|
||||||
|
|
||||||
|
final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables>
|
||||||
|
shiftResult = await _dataConnect
|
||||||
|
.createShift(title: shiftTitle, orderId: orderId)
|
||||||
|
.date(orderTimestamp)
|
||||||
|
.location(_globalLocationController.text)
|
||||||
|
.locationAddress(_globalLocationController.text)
|
||||||
|
.status(dc.ShiftStatus.PENDING)
|
||||||
|
.workersNeeded(workersNeeded)
|
||||||
|
.filled(0)
|
||||||
|
.durationDays(1)
|
||||||
|
.cost(shiftCost)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
final String? shiftId = shiftResult.data?.shift_insert.id;
|
||||||
|
if (shiftId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Map<String, dynamic> pos in _positions) {
|
||||||
|
final String roleId = pos['roleId']?.toString() ?? '';
|
||||||
|
if (roleId.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final DateTime start = _parseTime(date, pos['start_time'].toString());
|
||||||
|
final DateTime end = _parseTime(date, pos['end_time'].toString());
|
||||||
|
final DateTime normalizedEnd =
|
||||||
|
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
|
||||||
|
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||||
|
final int count = pos['count'] as int;
|
||||||
|
final double rate = _rateForRole(roleId);
|
||||||
|
final double totalValue = rate * hours * count;
|
||||||
|
final String lunchBreak = pos['lunch_break'] as String;
|
||||||
|
|
||||||
|
await _dataConnect
|
||||||
|
.createShiftRole(
|
||||||
|
shiftId: shiftId,
|
||||||
|
roleId: roleId,
|
||||||
|
count: count,
|
||||||
|
)
|
||||||
|
.startTime(_toTimestamp(start))
|
||||||
|
.endTime(_toTimestamp(normalizedEnd))
|
||||||
|
.hours(hours)
|
||||||
|
.breakType(_breakDurationFromValue(lunchBreak))
|
||||||
|
.totalValue(totalValue)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _dataConnect
|
||||||
|
.updateOrder(id: orderId)
|
||||||
|
.shifts(fdc.AnyValue(<String>[shiftId]))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
widget.onSubmit(<String, dynamic>{
|
||||||
|
'orderId': orderId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadVendors() async {
|
||||||
|
try {
|
||||||
|
final fdc.QueryResult<dc.ListVendorsData, void> result =
|
||||||
|
await _dataConnect.listVendors().execute();
|
||||||
|
final List<_VendorOption> vendors = result.data.vendors
|
||||||
|
.map(
|
||||||
|
(dc.ListVendorsVendors vendor) =>
|
||||||
|
_VendorOption(id: vendor.id, name: vendor.companyName),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_vendors = vendors;
|
||||||
|
final String? current = _selectedVendorId;
|
||||||
|
if (current == null ||
|
||||||
|
!vendors.any((_VendorOption v) => v.id == current)) {
|
||||||
|
_selectedVendorId = vendors.isNotEmpty ? vendors.first.id : null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (_selectedVendorId != null) {
|
||||||
|
await _loadRolesForVendor(_selectedVendorId!);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_vendors = const <_VendorOption>[];
|
||||||
|
_roles = const <_RoleOption>[];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRolesForVendor(String vendorId) async {
|
||||||
|
try {
|
||||||
|
final fdc.QueryResult<dc.ListRolesByVendorIdData, dc.ListRolesByVendorIdVariables>
|
||||||
|
result =
|
||||||
|
await _dataConnect.listRolesByVendorId(vendorId: vendorId).execute();
|
||||||
|
final List<_RoleOption> roles = result.data.roles
|
||||||
|
.map(
|
||||||
|
(dc.ListRolesByVendorIdRoles role) => _RoleOption(
|
||||||
|
id: role.id,
|
||||||
|
name: role.name,
|
||||||
|
costPerHour: role.costPerHour,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _roles = roles);
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _roles = const <_RoleOption>[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadOrderDetails() async {
|
||||||
|
final String? orderId = widget.initialData?['orderId']?.toString();
|
||||||
|
if (orderId == null || orderId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||||
|
if (businessId == null || businessId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final fdc.QueryResult<
|
||||||
|
dc.ListShiftRolesByBusinessAndOrderData,
|
||||||
|
dc.ListShiftRolesByBusinessAndOrderVariables> result = await _dataConnect
|
||||||
|
.listShiftRolesByBusinessAndOrder(
|
||||||
|
businessId: businessId,
|
||||||
|
orderId: orderId,
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
final List<dc.ListShiftRolesByBusinessAndOrderShiftRoles> shiftRoles =
|
||||||
|
result.data.shiftRoles;
|
||||||
|
if (shiftRoles.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShift firstShift =
|
||||||
|
shiftRoles.first.shift;
|
||||||
|
_globalLocationController.text = firstShift.order.location ??
|
||||||
|
firstShift.locationAddress ??
|
||||||
|
firstShift.location ??
|
||||||
|
_globalLocationController.text;
|
||||||
|
|
||||||
|
final String? vendorId = firstShift.order.vendorId;
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_selectedVendorId = vendorId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (vendorId != null && vendorId.isNotEmpty) {
|
||||||
|
await _loadRolesForVendor(vendorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> positions =
|
||||||
|
shiftRoles.map((dc.ListShiftRolesByBusinessAndOrderShiftRoles role) {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'roleId': role.roleId,
|
||||||
|
'roleName': role.role.name,
|
||||||
|
'count': role.count,
|
||||||
|
'start_time': _formatTimeForField(role.startTime),
|
||||||
|
'end_time': _formatTimeForField(role.endTime),
|
||||||
|
'lunch_break': _breakValueFromDuration(role.breakType),
|
||||||
|
'location': null,
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_positions = positions;
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// Keep defaults on failure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTimeForField(fdc.Timestamp? value) {
|
||||||
|
if (value == null) return '';
|
||||||
|
try {
|
||||||
|
return DateFormat('HH:mm').format(value.toDateTime());
|
||||||
|
} catch (_) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _breakValueFromDuration(dc.EnumValue<dc.BreakDuration>? breakType) {
|
||||||
|
final dc.BreakDuration? value =
|
||||||
|
breakType is dc.Known<dc.BreakDuration> ? breakType.value : null;
|
||||||
|
switch (value) {
|
||||||
|
case dc.BreakDuration.MIN_15:
|
||||||
|
return 'MIN_15';
|
||||||
|
case dc.BreakDuration.MIN_30:
|
||||||
|
return 'MIN_30';
|
||||||
|
case dc.BreakDuration.NO_BREAK:
|
||||||
|
case null:
|
||||||
|
return 'NO_BREAK';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dc.BreakDuration _breakDurationFromValue(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'MIN_15':
|
||||||
|
return dc.BreakDuration.MIN_15;
|
||||||
|
case 'MIN_30':
|
||||||
|
return dc.BreakDuration.MIN_30;
|
||||||
|
default:
|
||||||
|
return dc.BreakDuration.NO_BREAK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dc.OrderType _orderTypeFromValue(String? value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'PERMANENT':
|
||||||
|
return dc.OrderType.PERMANENT;
|
||||||
|
case 'RECURRING':
|
||||||
|
return dc.OrderType.RECURRING;
|
||||||
|
case 'RAPID':
|
||||||
|
return dc.OrderType.RAPID;
|
||||||
|
case 'ONE_TIME':
|
||||||
|
default:
|
||||||
|
return dc.OrderType.ONE_TIME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_RoleOption? _roleById(String roleId) {
|
||||||
|
for (final _RoleOption role in _roles) {
|
||||||
|
if (role.id == roleId) {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _rateForRole(String roleId) {
|
||||||
|
return _roleById(roleId)?.costPerHour ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime _parseTime(DateTime date, String time) {
|
||||||
|
DateTime parsed;
|
||||||
|
try {
|
||||||
|
parsed = DateFormat.Hm().parse(time);
|
||||||
|
} catch (_) {
|
||||||
|
parsed = DateFormat.jm().parse(time);
|
||||||
|
}
|
||||||
|
return DateTime(
|
||||||
|
date.year,
|
||||||
|
date.month,
|
||||||
|
date.day,
|
||||||
|
parsed.hour,
|
||||||
|
parsed.minute,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fdc.Timestamp _toTimestamp(DateTime date) {
|
||||||
|
final int millis = date.millisecondsSinceEpoch;
|
||||||
|
final int seconds = millis ~/ 1000;
|
||||||
|
final int nanos = (millis % 1000) * 1000000;
|
||||||
|
return fdc.Timestamp(nanos, seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -425,19 +710,18 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
color: UiColors.iconSecondary,
|
color: UiColors.iconSecondary,
|
||||||
),
|
),
|
||||||
style: UiTypography.body2r.textPrimary,
|
style: UiTypography.body2r.textPrimary,
|
||||||
items: _vendors
|
items: _vendors.map((_VendorOption vendor) {
|
||||||
.map(
|
return DropdownMenuItem<String>(
|
||||||
(Map<String, dynamic> vendor) => DropdownMenuItem<String>(
|
value: vendor.id,
|
||||||
value: vendor['id'],
|
child: Text(vendor.name),
|
||||||
child: Text(vendor['name']),
|
);
|
||||||
),
|
}).toList(),
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedVendorId = newValue;
|
_selectedVendorId = newValue;
|
||||||
});
|
});
|
||||||
|
_loadRolesForVendor(newValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -561,19 +845,32 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
|
|
||||||
_buildDropdownField(
|
_buildDropdownField(
|
||||||
hint: 'Select role',
|
hint: 'Select role',
|
||||||
value: pos['role'],
|
value: pos['roleId'],
|
||||||
items: _roles,
|
items: <String>[
|
||||||
itemBuilder: (dynamic role) {
|
..._roles.map((_RoleOption role) => role.id),
|
||||||
final Map<String, dynamic>? vendor = _vendors.firstWhere(
|
if (pos['roleId'] != null &&
|
||||||
(Map<String, dynamic> v) => v['id'] == _selectedVendorId,
|
pos['roleId'].toString().isNotEmpty &&
|
||||||
orElse: () => _vendors.first,
|
!_roles.any(
|
||||||
);
|
(_RoleOption role) => role.id == pos['roleId'].toString(),
|
||||||
final Map<String, dynamic>? rates = vendor?['rates'] as Map<String, dynamic>?;
|
))
|
||||||
final double? rate = rates?[role];
|
pos['roleId'].toString(),
|
||||||
if (rate == null) return role.toString();
|
],
|
||||||
return '$role - \$${rate.toStringAsFixed(0)}/hr';
|
itemBuilder: (dynamic roleId) {
|
||||||
|
final _RoleOption? role = _roleById(roleId.toString());
|
||||||
|
if (role == null) {
|
||||||
|
final String fallback = pos['roleName']?.toString() ?? '';
|
||||||
|
return fallback.isEmpty ? roleId.toString() : fallback;
|
||||||
|
}
|
||||||
|
return '${role.name} - \$${role.costPerHour.toStringAsFixed(0)}/hr';
|
||||||
|
},
|
||||||
|
onChanged: (dynamic val) {
|
||||||
|
final String roleId = val?.toString() ?? '';
|
||||||
|
final _RoleOption? role = _roleById(roleId);
|
||||||
|
setState(() {
|
||||||
|
_positions[index]['roleId'] = roleId;
|
||||||
|
_positions[index]['roleName'] = role?.name ?? '';
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onChanged: (dynamic val) => _updatePosition(index, 'role', val),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
const SizedBox(height: UiConstants.space3),
|
||||||
@@ -592,10 +889,16 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
|||||||
_buildDropdownField(
|
_buildDropdownField(
|
||||||
hint: 'None',
|
hint: 'None',
|
||||||
value: pos['lunch_break'],
|
value: pos['lunch_break'],
|
||||||
items: _lunchBreakOptions,
|
items: <String>['NO_BREAK', 'MIN_15', 'MIN_30'],
|
||||||
itemBuilder: (dynamic minutes) {
|
itemBuilder: (dynamic value) {
|
||||||
if (minutes == 0) return 'None';
|
switch (value.toString()) {
|
||||||
return '$minutes min';
|
case 'MIN_15':
|
||||||
|
return '15 min';
|
||||||
|
case 'MIN_30':
|
||||||
|
return '30 min';
|
||||||
|
default:
|
||||||
|
return 'No Break';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onChanged: (dynamic val) => _updatePosition(index, 'lunch_break', val),
|
onChanged: (dynamic val) => _updatePosition(index, 'lunch_break', val),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user