174 lines
5.4 KiB
Dart
174 lines
5.4 KiB
Dart
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),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|