import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/compliance.dart'; import '../models/shift.dart'; import '../models/worker.dart'; import 'session_controller.dart'; import 'shifts_controller.dart'; class ProfileState { const ProfileState({ required this.worker, required this.settings, required this.skills, required this.contacts, this.bank, this.newsletter = false, }); final Worker worker; /// Settings the worker has worked in, and the trades they can do. final Set settings; final Set skills; final List contacts; final BankAccount? bank; final bool newsletter; ProfileState copyWith({ Worker? worker, Set? settings, Set? skills, List? contacts, BankAccount? bank, bool? newsletter, }) => ProfileState( worker: worker ?? this.worker, settings: settings ?? this.settings, skills: skills ?? this.skills, contacts: contacts ?? this.contacts, bank: bank ?? this.bank, newsletter: newsletter ?? this.newsletter, ); } class ProfileController extends Notifier { static const _kSettings = 'profile_settings'; static const _kSkills = 'profile_skills'; static const _kNewsletter = 'profile_newsletter'; static const _kName = 'profile_name'; SharedPreferences get _prefs => ref.read(prefsProvider); @override ProfileState build() { final p = _prefs; final phone = ref.watch(sessionProvider).phone; return ProfileState( worker: Worker( name: p.getString(_kName) ?? 'Iliana Ortega', role: 'Barista', city: 'San Jose', phone: phone ?? '+1 408 555 0148', onTimePercent: 50, noShows: 1, cancellations: 1, ), settings: (p.getStringList(_kSettings) ?? const ['Hospitality', 'Events', 'Catering']) .toSet(), skills: (p.getStringList(_kSkills) ?? const ['Barista', 'Customer service', 'Food service']) .toSet(), contacts: [ EmergencyContact( name: 'Carlos Ortega', phone: '+1 555 001 0006', relationship: 'Spouse', ), ], newsletter: p.getBool(_kNewsletter) ?? false, ); } void updateName(String name) { _prefs.setString(_kName, name); state = state.copyWith(worker: state.worker.copyWith(name: name)); } void toggleSetting(String value) { final next = {...state.settings}; if (!next.remove(value)) next.add(value); _prefs.setStringList(_kSettings, next.toList()); state = state.copyWith(settings: next); } void toggleSkill(String value) { final next = {...state.skills}; if (!next.remove(value)) next.add(value); _prefs.setStringList(_kSkills, next.toList()); state = state.copyWith(skills: next); } void addSkill(String value) { final trimmed = value.trim(); if (trimmed.isEmpty) return; final next = {...state.skills, trimmed}; _prefs.setStringList(_kSkills, next.toList()); state = state.copyWith(skills: next); } void saveContact(EmergencyContact contact, {int? index}) { final next = [...state.contacts]; if (index != null && index < next.length) { next[index] = contact; } else { next.add(contact); } state = state.copyWith(contacts: next); } void removeContact(int index) { final next = [...state.contacts]..removeAt(index); state = state.copyWith(contacts: next); } void saveBank(BankAccount account) { state = state.copyWith(bank: account); } void setNewsletter(bool value) { _prefs.setBool(_kNewsletter, value); state = state.copyWith(newsletter: value); } } final profileProvider = NotifierProvider( ProfileController.new, ); /// Live stats derived from the shift list, so the profile reflects what the /// worker has actually done in this session. final workerStatsProvider = Provider((ref) { final base = ref.watch(profileProvider).worker; final shifts = ref.watch(shiftsProvider); final completed = shifts .where((s) => s.status == ShiftStatus.completed) .toList(); final noShows = shifts.where((s) => s.status == ShiftStatus.noShow).length; final cancels = shifts.where((s) => s.status == ShiftStatus.cancelled).length; final onTime = completed .where( (s) => s.clockIn != null && !s.clockIn!.isAfter(s.start.add(const Duration(minutes: 5))), ) .length; return base.copyWith( shiftsWorked: completed.length, onTimePercent: completed.isEmpty ? 100 : (onTime * 100 / completed.length).round(), noShows: noShows, cancellations: cancels, ); });