171 lines
6.1 KiB
Dart
171 lines
6.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|
|
|
import '../core/formatters.dart';
|
|
import '../core/theme.dart';
|
|
import '../models/shift.dart';
|
|
import 'common.dart';
|
|
|
|
/// The one card shape in the app. Identity on top, a hairline split, then the
|
|
/// money band — with a status bar welded to the bottom edge.
|
|
class ShiftCard extends StatelessWidget {
|
|
const ShiftCard({
|
|
super.key,
|
|
required this.shift,
|
|
this.onTap,
|
|
this.trailingAction,
|
|
this.showDate = false,
|
|
});
|
|
|
|
final Shift shift;
|
|
final VoidCallback? onTap;
|
|
|
|
/// Optional action shown in the money band, e.g. an Apply button.
|
|
final Widget? trailingAction;
|
|
final bool showDate;
|
|
|
|
(String, ChipTone) get _status => switch (shift.status) {
|
|
ShiftStatus.open => ('Open', ChipTone.blue),
|
|
ShiftStatus.booked => (Fmt.untilStart(shift.start), ChipTone.yellow),
|
|
ShiftStatus.active => ('On shift', ChipTone.yellow),
|
|
ShiftStatus.completed => ('Completed', ChipTone.neutral),
|
|
ShiftStatus.cancelled => ('Cancelled', ChipTone.muted),
|
|
ShiftStatus.noShow => ('No show', ChipTone.danger),
|
|
};
|
|
|
|
Color get _railColor => switch (shift.status) {
|
|
ShiftStatus.active => KrowPalette.yellow,
|
|
ShiftStatus.cancelled || ShiftStatus.noShow => const Color(0xFFCFDDD3),
|
|
_ => KrowPalette.blue,
|
|
};
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final (statusLabel, statusTone) = _status;
|
|
final unpaid =
|
|
shift.status == ShiftStatus.cancelled ||
|
|
shift.status == ShiftStatus.noShow;
|
|
|
|
return Material(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(18),
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(18),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(18),
|
|
border: Border.all(color: KrowPalette.line),
|
|
),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
BusinessBadge(shift.logoLetter),
|
|
const SizedBox(width: 13),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
shift.role,
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.5,
|
|
height: 1.15,
|
|
),
|
|
),
|
|
const SizedBox(height: 3),
|
|
Text(
|
|
shift.venue,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(
|
|
fontSize: 12.5,
|
|
color: KrowPalette.mute,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
StatusChip(statusLabel, tone: statusTone),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Wrap(
|
|
spacing: 7,
|
|
runSpacing: 7,
|
|
children: [
|
|
if (showDate)
|
|
MetaPill(
|
|
LucideIcons.calendar,
|
|
Fmt.dayShort(shift.start),
|
|
),
|
|
MetaPill(
|
|
LucideIcons.clock,
|
|
Fmt.window(shift.start, shift.end),
|
|
),
|
|
MetaPill(
|
|
LucideIcons.mapPin,
|
|
'${shift.distanceMiles} mi',
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Divider(height: 1),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
unpaid
|
|
? 'No pay generated'
|
|
: '${Fmt.money(shift.rate)}/hr · '
|
|
'${Fmt.duration(shift.scheduledDuration)}',
|
|
style: const TextStyle(
|
|
fontSize: 11.5,
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: 0.2,
|
|
color: Color(0xFF949FA5),
|
|
),
|
|
),
|
|
),
|
|
if (trailingAction != null)
|
|
trailingAction!
|
|
else
|
|
Text(
|
|
Fmt.money(shift.pay),
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.7,
|
|
color: unpaid
|
|
? const Color(0xFF949FA5)
|
|
: KrowPalette.ink,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(height: 4, color: _railColor),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|