Initial commit: Krow Worker App
This commit is contained in:
21
lib/models/availability.dart
Normal file
21
lib/models/availability.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
enum DayBlock { morning, afternoon, evening }
|
||||
|
||||
extension DayBlockX on DayBlock {
|
||||
String get label => switch (this) {
|
||||
DayBlock.morning => 'Morning',
|
||||
DayBlock.afternoon => 'Afternoon',
|
||||
DayBlock.evening => 'Evening',
|
||||
};
|
||||
|
||||
String get window => switch (this) {
|
||||
DayBlock.morning => '04:00 - 12:00',
|
||||
DayBlock.afternoon => '12:00 - 18:00',
|
||||
DayBlock.evening => '18:00 - 24:00',
|
||||
};
|
||||
|
||||
int get hours => switch (this) {
|
||||
DayBlock.morning => 8,
|
||||
DayBlock.afternoon => 6,
|
||||
DayBlock.evening => 6,
|
||||
};
|
||||
}
|
||||
82
lib/models/compliance.dart
Normal file
82
lib/models/compliance.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
enum ComplianceState { verified, pending, missing }
|
||||
|
||||
extension ComplianceStateX on ComplianceState {
|
||||
String get label => switch (this) {
|
||||
ComplianceState.verified => 'Verified',
|
||||
ComplianceState.pending => 'Pending',
|
||||
ComplianceState.missing => 'Missing',
|
||||
};
|
||||
}
|
||||
|
||||
/// A single thing a business must see before you can work: a document, a
|
||||
/// certificate, a tax form or a piece of attire.
|
||||
class ComplianceItem {
|
||||
ComplianceItem({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.state,
|
||||
this.statusLabel,
|
||||
this.fileName,
|
||||
this.fileMeta,
|
||||
this.expires,
|
||||
this.required_ = true,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final ComplianceState state;
|
||||
final String? statusLabel;
|
||||
final String? fileName;
|
||||
final String? fileMeta;
|
||||
final DateTime? expires;
|
||||
final bool required_;
|
||||
|
||||
String get badge => statusLabel ?? state.label;
|
||||
|
||||
ComplianceItem copyWith({
|
||||
ComplianceState? state,
|
||||
String? statusLabel,
|
||||
String? fileName,
|
||||
String? fileMeta,
|
||||
DateTime? expires,
|
||||
bool clearFile = false,
|
||||
}) => ComplianceItem(
|
||||
id: id,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
state: state ?? this.state,
|
||||
statusLabel: statusLabel ?? this.statusLabel,
|
||||
fileName: clearFile ? null : (fileName ?? this.fileName),
|
||||
fileMeta: clearFile ? null : (fileMeta ?? this.fileMeta),
|
||||
expires: expires ?? this.expires,
|
||||
required_: required_,
|
||||
);
|
||||
}
|
||||
|
||||
class EmergencyContact {
|
||||
EmergencyContact({
|
||||
required this.name,
|
||||
required this.phone,
|
||||
required this.relationship,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String phone;
|
||||
final String relationship;
|
||||
}
|
||||
|
||||
class BankAccount {
|
||||
BankAccount({
|
||||
required this.bankName,
|
||||
required this.routing,
|
||||
required this.accountLast4,
|
||||
required this.accountType,
|
||||
});
|
||||
|
||||
final String bankName;
|
||||
final String routing;
|
||||
final String accountLast4;
|
||||
final String accountType;
|
||||
}
|
||||
95
lib/models/shift.dart
Normal file
95
lib/models/shift.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
enum ShiftStatus { open, booked, active, completed, cancelled, noShow }
|
||||
|
||||
extension ShiftStatusX on ShiftStatus {
|
||||
String get label => switch (this) {
|
||||
ShiftStatus.open => 'Open',
|
||||
ShiftStatus.booked => 'Confirmed',
|
||||
ShiftStatus.active => 'In progress',
|
||||
ShiftStatus.completed => 'Completed',
|
||||
ShiftStatus.cancelled => 'Cancelled',
|
||||
ShiftStatus.noShow => 'No show',
|
||||
};
|
||||
}
|
||||
|
||||
class Shift {
|
||||
Shift({
|
||||
required this.id,
|
||||
required this.role,
|
||||
required this.business,
|
||||
required this.venue,
|
||||
required this.address,
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.rate,
|
||||
required this.status,
|
||||
this.breakMinutes = 30,
|
||||
this.distanceMiles = 2.4,
|
||||
this.manager,
|
||||
this.clockIn,
|
||||
this.clockOut,
|
||||
this.requirements = const [],
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String role;
|
||||
final String business;
|
||||
final String venue;
|
||||
final String address;
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
final double rate;
|
||||
final ShiftStatus status;
|
||||
final int breakMinutes;
|
||||
final double distanceMiles;
|
||||
final String? manager;
|
||||
final DateTime? clockIn;
|
||||
final DateTime? clockOut;
|
||||
final List<String> requirements;
|
||||
|
||||
/// Paid duration: the scheduled window less the unpaid break.
|
||||
Duration get scheduledDuration =>
|
||||
end.difference(start) - Duration(minutes: breakMinutes);
|
||||
|
||||
/// What was actually worked, once clocked out.
|
||||
Duration? get workedDuration {
|
||||
if (clockIn == null || clockOut == null) return null;
|
||||
final d = clockOut!.difference(clockIn!) - Duration(minutes: breakMinutes);
|
||||
return d.isNegative ? Duration.zero : d;
|
||||
}
|
||||
|
||||
double get scheduledPay => rate * scheduledDuration.inMinutes / 60;
|
||||
|
||||
/// Gross, pre-tax. Falls back to the scheduled figure until clocked out.
|
||||
double get pay {
|
||||
if (status == ShiftStatus.cancelled || status == ShiftStatus.noShow) {
|
||||
return 0;
|
||||
}
|
||||
final worked = workedDuration;
|
||||
if (worked != null) return rate * worked.inMinutes / 60;
|
||||
return scheduledPay;
|
||||
}
|
||||
|
||||
String get logoLetter => business.isEmpty ? '?' : business[0].toUpperCase();
|
||||
|
||||
Shift copyWith({
|
||||
ShiftStatus? status,
|
||||
DateTime? clockIn,
|
||||
DateTime? clockOut,
|
||||
}) => Shift(
|
||||
id: id,
|
||||
role: role,
|
||||
business: business,
|
||||
venue: venue,
|
||||
address: address,
|
||||
start: start,
|
||||
end: end,
|
||||
rate: rate,
|
||||
status: status ?? this.status,
|
||||
breakMinutes: breakMinutes,
|
||||
distanceMiles: distanceMiles,
|
||||
manager: manager,
|
||||
clockIn: clockIn ?? this.clockIn,
|
||||
clockOut: clockOut ?? this.clockOut,
|
||||
requirements: requirements,
|
||||
);
|
||||
}
|
||||
70
lib/models/timecard.dart
Normal file
70
lib/models/timecard.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
enum TimecardStatus { needsReview, approved, disputed }
|
||||
|
||||
extension TimecardStatusX on TimecardStatus {
|
||||
String get label => switch (this) {
|
||||
TimecardStatus.needsReview => 'Needs review',
|
||||
TimecardStatus.approved => 'Approved',
|
||||
TimecardStatus.disputed => 'Disputed',
|
||||
};
|
||||
}
|
||||
|
||||
class Timecard {
|
||||
Timecard({
|
||||
required this.id,
|
||||
required this.shiftId,
|
||||
required this.role,
|
||||
required this.business,
|
||||
required this.scheduledStart,
|
||||
required this.scheduledEnd,
|
||||
required this.clockIn,
|
||||
required this.clockOut,
|
||||
required this.breakMinutes,
|
||||
required this.rate,
|
||||
required this.status,
|
||||
required this.submittedAt,
|
||||
this.disputeNote,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String shiftId;
|
||||
final String role;
|
||||
final String business;
|
||||
final DateTime scheduledStart;
|
||||
final DateTime scheduledEnd;
|
||||
final DateTime clockIn;
|
||||
final DateTime clockOut;
|
||||
final int breakMinutes;
|
||||
final double rate;
|
||||
final TimecardStatus status;
|
||||
final DateTime submittedAt;
|
||||
final String? disputeNote;
|
||||
|
||||
DateTime get date =>
|
||||
DateTime(scheduledStart.year, scheduledStart.month, scheduledStart.day);
|
||||
|
||||
Duration get worked {
|
||||
final d = clockOut.difference(clockIn) - Duration(minutes: breakMinutes);
|
||||
return d.isNegative ? Duration.zero : d;
|
||||
}
|
||||
|
||||
double get amount => rate * worked.inMinutes / 60;
|
||||
|
||||
/// Minutes late (positive) or early (negative) against the schedule.
|
||||
int get varianceMinutes => clockIn.difference(scheduledStart).inMinutes;
|
||||
|
||||
Timecard copyWith({TimecardStatus? status, String? disputeNote}) => Timecard(
|
||||
id: id,
|
||||
shiftId: shiftId,
|
||||
role: role,
|
||||
business: business,
|
||||
scheduledStart: scheduledStart,
|
||||
scheduledEnd: scheduledEnd,
|
||||
clockIn: clockIn,
|
||||
clockOut: clockOut,
|
||||
breakMinutes: breakMinutes,
|
||||
rate: rate,
|
||||
status: status ?? this.status,
|
||||
submittedAt: submittedAt,
|
||||
disputeNote: disputeNote ?? this.disputeNote,
|
||||
);
|
||||
}
|
||||
57
lib/models/worker.dart
Normal file
57
lib/models/worker.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
class Worker {
|
||||
const Worker({
|
||||
required this.name,
|
||||
required this.role,
|
||||
required this.city,
|
||||
required this.phone,
|
||||
this.shiftsWorked = 0,
|
||||
this.rating = 5.0,
|
||||
this.onTimePercent = 100,
|
||||
this.noShows = 0,
|
||||
this.cancellations = 0,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String role;
|
||||
final String city;
|
||||
final String phone;
|
||||
final int shiftsWorked;
|
||||
final double rating;
|
||||
final int onTimePercent;
|
||||
final int noShows;
|
||||
final int cancellations;
|
||||
|
||||
/// Stay above 45% to keep picking up shifts. On-time arrivals move it most.
|
||||
int get trustIndex {
|
||||
final base = 40 + onTimePercent ~/ 2 - noShows * 15 - cancellations * 5;
|
||||
return base.clamp(0, 100);
|
||||
}
|
||||
|
||||
String get initials => name
|
||||
.split(' ')
|
||||
.where((p) => p.isNotEmpty)
|
||||
.map((p) => p[0].toUpperCase())
|
||||
.take(2)
|
||||
.join();
|
||||
|
||||
Worker copyWith({
|
||||
String? name,
|
||||
String? role,
|
||||
String? city,
|
||||
String? phone,
|
||||
int? shiftsWorked,
|
||||
int? onTimePercent,
|
||||
int? noShows,
|
||||
int? cancellations,
|
||||
}) => Worker(
|
||||
name: name ?? this.name,
|
||||
role: role ?? this.role,
|
||||
city: city ?? this.city,
|
||||
phone: phone ?? this.phone,
|
||||
shiftsWorked: shiftsWorked ?? this.shiftsWorked,
|
||||
rating: rating,
|
||||
onTimePercent: onTimePercent ?? this.onTimePercent,
|
||||
noShows: noShows ?? this.noShows,
|
||||
cancellations: cancellations ?? this.cancellations,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user