169 lines
4.8 KiB
Dart
169 lines
4.8 KiB
Dart
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<String> settings;
|
|
final Set<String> skills;
|
|
final List<EmergencyContact> contacts;
|
|
final BankAccount? bank;
|
|
final bool newsletter;
|
|
|
|
ProfileState copyWith({
|
|
Worker? worker,
|
|
Set<String>? settings,
|
|
Set<String>? skills,
|
|
List<EmergencyContact>? 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<ProfileState> {
|
|
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, ProfileState>(
|
|
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<Worker>((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,
|
|
);
|
|
});
|