Initial commit: Krow Worker App
This commit is contained in:
736
lib/screens/profile_detail_screens.dart
Normal file
736
lib/screens/profile_detail_screens.dart
Normal file
@@ -0,0 +1,736 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
import '../core/theme.dart';
|
||||
import '../models/compliance.dart';
|
||||
import '../state/profile_controller.dart';
|
||||
import '../state/seed.dart';
|
||||
import '../widgets/common.dart';
|
||||
|
||||
/// Only called if something happens during a shift. Managers never see these.
|
||||
class EmergencyContactsScreen extends ConsumerWidget {
|
||||
const EmergencyContactsScreen({super.key});
|
||||
|
||||
Future<void> _edit(
|
||||
BuildContext context,
|
||||
WidgetRef ref, {
|
||||
EmergencyContact? existing,
|
||||
int? index,
|
||||
}) async {
|
||||
final name = TextEditingController(text: existing?.name ?? '');
|
||||
final phone = TextEditingController(text: existing?.phone ?? '');
|
||||
var relationship = existing?.relationship ?? 'Spouse';
|
||||
|
||||
const options = ['Spouse', 'Parent', 'Sibling', 'Child', 'Friend', 'Other'];
|
||||
|
||||
final saved = await showModalBottomSheet<bool>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
showDragHandle: true,
|
||||
builder: (context) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom + 20,
|
||||
),
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setSheetState) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
existing == null ? 'Add a contact' : 'Edit contact',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: name,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Full name',
|
||||
prefixIcon: Icon(LucideIcons.user, size: 18),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: phone,
|
||||
keyboardType: TextInputType.phone,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9 +]')),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Mobile',
|
||||
prefixIcon: Icon(LucideIcons.phone, size: 18),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: relationship,
|
||||
decoration: const InputDecoration(labelText: 'Relationship'),
|
||||
items: [
|
||||
for (final o in options)
|
||||
DropdownMenuItem(value: o, child: Text(o)),
|
||||
],
|
||||
onChanged: (v) =>
|
||||
setSheetState(() => relationship = v ?? relationship),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Save contact'),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (saved != true) return;
|
||||
if (name.text.trim().isEmpty || phone.text.trim().isEmpty) return;
|
||||
ref
|
||||
.read(profileProvider.notifier)
|
||||
.saveContact(
|
||||
EmergencyContact(
|
||||
name: name.text.trim(),
|
||||
phone: phone.text.trim(),
|
||||
relationship: relationship,
|
||||
),
|
||||
index: index,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final contacts = ref.watch(profileProvider).contacts;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Emergency contacts')),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 28),
|
||||
children: [
|
||||
const Text(
|
||||
'We only call these people if something happens to you during a '
|
||||
'shift. Managers never see them.',
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
height: 1.55,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
const SectionLabel('Your contacts'),
|
||||
if (contacts.isEmpty)
|
||||
const EmptyState(
|
||||
icon: LucideIcons.phone,
|
||||
title: 'No contact yet',
|
||||
message: 'Add at least one before your first shift.',
|
||||
)
|
||||
else
|
||||
for (var i = 0; i < contacts.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: KrowCard(
|
||||
onTap: () =>
|
||||
_edit(context, ref, existing: contacts[i], index: i),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
color: KrowPalette.blueTint,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.user,
|
||||
size: 19,
|
||||
color: KrowPalette.blue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 13),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
contacts[i].name,
|
||||
style: const TextStyle(
|
||||
fontSize: 15.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.3,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${contacts[i].relationship} · '
|
||||
'${contacts[i].phone}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12.5,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
ref.read(profileProvider.notifier).removeContact(i),
|
||||
icon: const Icon(LucideIcons.trash2, size: 18),
|
||||
color: const Color(0xFF949FA5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _edit(context, ref),
|
||||
icon: const Icon(LucideIcons.plus, size: 18),
|
||||
label: const Text('Add a contact'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// More picks mean more shifts offered.
|
||||
class SkillsScreen extends ConsumerStatefulWidget {
|
||||
const SkillsScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SkillsScreen> createState() => _SkillsScreenState();
|
||||
}
|
||||
|
||||
class _SkillsScreenState extends ConsumerState<SkillsScreen> {
|
||||
final _custom = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_custom.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profile = ref.watch(profileProvider);
|
||||
final controller = ref.read(profileProvider.notifier);
|
||||
final extras = profile.skills.where((s) => !Seed.skills.contains(s));
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Experience & skills'),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Center(
|
||||
child: StatusChip(
|
||||
'${profile.settings.length + profile.skills.length} selected',
|
||||
tone: ChipTone.yellow,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 28),
|
||||
children: [
|
||||
const Text(
|
||||
"Where you've worked",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.8,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Pick every setting you have worked in. More picks mean more '
|
||||
'shifts offered.',
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
height: 1.55,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Wrap(
|
||||
spacing: 7,
|
||||
runSpacing: 7,
|
||||
children: [
|
||||
for (final s in Seed.workSettings)
|
||||
_Tag(
|
||||
label: s,
|
||||
selected: profile.settings.contains(s),
|
||||
onTap: () => controller.toggleSetting(s),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SectionLabel('Skills and trades'),
|
||||
Wrap(
|
||||
spacing: 7,
|
||||
runSpacing: 7,
|
||||
children: [
|
||||
for (final s in Seed.skills)
|
||||
_Tag(
|
||||
label: s,
|
||||
selected: profile.skills.contains(s),
|
||||
onTap: () => controller.toggleSkill(s),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SectionLabel('Anything else you do'),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _custom,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: (v) {
|
||||
controller.addSkill(v);
|
||||
_custom.clear();
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Type a skill...',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 9),
|
||||
SizedBox(
|
||||
width: 52,
|
||||
height: 52,
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
controller.addSkill(_custom.text);
|
||||
_custom.clear();
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: const Size(52, 52),
|
||||
),
|
||||
child: const Icon(LucideIcons.plus, size: 20),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (extras.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 7,
|
||||
runSpacing: 7,
|
||||
children: [
|
||||
for (final s in extras)
|
||||
Chip(
|
||||
label: Text(s),
|
||||
backgroundColor: KrowPalette.yellow,
|
||||
side: BorderSide.none,
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: KrowPalette.ink,
|
||||
),
|
||||
deleteIcon: const Icon(LucideIcons.x, size: 14),
|
||||
onDeleted: () => controller.toggleSkill(s),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Skills saved')));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Save skills'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tag extends StatelessWidget {
|
||||
const _Tag({
|
||||
required this.label,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FilterChip(
|
||||
label: Text(label),
|
||||
selected: selected,
|
||||
showCheckmark: false,
|
||||
onSelected: (_) => onTap(),
|
||||
selectedColor: KrowPalette.blue,
|
||||
backgroundColor: Colors.white,
|
||||
side: BorderSide(color: selected ? KrowPalette.blue : KrowPalette.line),
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: selected ? Colors.white : KrowPalette.mute,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Where your pay lands. Needed before the first pay date.
|
||||
class DirectDepositScreen extends ConsumerStatefulWidget {
|
||||
const DirectDepositScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DirectDepositScreen> createState() =>
|
||||
_DirectDepositScreenState();
|
||||
}
|
||||
|
||||
class _DirectDepositScreenState extends ConsumerState<DirectDepositScreen> {
|
||||
final _bank = TextEditingController();
|
||||
final _routing = TextEditingController();
|
||||
final _account = TextEditingController();
|
||||
String _type = 'Chequing';
|
||||
bool _editing = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_bank.dispose();
|
||||
_routing.dispose();
|
||||
_account.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _save() {
|
||||
if (_bank.text.trim().isEmpty ||
|
||||
_routing.text.length != 9 ||
|
||||
_account.text.length < 4) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Check the bank name, 9-digit routing and account'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
ref
|
||||
.read(profileProvider.notifier)
|
||||
.saveBank(
|
||||
BankAccount(
|
||||
bankName: _bank.text.trim(),
|
||||
routing: _routing.text,
|
||||
accountLast4: _account.text.substring(_account.text.length - 4),
|
||||
accountType: _type,
|
||||
),
|
||||
);
|
||||
setState(() => _editing = false);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Account saved')));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bank = ref.watch(profileProvider).bank;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Direct deposit')),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 28),
|
||||
children: [
|
||||
const Text(
|
||||
'Where your pay lands. You need this before the first pay date.',
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
height: 1.55,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
KrowCard(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: KrowPalette.blueTint,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.shield,
|
||||
size: 17,
|
||||
color: KrowPalette.blue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Bank-grade encryption',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 3),
|
||||
Text(
|
||||
'Account numbers are tokenised. KROW staff never see '
|
||||
'them, and neither do the businesses you work for.',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.45,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SectionLabel('Your account'),
|
||||
if (bank != null && !_editing)
|
||||
KrowCard(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
DetailRow('Bank', bank.bankName),
|
||||
const Divider(height: 1),
|
||||
DetailRow('Type', bank.accountType),
|
||||
const Divider(height: 1),
|
||||
DetailRow('Routing', bank.routing),
|
||||
const Divider(height: 1),
|
||||
DetailRow('Account', '····${bank.accountLast4}'),
|
||||
],
|
||||
),
|
||||
)
|
||||
else if (!_editing)
|
||||
EmptyState(
|
||||
icon: LucideIcons.landmark,
|
||||
title: 'No account added',
|
||||
message: 'Add one before the next pay date to get paid on time.',
|
||||
action: FilledButton.icon(
|
||||
onPressed: () => setState(() => _editing = true),
|
||||
icon: const Icon(LucideIcons.plus, size: 18),
|
||||
label: const Text('Add an account'),
|
||||
),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _bank,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
decoration: const InputDecoration(labelText: 'Bank name'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _routing,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(9),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Routing number',
|
||||
helperText: 'Nine digits, on the bottom of a cheque',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _account,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(17),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Account number',
|
||||
helperText: 'Chequing or savings, in your own name',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: _type,
|
||||
decoration: const InputDecoration(labelText: 'Account type'),
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: 'Chequing',
|
||||
child: Text('Chequing'),
|
||||
),
|
||||
DropdownMenuItem(value: 'Savings', child: Text('Savings')),
|
||||
],
|
||||
onChanged: (v) => setState(() => _type = v ?? _type),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
if (_editing)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => setState(() => _editing = false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
onPressed: _save,
|
||||
child: const Text('Save account'),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else if (bank != null)
|
||||
OutlinedButton(
|
||||
onPressed: () => setState(() => _editing = true),
|
||||
child: const Text('Replace account'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Policies and balances, per staffing vendor.
|
||||
class BenefitsScreen extends ConsumerWidget {
|
||||
const BenefitsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Sick-leave accrual starts after 30 days worked.
|
||||
const daysWorked = 12;
|
||||
const threshold = 30;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Your benefits'),
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Center(
|
||||
child: StatusChip('Legendary Events', tone: ChipTone.yellow),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 28),
|
||||
children: [
|
||||
const Text(
|
||||
'Policies and balances, per staffing vendor.',
|
||||
style: TextStyle(fontSize: 13.5, color: KrowPalette.mute),
|
||||
),
|
||||
const SectionLabel('Active policies'),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: KrowPalette.line),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: KrowPalette.line),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.gift,
|
||||
size: 24,
|
||||
color: Color(0xFF949FA5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const Text(
|
||||
'No policy active yet',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
const Text(
|
||||
'Sick-leave accrual starts after 30 days worked. '
|
||||
'You are at day $daysWorked.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
height: 1.5,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: KrowProgressBar(
|
||||
daysWorked / threshold,
|
||||
color: KrowPalette.yellow,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 9),
|
||||
const Text(
|
||||
'${threshold - daysWorked} days to go',
|
||||
style: TextStyle(fontSize: 11.5, color: Color(0xFF949FA5)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SectionLabel('Balance history'),
|
||||
KrowCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'A read-only record of accruals, usage, adjustments and '
|
||||
'reversals.',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
height: 1.5,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: KrowPalette.sage,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Text(
|
||||
'Nothing recorded yet',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 13, color: KrowPalette.mute),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const InfoBanner(
|
||||
'Working for a different vendor? Switch at the top to see their '
|
||||
'policies.',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user