feat: Refine badge and status indicator styling across various client features, including updated colors, borders, and typography, and remove unused action buttons.
This commit is contained in:
@@ -205,6 +205,14 @@ class UiTypography {
|
||||
color: UiColors.textPrimary,
|
||||
);
|
||||
|
||||
/// Headline 3 Bold - Font: Instrument Sans, Size: 22, Height: 1.5 (#121826)
|
||||
static final TextStyle headline3b = _primaryBase.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20,
|
||||
height: 1.5,
|
||||
color: UiColors.textPrimary,
|
||||
);
|
||||
|
||||
/// Headline 4 Medium - Font: Instrument Sans, Size: 22, Height: 1.5 (#121826)
|
||||
static final TextStyle headline4m = _primaryBase.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -354,6 +362,15 @@ class UiTypography {
|
||||
color: UiColors.textPrimary,
|
||||
);
|
||||
|
||||
/// Body 3 Bold - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: -0.1 (#121826)
|
||||
static final TextStyle body3b = _primaryBase.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12,
|
||||
height: 1.5,
|
||||
letterSpacing: -0.1,
|
||||
color: UiColors.textPrimary,
|
||||
);
|
||||
|
||||
/// Body 4 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.05 (#121826)
|
||||
static final TextStyle body4r = _primaryBase.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
|
||||
@@ -88,10 +88,6 @@ class _BillingViewState extends State<BillingView> {
|
||||
controller: _scrollController,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
// ... (APP BAR CODE REMAINS UNCHANGED, BUT I MUST INCLUDE IT OR CHUNK IT CORRECTLY)
|
||||
// Since I cannot see the headers in this chunk, I will target the _buildContent method instead
|
||||
// to avoid messing up the whole file structure.
|
||||
// Wait, I can just replace the build method wrapper.
|
||||
pinned: true,
|
||||
expandedHeight: 200.0,
|
||||
backgroundColor: UiColors.primary,
|
||||
@@ -227,13 +223,6 @@ class _BillingViewState extends State<BillingView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: UiConstants.space4,
|
||||
children: <Widget>[
|
||||
UiButton.primary(
|
||||
text: 'View Pending Timesheets',
|
||||
leadingIcon: UiIcons.clock,
|
||||
onPressed: () => Modular.to.pushNamed('${ClientPaths.billing}/timesheets'),
|
||||
fullWidth: true,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
if (state.pendingInvoices.isNotEmpty) ...<Widget>[
|
||||
PendingInvoicesSection(invoices: state.pendingInvoices),
|
||||
],
|
||||
|
||||
@@ -77,10 +77,11 @@ class _StatCard extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgMenu,
|
||||
color: color.withAlpha(10),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(
|
||||
color: UiColors.border,
|
||||
color: color,
|
||||
width: 0.75,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../blocs/coverage_bloc.dart';
|
||||
import '../blocs/coverage_event.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
/// List of shifts with their workers.
|
||||
///
|
||||
@@ -235,19 +232,6 @@ class _ShiftHeader extends StatelessWidget {
|
||||
total: total,
|
||||
coveragePercent: coveragePercent,
|
||||
),
|
||||
if (current < total)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: UiConstants.space2),
|
||||
child: UiButton.primary(
|
||||
text: 'Repost',
|
||||
size: UiButtonSize.small,
|
||||
onPressed: () {
|
||||
ReadContext(context).read<CoverageBloc>().add(
|
||||
CoverageRepostShiftRequested(shiftId: shiftId),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -278,14 +262,14 @@ class _CoverageBadge extends StatelessWidget {
|
||||
Color text;
|
||||
|
||||
if (coveragePercent >= 100) {
|
||||
bg = UiColors.textSuccess;
|
||||
text = UiColors.primaryForeground;
|
||||
bg = UiColors.textSuccess.withAlpha(40);
|
||||
text = UiColors.textSuccess;
|
||||
} else if (coveragePercent >= 80) {
|
||||
bg = UiColors.textWarning;
|
||||
text = UiColors.primaryForeground;
|
||||
bg = UiColors.textWarning.withAlpha(40);
|
||||
text = UiColors.textWarning;
|
||||
} else {
|
||||
bg = UiColors.destructive;
|
||||
text = UiColors.destructiveForeground;
|
||||
bg = UiColors.destructive.withAlpha(40);
|
||||
text = UiColors.destructive;
|
||||
}
|
||||
|
||||
return Container(
|
||||
@@ -295,11 +279,12 @@ class _CoverageBadge extends StatelessWidget {
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
border: Border.all(color: text, width: 0.75),
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
),
|
||||
child: Text(
|
||||
'$current/$total',
|
||||
style: UiTypography.body3m.copyWith(
|
||||
style: UiTypography.body3b.copyWith(
|
||||
color: text,
|
||||
),
|
||||
),
|
||||
@@ -335,92 +320,101 @@ class _WorkerRow extends StatelessWidget {
|
||||
String statusText;
|
||||
Color badgeBg;
|
||||
Color badgeText;
|
||||
Color badgeBorder;
|
||||
String badgeLabel;
|
||||
|
||||
switch (worker.status) {
|
||||
case CoverageWorkerStatus.checkedIn:
|
||||
bg = UiColors.textSuccess.withOpacity(0.1);
|
||||
bg = UiColors.textSuccess.withAlpha(26);
|
||||
border = UiColors.textSuccess;
|
||||
textBg = UiColors.textSuccess.withOpacity(0.2);
|
||||
textBg = UiColors.textSuccess.withAlpha(51);
|
||||
textColor = UiColors.textSuccess;
|
||||
icon = UiIcons.success;
|
||||
statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}';
|
||||
badgeBg = UiColors.textSuccess;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeBg = UiColors.textSuccess.withAlpha(40);
|
||||
badgeText = UiColors.textSuccess;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = 'On Site';
|
||||
case CoverageWorkerStatus.confirmed:
|
||||
if (worker.checkInTime == null) {
|
||||
bg = UiColors.textWarning.withOpacity(0.1);
|
||||
bg = UiColors.textWarning.withAlpha(26);
|
||||
border = UiColors.textWarning;
|
||||
textBg = UiColors.textWarning.withOpacity(0.2);
|
||||
textBg = UiColors.textWarning.withAlpha(51);
|
||||
textColor = UiColors.textWarning;
|
||||
icon = UiIcons.clock;
|
||||
statusText = 'En Route - Expected $shiftStartTime';
|
||||
badgeBg = UiColors.textWarning;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeBg = UiColors.textWarning.withAlpha(40);
|
||||
badgeText = UiColors.textWarning;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = 'En Route';
|
||||
} else {
|
||||
bg = UiColors.muted.withOpacity(0.1);
|
||||
bg = UiColors.muted.withAlpha(26);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withOpacity(0.2);
|
||||
textBg = UiColors.muted.withAlpha(51);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.success;
|
||||
statusText = 'Confirmed';
|
||||
badgeBg = UiColors.muted;
|
||||
badgeText = UiColors.textPrimary;
|
||||
badgeBg = UiColors.textSecondary.withAlpha(40);
|
||||
badgeText = UiColors.textSecondary;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = 'Confirmed';
|
||||
}
|
||||
case CoverageWorkerStatus.late:
|
||||
bg = UiColors.destructive.withOpacity(0.1);
|
||||
bg = UiColors.destructive.withAlpha(26);
|
||||
border = UiColors.destructive;
|
||||
textBg = UiColors.destructive.withOpacity(0.2);
|
||||
textBg = UiColors.destructive.withAlpha(51);
|
||||
textColor = UiColors.destructive;
|
||||
icon = UiIcons.warning;
|
||||
statusText = '⚠ Running Late';
|
||||
badgeBg = UiColors.destructive;
|
||||
badgeText = UiColors.destructiveForeground;
|
||||
badgeBg = UiColors.destructive.withAlpha(40);
|
||||
badgeText = UiColors.destructive;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = 'Late';
|
||||
case CoverageWorkerStatus.checkedOut:
|
||||
bg = UiColors.muted.withOpacity(0.1);
|
||||
bg = UiColors.muted.withAlpha(26);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withOpacity(0.2);
|
||||
textBg = UiColors.muted.withAlpha(51);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.success;
|
||||
statusText = 'Checked Out';
|
||||
badgeBg = UiColors.muted;
|
||||
badgeText = UiColors.textPrimary;
|
||||
badgeBg = UiColors.textSecondary.withAlpha(40);
|
||||
badgeText = UiColors.textSecondary;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = 'Done';
|
||||
case CoverageWorkerStatus.noShow:
|
||||
bg = UiColors.destructive.withOpacity(0.1);
|
||||
bg = UiColors.destructive.withAlpha(26);
|
||||
border = UiColors.destructive;
|
||||
textBg = UiColors.destructive.withOpacity(0.2);
|
||||
textBg = UiColors.destructive.withAlpha(51);
|
||||
textColor = UiColors.destructive;
|
||||
icon = UiIcons.warning;
|
||||
statusText = 'No Show';
|
||||
badgeBg = UiColors.destructive;
|
||||
badgeText = UiColors.destructiveForeground;
|
||||
badgeBg = UiColors.destructive.withAlpha(40);
|
||||
badgeText = UiColors.destructive;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = 'No Show';
|
||||
case CoverageWorkerStatus.completed:
|
||||
bg = UiColors.textSuccess.withOpacity(0.1);
|
||||
border = UiColors.textSuccess;
|
||||
textBg = UiColors.textSuccess.withOpacity(0.2);
|
||||
bg = UiColors.iconSuccess.withAlpha(26);
|
||||
border = UiColors.iconSuccess;
|
||||
textBg = UiColors.iconSuccess.withAlpha(51);
|
||||
textColor = UiColors.textSuccess;
|
||||
icon = UiIcons.success;
|
||||
statusText = 'Completed';
|
||||
badgeBg = UiColors.textSuccess;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeBg = UiColors.textSuccess.withAlpha(40);
|
||||
badgeText = UiColors.textSuccess;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = 'Completed';
|
||||
case CoverageWorkerStatus.pending:
|
||||
case CoverageWorkerStatus.accepted:
|
||||
case CoverageWorkerStatus.rejected:
|
||||
bg = UiColors.muted.withOpacity(0.1);
|
||||
bg = UiColors.muted.withAlpha(26);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withOpacity(0.2);
|
||||
textBg = UiColors.muted.withAlpha(51);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.clock;
|
||||
statusText = worker.status.name.toUpperCase();
|
||||
badgeBg = UiColors.muted;
|
||||
badgeText = UiColors.textPrimary;
|
||||
badgeBg = UiColors.textSecondary.withAlpha(40);
|
||||
badgeText = UiColors.textSecondary;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = worker.status.name[0].toUpperCase() +
|
||||
worker.status.name.substring(1);
|
||||
}
|
||||
@@ -493,40 +487,42 @@ class _WorkerRow extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Column(
|
||||
spacing: UiConstants.space2,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space2,
|
||||
vertical: UiConstants.space1 / 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: badgeBg,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
),
|
||||
child: Text(
|
||||
badgeLabel,
|
||||
style: UiTypography.footnote2b.copyWith(
|
||||
color: badgeText,
|
||||
),
|
||||
spacing: UiConstants.space2,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space2,
|
||||
vertical: UiConstants.space1 / 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: badgeBg,
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
border: Border.all(color: badgeBorder, width: 0.5),
|
||||
),
|
||||
child: Text(
|
||||
badgeLabel,
|
||||
style: UiTypography.footnote2b.copyWith(
|
||||
color: badgeText,
|
||||
),
|
||||
),
|
||||
if (worker.status == CoverageWorkerStatus.checkedIn)
|
||||
UiButton.primary(
|
||||
text: context.t.client_coverage.worker_row.verify,
|
||||
size: UiButtonSize.small,
|
||||
onPressed: () {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: context.t.client_coverage.worker_row.verified_message(
|
||||
name: worker.name,
|
||||
),
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (worker.status == CoverageWorkerStatus.checkedIn)
|
||||
UiButton.primary(
|
||||
text: context.t.client_coverage.worker_row.verify,
|
||||
size: UiButtonSize.small,
|
||||
onPressed: () {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message:
|
||||
context.t.client_coverage.worker_row.verified_message(
|
||||
name: worker.name,
|
||||
),
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -28,11 +28,7 @@ class _ShiftRoleKey {
|
||||
/// A sophisticated bottom sheet for editing an existing order,
|
||||
/// following the Unified Order Flow prototype and matching OneTimeOrderView.
|
||||
class OrderEditSheet extends StatefulWidget {
|
||||
const OrderEditSheet({
|
||||
required this.order,
|
||||
this.onUpdated,
|
||||
super.key,
|
||||
});
|
||||
const OrderEditSheet({required this.order, this.onUpdated, super.key});
|
||||
|
||||
final OrderItem order;
|
||||
final VoidCallback? onUpdated;
|
||||
@@ -57,7 +53,8 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
List<Vendor> _vendors = const <Vendor>[];
|
||||
Vendor? _selectedVendor;
|
||||
List<_RoleOption> _roles = const <_RoleOption>[];
|
||||
List<dc.ListTeamHubsByOwnerIdTeamHubs> _hubs = const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
|
||||
List<dc.ListTeamHubsByOwnerIdTeamHubs> _hubs =
|
||||
const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
|
||||
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
|
||||
|
||||
String? _shiftId;
|
||||
@@ -111,8 +108,10 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
|
||||
try {
|
||||
final QueryResult<
|
||||
dc.ListShiftRolesByBusinessAndOrderData,
|
||||
dc.ListShiftRolesByBusinessAndOrderVariables> result = await _dataConnect
|
||||
dc.ListShiftRolesByBusinessAndOrderData,
|
||||
dc.ListShiftRolesByBusinessAndOrderVariables
|
||||
>
|
||||
result = await _dataConnect
|
||||
.listShiftRolesByBusinessAndOrder(
|
||||
businessId: businessId,
|
||||
orderId: widget.order.orderId,
|
||||
@@ -139,8 +138,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
_orderNameController.text = firstShift.order.eventName ?? '';
|
||||
_shiftId = shiftRoles.first.shiftId;
|
||||
|
||||
final List<Map<String, dynamic>> positions =
|
||||
shiftRoles.map((dc.ListShiftRolesByBusinessAndOrderShiftRoles role) {
|
||||
final List<Map<String, dynamic>> positions = shiftRoles.map((
|
||||
dc.ListShiftRolesByBusinessAndOrderShiftRoles role,
|
||||
) {
|
||||
return <String, dynamic>{
|
||||
'shiftId': role.shiftId,
|
||||
'roleId': role.roleId,
|
||||
@@ -158,13 +158,12 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
positions.add(_emptyPosition());
|
||||
}
|
||||
|
||||
final List<_ShiftRoleKey> originalShiftRoles =
|
||||
shiftRoles
|
||||
.map(
|
||||
(dc.ListShiftRolesByBusinessAndOrderShiftRoles role) =>
|
||||
_ShiftRoleKey(shiftId: role.shiftId, roleId: role.roleId),
|
||||
)
|
||||
.toList();
|
||||
final List<_ShiftRoleKey> originalShiftRoles = shiftRoles
|
||||
.map(
|
||||
(dc.ListShiftRolesByBusinessAndOrderShiftRoles role) =>
|
||||
_ShiftRoleKey(shiftId: role.shiftId, roleId: role.roleId),
|
||||
)
|
||||
.toList();
|
||||
|
||||
await _loadVendorsAndSelect(firstShift.order.vendorId);
|
||||
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub
|
||||
@@ -199,8 +198,10 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
|
||||
try {
|
||||
final QueryResult<
|
||||
dc.ListTeamHubsByOwnerIdData,
|
||||
dc.ListTeamHubsByOwnerIdVariables> result = await _dataConnect
|
||||
dc.ListTeamHubsByOwnerIdData,
|
||||
dc.ListTeamHubsByOwnerIdVariables
|
||||
>
|
||||
result = await _dataConnect
|
||||
.listTeamHubsByOwnerId(ownerId: businessId)
|
||||
.execute();
|
||||
|
||||
@@ -257,8 +258,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
|
||||
Future<void> _loadVendorsAndSelect(String? selectedVendorId) async {
|
||||
try {
|
||||
final QueryResult<dc.ListVendorsData, void> result =
|
||||
await _dataConnect.listVendors().execute();
|
||||
final QueryResult<dc.ListVendorsData, void> result = await _dataConnect
|
||||
.listVendors()
|
||||
.execute();
|
||||
final List<Vendor> vendors = result.data.vendors
|
||||
.map(
|
||||
(dc.ListVendorsVendors vendor) => Vendor(
|
||||
@@ -303,10 +305,13 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
|
||||
Future<void> _loadRolesForVendor(String vendorId) async {
|
||||
try {
|
||||
final QueryResult<dc.ListRolesByVendorIdData, dc.ListRolesByVendorIdVariables>
|
||||
result = await _dataConnect
|
||||
.listRolesByVendorId(vendorId: vendorId)
|
||||
.execute();
|
||||
final QueryResult<
|
||||
dc.ListRolesByVendorIdData,
|
||||
dc.ListRolesByVendorIdVariables
|
||||
>
|
||||
result = await _dataConnect
|
||||
.listRolesByVendorId(vendorId: vendorId)
|
||||
.execute();
|
||||
final List<_RoleOption> roles = result.data.roles
|
||||
.map(
|
||||
(dc.ListRolesByVendorIdRoles role) => _RoleOption(
|
||||
@@ -350,8 +355,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
}
|
||||
|
||||
String _breakValueFromDuration(dc.EnumValue<dc.BreakDuration>? breakType) {
|
||||
final dc.BreakDuration? value =
|
||||
breakType is dc.Known<dc.BreakDuration> ? breakType.value : null;
|
||||
final dc.BreakDuration? value = breakType is dc.Known<dc.BreakDuration>
|
||||
? breakType.value
|
||||
: null;
|
||||
switch (value) {
|
||||
case dc.BreakDuration.MIN_10:
|
||||
return 'MIN_10';
|
||||
@@ -450,8 +456,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
final DateTime date = _parseDate(_dateController.text);
|
||||
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 DateTime normalizedEnd = end.isBefore(start)
|
||||
? end.add(const Duration(days: 1))
|
||||
: end;
|
||||
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final double rate = _rateForRole(roleId);
|
||||
final int count = pos['count'] as int;
|
||||
@@ -481,8 +488,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
int totalWorkers = 0;
|
||||
double shiftCost = 0;
|
||||
|
||||
final List<_ShiftRoleKey> remainingOriginal =
|
||||
List<_ShiftRoleKey>.from(_originalShiftRoles);
|
||||
final List<_ShiftRoleKey> remainingOriginal = List<_ShiftRoleKey>.from(
|
||||
_originalShiftRoles,
|
||||
);
|
||||
|
||||
for (final Map<String, dynamic> pos in _positions) {
|
||||
final String roleId = pos['roleId']?.toString() ?? '';
|
||||
@@ -492,10 +500,14 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
|
||||
final String shiftId = pos['shiftId']?.toString() ?? _shiftId!;
|
||||
final int count = pos['count'] as int;
|
||||
final DateTime start = _parseTime(orderDate, pos['start_time'].toString());
|
||||
final DateTime start = _parseTime(
|
||||
orderDate,
|
||||
pos['start_time'].toString(),
|
||||
);
|
||||
final DateTime end = _parseTime(orderDate, pos['end_time'].toString());
|
||||
final DateTime normalizedEnd =
|
||||
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
|
||||
final DateTime normalizedEnd = end.isBefore(start)
|
||||
? end.add(const Duration(days: 1))
|
||||
: end;
|
||||
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final double rate = _rateForRole(roleId);
|
||||
final double totalValue = rate * hours * count;
|
||||
@@ -516,11 +528,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
.deleteShiftRole(shiftId: shiftId, roleId: originalRoleId)
|
||||
.execute();
|
||||
await _dataConnect
|
||||
.createShiftRole(
|
||||
shiftId: shiftId,
|
||||
roleId: roleId,
|
||||
count: count,
|
||||
)
|
||||
.createShiftRole(shiftId: shiftId, roleId: roleId, count: count)
|
||||
.startTime(_toTimestamp(start))
|
||||
.endTime(_toTimestamp(normalizedEnd))
|
||||
.hours(hours)
|
||||
@@ -542,11 +550,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
}
|
||||
} else {
|
||||
await _dataConnect
|
||||
.createShiftRole(
|
||||
shiftId: shiftId,
|
||||
roleId: roleId,
|
||||
count: count,
|
||||
)
|
||||
.createShiftRole(shiftId: shiftId, roleId: roleId, count: count)
|
||||
.startTime(_toTimestamp(start))
|
||||
.endTime(_toTimestamp(normalizedEnd))
|
||||
.hours(hours)
|
||||
@@ -601,54 +605,6 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _cancelOrder() async {
|
||||
final bool? confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: const Text('Cancel Order'),
|
||||
content: const Text(
|
||||
'Are you sure you want to cancel this order? This action cannot be undone.',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('No, Keep It'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: TextButton.styleFrom(foregroundColor: UiColors.destructive),
|
||||
child: const Text('Yes, Cancel Order'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirm != true) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
await _dataConnect.deleteOrder(id: widget.order.orderId).execute();
|
||||
if (mounted) {
|
||||
widget.onUpdated?.call();
|
||||
Navigator.pop(context);
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: 'Order cancelled successfully',
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: 'Failed to cancel order',
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _removePosition(int index) {
|
||||
if (_positions.length > 1) {
|
||||
setState(() => _positions.removeAt(index));
|
||||
@@ -766,8 +722,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
size: 18,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
onChanged:
|
||||
(dc.ListTeamHubsByOwnerIdTeamHubs? hub) {
|
||||
onChanged: (dc.ListTeamHubsByOwnerIdTeamHubs? hub) {
|
||||
if (hub != null) {
|
||||
setState(() {
|
||||
_selectedHub = hub;
|
||||
@@ -775,18 +730,17 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
});
|
||||
}
|
||||
},
|
||||
items: _hubs.map(
|
||||
(dc.ListTeamHubsByOwnerIdTeamHubs hub) {
|
||||
return DropdownMenuItem<
|
||||
dc.ListTeamHubsByOwnerIdTeamHubs>(
|
||||
value: hub,
|
||||
child: Text(
|
||||
hub.hubName,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
items: _hubs.map((dc.ListTeamHubsByOwnerIdTeamHubs hub) {
|
||||
return DropdownMenuItem<
|
||||
dc.ListTeamHubsByOwnerIdTeamHubs
|
||||
>(
|
||||
value: hub,
|
||||
child: Text(
|
||||
hub.hubName,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -810,7 +764,11 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: UiConstants.space2,
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.add, size: 16, color: UiColors.primary),
|
||||
const Icon(
|
||||
UiIcons.add,
|
||||
size: 16,
|
||||
color: UiColors.primary,
|
||||
),
|
||||
Text(
|
||||
'Add Position',
|
||||
style: UiTypography.body2m.primary,
|
||||
@@ -836,21 +794,12 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
label: 'Review ${_positions.length} Positions',
|
||||
onPressed: () => setState(() => _showReview = true),
|
||||
),
|
||||
Padding(
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
0,
|
||||
UiConstants.space5,
|
||||
MediaQuery.of(context).padding.bottom + UiConstants.space2,
|
||||
),
|
||||
child: UiButton.secondary(
|
||||
text: 'Cancel Entire Order',
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
side: const BorderSide(color: UiColors.destructive),
|
||||
),
|
||||
fullWidth: true,
|
||||
onPressed: _cancelOrder,
|
||||
0,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -863,7 +812,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
|
||||
decoration: const BoxDecoration(
|
||||
color: UiColors.primary,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(UiConstants.space6),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
@@ -1279,7 +1230,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
height: MediaQuery.of(context).size.height * 0.95,
|
||||
decoration: const BoxDecoration(
|
||||
color: UiColors.bgSecondary,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(UiConstants.space6),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
@@ -1515,7 +1468,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
|
||||
height: MediaQuery.of(context).size.height * 0.95,
|
||||
decoration: const BoxDecoration(
|
||||
color: UiColors.primary,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(UiConstants.space6),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -203,10 +203,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
// Title
|
||||
Text(
|
||||
order.title,
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
),
|
||||
Text(order.title, style: UiTypography.headline3b),
|
||||
Row(
|
||||
spacing: UiConstants.space1,
|
||||
children: <Widget>[
|
||||
@@ -224,7 +221,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
// Location (Hub name + Address)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 2),
|
||||
@@ -234,7 +231,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -242,8 +239,9 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
if (order.location.isNotEmpty)
|
||||
Text(
|
||||
order.location,
|
||||
style:
|
||||
UiTypography.footnote1b.textPrimary,
|
||||
style: UiTypography
|
||||
.footnote1b
|
||||
.textSecondary,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -294,27 +292,32 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Stats Row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
_buildStatItem(
|
||||
icon: UiIcons.dollar,
|
||||
value: '\$${cost.round()}',
|
||||
label: t.client_view_orders.card.total,
|
||||
),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem(
|
||||
icon: UiIcons.clock,
|
||||
value: hours.toStringAsFixed(1),
|
||||
label: t.client_view_orders.card.hrs,
|
||||
),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem(
|
||||
icon: UiIcons.users,
|
||||
value: '${order.workersNeeded}',
|
||||
label: t.client_create_order.one_time.workers_label,
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
_buildStatItem(
|
||||
icon: UiIcons.dollar,
|
||||
value: '\$${cost.round()}',
|
||||
label: t.client_view_orders.card.total,
|
||||
),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem(
|
||||
icon: UiIcons.clock,
|
||||
value: hours.toStringAsFixed(1),
|
||||
label: t.client_view_orders.card.hrs,
|
||||
),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem(
|
||||
icon: UiIcons.users,
|
||||
value: '${order.workersNeeded}',
|
||||
label: t.client_create_order.one_time.workers_label,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
@@ -486,16 +489,15 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgSecondary,
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
label.toUpperCase(),
|
||||
style: UiTypography.titleUppercase4m.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(time, style: UiTypography.body1b.textPrimary),
|
||||
],
|
||||
),
|
||||
@@ -715,12 +717,12 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
required String label,
|
||||
}) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 14, color: UiColors.iconSecondary),
|
||||
const SizedBox(width: 6),
|
||||
Text(value, style: UiTypography.body1b.textPrimary),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user