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:
Achintha Isuru
2026-02-23 12:14:28 -05:00
parent 7136aa7686
commit f453f8aadd
6 changed files with 218 additions and 258 deletions

View File

@@ -205,6 +205,14 @@ class UiTypography {
color: UiColors.textPrimary, 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) /// Headline 4 Medium - Font: Instrument Sans, Size: 22, Height: 1.5 (#121826)
static final TextStyle headline4m = _primaryBase.copyWith( static final TextStyle headline4m = _primaryBase.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -354,6 +362,15 @@ class UiTypography {
color: UiColors.textPrimary, 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) /// Body 4 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.05 (#121826)
static final TextStyle body4r = _primaryBase.copyWith( static final TextStyle body4r = _primaryBase.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,

View File

@@ -88,10 +88,6 @@ class _BillingViewState extends State<BillingView> {
controller: _scrollController, controller: _scrollController,
slivers: <Widget>[ slivers: <Widget>[
SliverAppBar( 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, pinned: true,
expandedHeight: 200.0, expandedHeight: 200.0,
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
@@ -227,13 +223,6 @@ class _BillingViewState extends State<BillingView> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: UiConstants.space4, spacing: UiConstants.space4,
children: <Widget>[ 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>[ if (state.pendingInvoices.isNotEmpty) ...<Widget>[
PendingInvoicesSection(invoices: state.pendingInvoices), PendingInvoicesSection(invoices: state.pendingInvoices),
], ],

View File

@@ -77,10 +77,11 @@ class _StatCard extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.all(UiConstants.space3), padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgMenu, color: color.withAlpha(10),
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: UiColors.border, color: color,
width: 0.75,
), ),
), ),
child: Column( child: Column(

View File

@@ -1,11 +1,8 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.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. /// List of shifts with their workers.
/// ///
@@ -235,19 +232,6 @@ class _ShiftHeader extends StatelessWidget {
total: total, total: total,
coveragePercent: coveragePercent, 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; Color text;
if (coveragePercent >= 100) { if (coveragePercent >= 100) {
bg = UiColors.textSuccess; bg = UiColors.textSuccess.withAlpha(40);
text = UiColors.primaryForeground; text = UiColors.textSuccess;
} else if (coveragePercent >= 80) { } else if (coveragePercent >= 80) {
bg = UiColors.textWarning; bg = UiColors.textWarning.withAlpha(40);
text = UiColors.primaryForeground; text = UiColors.textWarning;
} else { } else {
bg = UiColors.destructive; bg = UiColors.destructive.withAlpha(40);
text = UiColors.destructiveForeground; text = UiColors.destructive;
} }
return Container( return Container(
@@ -295,11 +279,12 @@ class _CoverageBadge extends StatelessWidget {
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: bg, color: bg,
borderRadius: UiConstants.radiusFull, border: Border.all(color: text, width: 0.75),
borderRadius: UiConstants.radiusMd,
), ),
child: Text( child: Text(
'$current/$total', '$current/$total',
style: UiTypography.body3m.copyWith( style: UiTypography.body3b.copyWith(
color: text, color: text,
), ),
), ),
@@ -335,92 +320,101 @@ class _WorkerRow extends StatelessWidget {
String statusText; String statusText;
Color badgeBg; Color badgeBg;
Color badgeText; Color badgeText;
Color badgeBorder;
String badgeLabel; String badgeLabel;
switch (worker.status) { switch (worker.status) {
case CoverageWorkerStatus.checkedIn: case CoverageWorkerStatus.checkedIn:
bg = UiColors.textSuccess.withOpacity(0.1); bg = UiColors.textSuccess.withAlpha(26);
border = UiColors.textSuccess; border = UiColors.textSuccess;
textBg = UiColors.textSuccess.withOpacity(0.2); textBg = UiColors.textSuccess.withAlpha(51);
textColor = UiColors.textSuccess; textColor = UiColors.textSuccess;
icon = UiIcons.success; icon = UiIcons.success;
statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}'; statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}';
badgeBg = UiColors.textSuccess; badgeBg = UiColors.textSuccess.withAlpha(40);
badgeText = UiColors.primaryForeground; badgeText = UiColors.textSuccess;
badgeBorder = badgeText;
badgeLabel = 'On Site'; badgeLabel = 'On Site';
case CoverageWorkerStatus.confirmed: case CoverageWorkerStatus.confirmed:
if (worker.checkInTime == null) { if (worker.checkInTime == null) {
bg = UiColors.textWarning.withOpacity(0.1); bg = UiColors.textWarning.withAlpha(26);
border = UiColors.textWarning; border = UiColors.textWarning;
textBg = UiColors.textWarning.withOpacity(0.2); textBg = UiColors.textWarning.withAlpha(51);
textColor = UiColors.textWarning; textColor = UiColors.textWarning;
icon = UiIcons.clock; icon = UiIcons.clock;
statusText = 'En Route - Expected $shiftStartTime'; statusText = 'En Route - Expected $shiftStartTime';
badgeBg = UiColors.textWarning; badgeBg = UiColors.textWarning.withAlpha(40);
badgeText = UiColors.primaryForeground; badgeText = UiColors.textWarning;
badgeBorder = badgeText;
badgeLabel = 'En Route'; badgeLabel = 'En Route';
} else { } else {
bg = UiColors.muted.withOpacity(0.1); bg = UiColors.muted.withAlpha(26);
border = UiColors.border; border = UiColors.border;
textBg = UiColors.muted.withOpacity(0.2); textBg = UiColors.muted.withAlpha(51);
textColor = UiColors.textSecondary; textColor = UiColors.textSecondary;
icon = UiIcons.success; icon = UiIcons.success;
statusText = 'Confirmed'; statusText = 'Confirmed';
badgeBg = UiColors.muted; badgeBg = UiColors.textSecondary.withAlpha(40);
badgeText = UiColors.textPrimary; badgeText = UiColors.textSecondary;
badgeBorder = badgeText;
badgeLabel = 'Confirmed'; badgeLabel = 'Confirmed';
} }
case CoverageWorkerStatus.late: case CoverageWorkerStatus.late:
bg = UiColors.destructive.withOpacity(0.1); bg = UiColors.destructive.withAlpha(26);
border = UiColors.destructive; border = UiColors.destructive;
textBg = UiColors.destructive.withOpacity(0.2); textBg = UiColors.destructive.withAlpha(51);
textColor = UiColors.destructive; textColor = UiColors.destructive;
icon = UiIcons.warning; icon = UiIcons.warning;
statusText = '⚠ Running Late'; statusText = '⚠ Running Late';
badgeBg = UiColors.destructive; badgeBg = UiColors.destructive.withAlpha(40);
badgeText = UiColors.destructiveForeground; badgeText = UiColors.destructive;
badgeBorder = badgeText;
badgeLabel = 'Late'; badgeLabel = 'Late';
case CoverageWorkerStatus.checkedOut: case CoverageWorkerStatus.checkedOut:
bg = UiColors.muted.withOpacity(0.1); bg = UiColors.muted.withAlpha(26);
border = UiColors.border; border = UiColors.border;
textBg = UiColors.muted.withOpacity(0.2); textBg = UiColors.muted.withAlpha(51);
textColor = UiColors.textSecondary; textColor = UiColors.textSecondary;
icon = UiIcons.success; icon = UiIcons.success;
statusText = 'Checked Out'; statusText = 'Checked Out';
badgeBg = UiColors.muted; badgeBg = UiColors.textSecondary.withAlpha(40);
badgeText = UiColors.textPrimary; badgeText = UiColors.textSecondary;
badgeBorder = badgeText;
badgeLabel = 'Done'; badgeLabel = 'Done';
case CoverageWorkerStatus.noShow: case CoverageWorkerStatus.noShow:
bg = UiColors.destructive.withOpacity(0.1); bg = UiColors.destructive.withAlpha(26);
border = UiColors.destructive; border = UiColors.destructive;
textBg = UiColors.destructive.withOpacity(0.2); textBg = UiColors.destructive.withAlpha(51);
textColor = UiColors.destructive; textColor = UiColors.destructive;
icon = UiIcons.warning; icon = UiIcons.warning;
statusText = 'No Show'; statusText = 'No Show';
badgeBg = UiColors.destructive; badgeBg = UiColors.destructive.withAlpha(40);
badgeText = UiColors.destructiveForeground; badgeText = UiColors.destructive;
badgeBorder = badgeText;
badgeLabel = 'No Show'; badgeLabel = 'No Show';
case CoverageWorkerStatus.completed: case CoverageWorkerStatus.completed:
bg = UiColors.textSuccess.withOpacity(0.1); bg = UiColors.iconSuccess.withAlpha(26);
border = UiColors.textSuccess; border = UiColors.iconSuccess;
textBg = UiColors.textSuccess.withOpacity(0.2); textBg = UiColors.iconSuccess.withAlpha(51);
textColor = UiColors.textSuccess; textColor = UiColors.textSuccess;
icon = UiIcons.success; icon = UiIcons.success;
statusText = 'Completed'; statusText = 'Completed';
badgeBg = UiColors.textSuccess; badgeBg = UiColors.textSuccess.withAlpha(40);
badgeText = UiColors.primaryForeground; badgeText = UiColors.textSuccess;
badgeBorder = badgeText;
badgeLabel = 'Completed'; badgeLabel = 'Completed';
case CoverageWorkerStatus.pending: case CoverageWorkerStatus.pending:
case CoverageWorkerStatus.accepted: case CoverageWorkerStatus.accepted:
case CoverageWorkerStatus.rejected: case CoverageWorkerStatus.rejected:
bg = UiColors.muted.withOpacity(0.1); bg = UiColors.muted.withAlpha(26);
border = UiColors.border; border = UiColors.border;
textBg = UiColors.muted.withOpacity(0.2); textBg = UiColors.muted.withAlpha(51);
textColor = UiColors.textSecondary; textColor = UiColors.textSecondary;
icon = UiIcons.clock; icon = UiIcons.clock;
statusText = worker.status.name.toUpperCase(); statusText = worker.status.name.toUpperCase();
badgeBg = UiColors.muted; badgeBg = UiColors.textSecondary.withAlpha(40);
badgeText = UiColors.textPrimary; badgeText = UiColors.textSecondary;
badgeBorder = badgeText;
badgeLabel = worker.status.name[0].toUpperCase() + badgeLabel = worker.status.name[0].toUpperCase() +
worker.status.name.substring(1); worker.status.name.substring(1);
} }
@@ -493,40 +487,42 @@ class _WorkerRow extends StatelessWidget {
), ),
), ),
Column( Column(
spacing: UiConstants.space2, spacing: UiConstants.space2,
children: [ children: <Widget>[
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space2, horizontal: UiConstants.space2,
vertical: UiConstants.space1 / 2, vertical: UiConstants.space1 / 2,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: badgeBg, color: badgeBg,
borderRadius: UiConstants.radiusFull, borderRadius: UiConstants.radiusMd,
), border: Border.all(color: badgeBorder, width: 0.5),
child: Text( ),
badgeLabel, child: Text(
style: UiTypography.footnote2b.copyWith( badgeLabel,
color: badgeText, style: UiTypography.footnote2b.copyWith(
), color: badgeText,
), ),
), ),
if (worker.status == CoverageWorkerStatus.checkedIn) ),
UiButton.primary( if (worker.status == CoverageWorkerStatus.checkedIn)
text: context.t.client_coverage.worker_row.verify, UiButton.primary(
size: UiButtonSize.small, text: context.t.client_coverage.worker_row.verify,
onPressed: () { size: UiButtonSize.small,
UiSnackbar.show( onPressed: () {
context, UiSnackbar.show(
message: context.t.client_coverage.worker_row.verified_message( context,
name: worker.name, message:
), context.t.client_coverage.worker_row.verified_message(
type: UiSnackbarType.success, name: worker.name,
); ),
}, type: UiSnackbarType.success,
), );
], },
), ),
],
),
], ],
), ),
); );

View File

@@ -28,11 +28,7 @@ class _ShiftRoleKey {
/// A sophisticated bottom sheet for editing an existing order, /// A sophisticated bottom sheet for editing an existing order,
/// following the Unified Order Flow prototype and matching OneTimeOrderView. /// following the Unified Order Flow prototype and matching OneTimeOrderView.
class OrderEditSheet extends StatefulWidget { class OrderEditSheet extends StatefulWidget {
const OrderEditSheet({ const OrderEditSheet({required this.order, this.onUpdated, super.key});
required this.order,
this.onUpdated,
super.key,
});
final OrderItem order; final OrderItem order;
final VoidCallback? onUpdated; final VoidCallback? onUpdated;
@@ -57,7 +53,8 @@ class OrderEditSheetState extends State<OrderEditSheet> {
List<Vendor> _vendors = const <Vendor>[]; List<Vendor> _vendors = const <Vendor>[];
Vendor? _selectedVendor; Vendor? _selectedVendor;
List<_RoleOption> _roles = const <_RoleOption>[]; List<_RoleOption> _roles = const <_RoleOption>[];
List<dc.ListTeamHubsByOwnerIdTeamHubs> _hubs = const <dc.ListTeamHubsByOwnerIdTeamHubs>[]; List<dc.ListTeamHubsByOwnerIdTeamHubs> _hubs =
const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub; dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
String? _shiftId; String? _shiftId;
@@ -111,8 +108,10 @@ class OrderEditSheetState extends State<OrderEditSheet> {
try { try {
final QueryResult< final QueryResult<
dc.ListShiftRolesByBusinessAndOrderData, dc.ListShiftRolesByBusinessAndOrderData,
dc.ListShiftRolesByBusinessAndOrderVariables> result = await _dataConnect dc.ListShiftRolesByBusinessAndOrderVariables
>
result = await _dataConnect
.listShiftRolesByBusinessAndOrder( .listShiftRolesByBusinessAndOrder(
businessId: businessId, businessId: businessId,
orderId: widget.order.orderId, orderId: widget.order.orderId,
@@ -139,8 +138,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
_orderNameController.text = firstShift.order.eventName ?? ''; _orderNameController.text = firstShift.order.eventName ?? '';
_shiftId = shiftRoles.first.shiftId; _shiftId = shiftRoles.first.shiftId;
final List<Map<String, dynamic>> positions = final List<Map<String, dynamic>> positions = shiftRoles.map((
shiftRoles.map((dc.ListShiftRolesByBusinessAndOrderShiftRoles role) { dc.ListShiftRolesByBusinessAndOrderShiftRoles role,
) {
return <String, dynamic>{ return <String, dynamic>{
'shiftId': role.shiftId, 'shiftId': role.shiftId,
'roleId': role.roleId, 'roleId': role.roleId,
@@ -158,13 +158,12 @@ class OrderEditSheetState extends State<OrderEditSheet> {
positions.add(_emptyPosition()); positions.add(_emptyPosition());
} }
final List<_ShiftRoleKey> originalShiftRoles = final List<_ShiftRoleKey> originalShiftRoles = shiftRoles
shiftRoles .map(
.map( (dc.ListShiftRolesByBusinessAndOrderShiftRoles role) =>
(dc.ListShiftRolesByBusinessAndOrderShiftRoles role) => _ShiftRoleKey(shiftId: role.shiftId, roleId: role.roleId),
_ShiftRoleKey(shiftId: role.shiftId, roleId: role.roleId), )
) .toList();
.toList();
await _loadVendorsAndSelect(firstShift.order.vendorId); await _loadVendorsAndSelect(firstShift.order.vendorId);
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub
@@ -199,8 +198,10 @@ class OrderEditSheetState extends State<OrderEditSheet> {
try { try {
final QueryResult< final QueryResult<
dc.ListTeamHubsByOwnerIdData, dc.ListTeamHubsByOwnerIdData,
dc.ListTeamHubsByOwnerIdVariables> result = await _dataConnect dc.ListTeamHubsByOwnerIdVariables
>
result = await _dataConnect
.listTeamHubsByOwnerId(ownerId: businessId) .listTeamHubsByOwnerId(ownerId: businessId)
.execute(); .execute();
@@ -257,8 +258,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
Future<void> _loadVendorsAndSelect(String? selectedVendorId) async { Future<void> _loadVendorsAndSelect(String? selectedVendorId) async {
try { try {
final QueryResult<dc.ListVendorsData, void> result = final QueryResult<dc.ListVendorsData, void> result = await _dataConnect
await _dataConnect.listVendors().execute(); .listVendors()
.execute();
final List<Vendor> vendors = result.data.vendors final List<Vendor> vendors = result.data.vendors
.map( .map(
(dc.ListVendorsVendors vendor) => Vendor( (dc.ListVendorsVendors vendor) => Vendor(
@@ -303,10 +305,13 @@ class OrderEditSheetState extends State<OrderEditSheet> {
Future<void> _loadRolesForVendor(String vendorId) async { Future<void> _loadRolesForVendor(String vendorId) async {
try { try {
final QueryResult<dc.ListRolesByVendorIdData, dc.ListRolesByVendorIdVariables> final QueryResult<
result = await _dataConnect dc.ListRolesByVendorIdData,
.listRolesByVendorId(vendorId: vendorId) dc.ListRolesByVendorIdVariables
.execute(); >
result = await _dataConnect
.listRolesByVendorId(vendorId: vendorId)
.execute();
final List<_RoleOption> roles = result.data.roles final List<_RoleOption> roles = result.data.roles
.map( .map(
(dc.ListRolesByVendorIdRoles role) => _RoleOption( (dc.ListRolesByVendorIdRoles role) => _RoleOption(
@@ -350,8 +355,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
} }
String _breakValueFromDuration(dc.EnumValue<dc.BreakDuration>? breakType) { String _breakValueFromDuration(dc.EnumValue<dc.BreakDuration>? breakType) {
final dc.BreakDuration? value = final dc.BreakDuration? value = breakType is dc.Known<dc.BreakDuration>
breakType is dc.Known<dc.BreakDuration> ? breakType.value : null; ? breakType.value
: null;
switch (value) { switch (value) {
case dc.BreakDuration.MIN_10: case dc.BreakDuration.MIN_10:
return 'MIN_10'; return 'MIN_10';
@@ -450,8 +456,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
final DateTime date = _parseDate(_dateController.text); final DateTime date = _parseDate(_dateController.text);
final DateTime start = _parseTime(date, pos['start_time'].toString()); final DateTime start = _parseTime(date, pos['start_time'].toString());
final DateTime end = _parseTime(date, pos['end_time'].toString()); final DateTime end = _parseTime(date, pos['end_time'].toString());
final DateTime normalizedEnd = final DateTime normalizedEnd = end.isBefore(start)
end.isBefore(start) ? end.add(const Duration(days: 1)) : end; ? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0; final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = _rateForRole(roleId); final double rate = _rateForRole(roleId);
final int count = pos['count'] as int; final int count = pos['count'] as int;
@@ -481,8 +488,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
int totalWorkers = 0; int totalWorkers = 0;
double shiftCost = 0; double shiftCost = 0;
final List<_ShiftRoleKey> remainingOriginal = final List<_ShiftRoleKey> remainingOriginal = List<_ShiftRoleKey>.from(
List<_ShiftRoleKey>.from(_originalShiftRoles); _originalShiftRoles,
);
for (final Map<String, dynamic> pos in _positions) { for (final Map<String, dynamic> pos in _positions) {
final String roleId = pos['roleId']?.toString() ?? ''; final String roleId = pos['roleId']?.toString() ?? '';
@@ -492,10 +500,14 @@ class OrderEditSheetState extends State<OrderEditSheet> {
final String shiftId = pos['shiftId']?.toString() ?? _shiftId!; final String shiftId = pos['shiftId']?.toString() ?? _shiftId!;
final int count = pos['count'] as int; 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 end = _parseTime(orderDate, pos['end_time'].toString());
final DateTime normalizedEnd = final DateTime normalizedEnd = end.isBefore(start)
end.isBefore(start) ? end.add(const Duration(days: 1)) : end; ? end.add(const Duration(days: 1))
: end;
final double hours = normalizedEnd.difference(start).inMinutes / 60.0; final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
final double rate = _rateForRole(roleId); final double rate = _rateForRole(roleId);
final double totalValue = rate * hours * count; final double totalValue = rate * hours * count;
@@ -516,11 +528,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
.deleteShiftRole(shiftId: shiftId, roleId: originalRoleId) .deleteShiftRole(shiftId: shiftId, roleId: originalRoleId)
.execute(); .execute();
await _dataConnect await _dataConnect
.createShiftRole( .createShiftRole(shiftId: shiftId, roleId: roleId, count: count)
shiftId: shiftId,
roleId: roleId,
count: count,
)
.startTime(_toTimestamp(start)) .startTime(_toTimestamp(start))
.endTime(_toTimestamp(normalizedEnd)) .endTime(_toTimestamp(normalizedEnd))
.hours(hours) .hours(hours)
@@ -542,11 +550,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
} }
} else { } else {
await _dataConnect await _dataConnect
.createShiftRole( .createShiftRole(shiftId: shiftId, roleId: roleId, count: count)
shiftId: shiftId,
roleId: roleId,
count: count,
)
.startTime(_toTimestamp(start)) .startTime(_toTimestamp(start))
.endTime(_toTimestamp(normalizedEnd)) .endTime(_toTimestamp(normalizedEnd))
.hours(hours) .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) { void _removePosition(int index) {
if (_positions.length > 1) { if (_positions.length > 1) {
setState(() => _positions.removeAt(index)); setState(() => _positions.removeAt(index));
@@ -766,8 +722,7 @@ class OrderEditSheetState extends State<OrderEditSheet> {
size: 18, size: 18,
color: UiColors.iconSecondary, color: UiColors.iconSecondary,
), ),
onChanged: onChanged: (dc.ListTeamHubsByOwnerIdTeamHubs? hub) {
(dc.ListTeamHubsByOwnerIdTeamHubs? hub) {
if (hub != null) { if (hub != null) {
setState(() { setState(() {
_selectedHub = hub; _selectedHub = hub;
@@ -775,18 +730,17 @@ class OrderEditSheetState extends State<OrderEditSheet> {
}); });
} }
}, },
items: _hubs.map( items: _hubs.map((dc.ListTeamHubsByOwnerIdTeamHubs hub) {
(dc.ListTeamHubsByOwnerIdTeamHubs hub) { return DropdownMenuItem<
return DropdownMenuItem< dc.ListTeamHubsByOwnerIdTeamHubs
dc.ListTeamHubsByOwnerIdTeamHubs>( >(
value: hub, value: hub,
child: Text( child: Text(
hub.hubName, hub.hubName,
style: UiTypography.body2m.textPrimary, style: UiTypography.body2m.textPrimary,
), ),
); );
}, }).toList(),
).toList(),
), ),
), ),
), ),
@@ -810,7 +764,11 @@ class OrderEditSheetState extends State<OrderEditSheet> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
spacing: UiConstants.space2, spacing: UiConstants.space2,
children: <Widget>[ children: <Widget>[
const Icon(UiIcons.add, size: 16, color: UiColors.primary), const Icon(
UiIcons.add,
size: 16,
color: UiColors.primary,
),
Text( Text(
'Add Position', 'Add Position',
style: UiTypography.body2m.primary, style: UiTypography.body2m.primary,
@@ -836,21 +794,12 @@ class OrderEditSheetState extends State<OrderEditSheet> {
label: 'Review ${_positions.length} Positions', label: 'Review ${_positions.length} Positions',
onPressed: () => setState(() => _showReview = true), onPressed: () => setState(() => _showReview = true),
), ),
Padding( const Padding(
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
UiConstants.space5, UiConstants.space5,
0, 0,
UiConstants.space5, UiConstants.space5,
MediaQuery.of(context).padding.bottom + UiConstants.space2, 0,
),
child: UiButton.secondary(
text: 'Cancel Entire Order',
style: OutlinedButton.styleFrom(
foregroundColor: UiColors.destructive,
side: const BorderSide(color: UiColors.destructive),
),
fullWidth: true,
onPressed: _cancelOrder,
), ),
), ),
], ],
@@ -863,7 +812,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20), padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: UiColors.primary, color: UiColors.primary,
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)), borderRadius: BorderRadius.vertical(
top: Radius.circular(UiConstants.space6),
),
), ),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
@@ -1279,7 +1230,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
height: MediaQuery.of(context).size.height * 0.95, height: MediaQuery.of(context).size.height * 0.95,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: UiColors.bgSecondary, color: UiColors.bgSecondary,
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)), borderRadius: BorderRadius.vertical(
top: Radius.circular(UiConstants.space6),
),
), ),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
@@ -1515,7 +1468,9 @@ class OrderEditSheetState extends State<OrderEditSheet> {
height: MediaQuery.of(context).size.height * 0.95, height: MediaQuery.of(context).size.height * 0.95,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: UiColors.primary, color: UiColors.primary,
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)), borderRadius: BorderRadius.vertical(
top: Radius.circular(UiConstants.space6),
),
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View File

@@ -203,10 +203,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
), ),
const SizedBox(height: UiConstants.space3), const SizedBox(height: UiConstants.space3),
// Title // Title
Text( Text(order.title, style: UiTypography.headline3b),
order.title,
style: UiTypography.headline3m.textPrimary,
),
Row( Row(
spacing: UiConstants.space1, spacing: UiConstants.space1,
children: <Widget>[ children: <Widget>[
@@ -224,7 +221,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
const SizedBox(height: UiConstants.space4), const SizedBox(height: UiConstants.space4),
// Location (Hub name + Address) // Location (Hub name + Address)
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
const Padding( const Padding(
padding: EdgeInsets.only(top: 2), padding: EdgeInsets.only(top: 2),
@@ -234,7 +231,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
color: UiColors.iconSecondary, color: UiColors.iconSecondary,
), ),
), ),
const SizedBox(width: UiConstants.space1), const SizedBox(width: UiConstants.space2),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -242,8 +239,9 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
if (order.location.isNotEmpty) if (order.location.isNotEmpty)
Text( Text(
order.location, order.location,
style: style: UiTypography
UiTypography.footnote1b.textPrimary, .footnote1b
.textSecondary,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -294,27 +292,32 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
const SizedBox(height: UiConstants.space4), const SizedBox(height: UiConstants.space4),
// Stats Row // Stats Row
Row( Padding(
mainAxisAlignment: MainAxisAlignment.spaceBetween, padding: const EdgeInsets.symmetric(
children: <Widget>[ horizontal: UiConstants.space4,
_buildStatItem( ),
icon: UiIcons.dollar, child: Row(
value: '\$${cost.round()}', mainAxisAlignment: MainAxisAlignment.spaceBetween,
label: t.client_view_orders.card.total, children: <Widget>[
), _buildStatItem(
_buildStatDivider(), icon: UiIcons.dollar,
_buildStatItem( value: '\$${cost.round()}',
icon: UiIcons.clock, label: t.client_view_orders.card.total,
value: hours.toStringAsFixed(1), ),
label: t.client_view_orders.card.hrs, _buildStatDivider(),
), _buildStatItem(
_buildStatDivider(), icon: UiIcons.clock,
_buildStatItem( value: hours.toStringAsFixed(1),
icon: UiIcons.users, label: t.client_view_orders.card.hrs,
value: '${order.workersNeeded}', ),
label: t.client_create_order.one_time.workers_label, _buildStatDivider(),
), _buildStatItem(
], icon: UiIcons.users,
value: '${order.workersNeeded}',
label: t.client_create_order.one_time.workers_label,
),
],
),
), ),
const SizedBox(height: UiConstants.space5), const SizedBox(height: UiConstants.space5),
@@ -486,16 +489,15 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
padding: const EdgeInsets.all(UiConstants.space3), padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgSecondary, color: UiColors.bgSecondary,
borderRadius: UiConstants.radiusMd, borderRadius: UiConstants.radiusLg,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text( Text(
label.toUpperCase(), label.toUpperCase(),
style: UiTypography.titleUppercase4m.textSecondary, style: UiTypography.titleUppercase4m.textSecondary,
), ),
const SizedBox(height: UiConstants.space1),
Text(time, style: UiTypography.body1b.textPrimary), Text(time, style: UiTypography.body1b.textPrimary),
], ],
), ),
@@ -715,12 +717,12 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
required String label, required String label,
}) { }) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Row( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Icon(icon, size: 14, color: UiColors.iconSecondary), Icon(icon, size: 14, color: UiColors.iconSecondary),
const SizedBox(width: 6),
Text(value, style: UiTypography.body1b.textPrimary), Text(value, style: UiTypography.body1b.textPrimary),
], ],
), ),