feat: enhance UI, branding assets, onboarding flow, and screen layouts
This commit is contained in:
173
lib/screens/requirements_screen.dart
Normal file
173
lib/screens/requirements_screen.dart
Normal file
@@ -0,0 +1,173 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
import '../core/theme.dart';
|
||||
import '../models/compliance.dart';
|
||||
import '../state/compliance_controller.dart';
|
||||
import '../state/shifts_controller.dart';
|
||||
import '../widgets/common.dart';
|
||||
import 'shift_detail_screen.dart';
|
||||
|
||||
/// Everything one business asks for, on its own screen.
|
||||
///
|
||||
/// The shift page shows the same check inline; this is the full read, grouped
|
||||
/// the way the compliance hub groups things, so it is obvious *which* kind of
|
||||
/// item is missing and where to go and fix it.
|
||||
class RequirementsScreen extends ConsumerWidget {
|
||||
const RequirementsScreen({super.key, required this.shiftId});
|
||||
|
||||
final String shiftId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final shift = ref.watch(shiftsProvider.notifier).byId(shiftId);
|
||||
if (shift == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: const Center(child: Text('That shift is no longer listed.')),
|
||||
);
|
||||
}
|
||||
|
||||
final compliance = ref.watch(complianceProvider);
|
||||
|
||||
/// The items this shift needs, split by which compliance section they
|
||||
/// belong to, so each group can be labelled and linked separately.
|
||||
List<ComplianceItem> group(ComplianceSection section) => compliance
|
||||
.section(section)
|
||||
.where((i) => shift.requirements.contains(i.title))
|
||||
.toList();
|
||||
|
||||
final groups = <(ComplianceSection, String, List<ComplianceItem>)>[
|
||||
(
|
||||
ComplianceSection.documents,
|
||||
'Documents',
|
||||
group(ComplianceSection.documents),
|
||||
),
|
||||
(ComplianceSection.wardrobe, 'Attire', group(ComplianceSection.wardrobe)),
|
||||
(
|
||||
ComplianceSection.certificates,
|
||||
'Certificates',
|
||||
group(ComplianceSection.certificates),
|
||||
),
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Requirements'),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
onPressed: () => context.push('/support'),
|
||||
icon: const Icon(LucideIcons.messageCircle, size: 20),
|
||||
tooltip: 'Message the manager',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 28),
|
||||
children: [
|
||||
Text(
|
||||
'${shift.role} at ${shift.business}',
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.7,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// The same summary the shift page shows, so the two never disagree.
|
||||
ShiftRequirements(shift: shift),
|
||||
for (final (section, label, items) in groups)
|
||||
if (items.isNotEmpty) ...[
|
||||
SectionLabel(
|
||||
label,
|
||||
trailing: TextButton(
|
||||
onPressed: () => context.push('/compliance/${section.name}'),
|
||||
child: const Text('Manage'),
|
||||
),
|
||||
),
|
||||
KrowCard(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < items.length; i++) ...[
|
||||
if (i > 0) const Divider(height: 1),
|
||||
_RequirementRow(item: items[i]),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
const InfoBanner(
|
||||
'These are shared with the business only while you are booked on '
|
||||
'one of their shifts.',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RequirementRow extends StatelessWidget {
|
||||
const _RequirementRow({required this.item});
|
||||
|
||||
final ComplianceItem item;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ok = item.state == ComplianceState.verified;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 11),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
decoration: BoxDecoration(
|
||||
color: ok ? KrowPalette.blueTint : const Color(0xFFF7E4AE),
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
),
|
||||
child: Icon(
|
||||
ok ? LucideIcons.check : LucideIcons.clock,
|
||||
size: 12,
|
||||
color: ok ? KrowPalette.blue : KrowPalette.ink,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
Text(
|
||||
item.subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.4,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
StatusChip(item.badge, tone: ok ? ChipTone.neutral : ChipTone.yellow),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user