98 lines
3.3 KiB
Dart
98 lines
3.3 KiB
Dart
/// What a worker has said about a given day.
|
|
///
|
|
/// Three states rather than an on/off switch, because "I can work" and "I want
|
|
/// work" are different answers and Auto-Match treats them differently: an
|
|
/// available day is one it may fill, a preferred day is one it should fill
|
|
/// first.
|
|
enum DayAvailability { available, prefers, unavailable }
|
|
|
|
extension DayAvailabilityX on DayAvailability {
|
|
String get label => switch (this) {
|
|
DayAvailability.available => 'Available',
|
|
DayAvailability.prefers => 'Prefers to work',
|
|
DayAvailability.unavailable => 'Unavailable',
|
|
};
|
|
|
|
/// The line under the option, in the picker.
|
|
String get blurb => switch (this) {
|
|
DayAvailability.available => 'Can be scheduled any time',
|
|
DayAvailability.prefers => 'Wants shifts — optionally within a time window',
|
|
DayAvailability.unavailable => 'Marked off (vacation, sick, personal)',
|
|
};
|
|
|
|
/// The line under the date, once the day is set.
|
|
String get summary => switch (this) {
|
|
DayAvailability.available => 'Can be scheduled any time',
|
|
DayAvailability.prefers => 'Wants shifts',
|
|
DayAvailability.unavailable => 'Not available',
|
|
};
|
|
}
|
|
|
|
/// The hours a worker would rather be given, on a day they want work.
|
|
///
|
|
/// Both ends are optional and independent: "from 6pm, any end" is a normal
|
|
/// thing to want, and forcing a full range would make the worker invent one.
|
|
/// Null at both ends means any time, which is also the default.
|
|
class PreferredWindow {
|
|
const PreferredWindow({this.from, this.to});
|
|
|
|
/// Hour of the day, 0-23. Null is "any time".
|
|
final int? from;
|
|
final int? to;
|
|
|
|
static const any = PreferredWindow();
|
|
|
|
bool get isAnyTime => from == null && to == null;
|
|
|
|
/// How the window reads on its own, as a label.
|
|
String get label {
|
|
if (isAnyTime) return 'Any time';
|
|
if (from != null && to != null) return '${_hhmm(from!)} - ${_hhmm(to!)}';
|
|
if (from != null) return 'From ${_hhmm(from!)}';
|
|
return 'Until ${_hhmm(to!)}';
|
|
}
|
|
|
|
/// How the window reads inside a sentence — "look for shifts $sentence
|
|
/// first". The label does not survive being dropped mid-sentence: "look for
|
|
/// From 09:00 first" is not English.
|
|
String get sentence {
|
|
if (isAnyTime) return 'at any time';
|
|
if (from != null && to != null) {
|
|
return 'between ${_hhmm(from!)} and ${_hhmm(to!)}';
|
|
}
|
|
if (from != null) return 'from ${_hhmm(from!)}';
|
|
return 'until ${_hhmm(to!)}';
|
|
}
|
|
|
|
PreferredWindow copyWith({
|
|
int? from,
|
|
int? to,
|
|
bool clearFrom = false,
|
|
bool clearTo = false,
|
|
}) => PreferredWindow(
|
|
from: clearFrom ? null : (from ?? this.from),
|
|
to: clearTo ? null : (to ?? this.to),
|
|
);
|
|
|
|
static String _hhmm(int hour) => '${hour.toString().padLeft(2, '0')}:00';
|
|
}
|
|
|
|
/// A day's answer: the status, and the window if one was given.
|
|
class DayPlan {
|
|
const DayPlan({
|
|
this.status = DayAvailability.unavailable,
|
|
this.window = PreferredWindow.any,
|
|
});
|
|
|
|
final DayAvailability status;
|
|
|
|
/// Only meaningful while [status] is [DayAvailability.prefers]; kept across
|
|
/// a status change so flipping away and back does not lose the hours.
|
|
final PreferredWindow window;
|
|
|
|
bool get isSet => status != DayAvailability.unavailable;
|
|
|
|
DayPlan copyWith({DayAvailability? status, PreferredWindow? window}) =>
|
|
DayPlan(status: status ?? this.status, window: window ?? this.window);
|
|
}
|