feat(breaks): Implement break functionality with Break entity and adapter
This commit is contained in:
@@ -30,6 +30,8 @@ export 'src/entities/events/work_session.dart';
|
|||||||
// Shifts
|
// Shifts
|
||||||
export 'src/entities/shifts/shift.dart';
|
export 'src/entities/shifts/shift.dart';
|
||||||
export 'src/adapters/shifts/shift_adapter.dart';
|
export 'src/adapters/shifts/shift_adapter.dart';
|
||||||
|
export 'src/entities/shifts/break/break.dart';
|
||||||
|
export 'src/adapters/shifts/break/break_adapter.dart';
|
||||||
|
|
||||||
// Orders & Requests
|
// Orders & Requests
|
||||||
export 'src/entities/orders/order_type.dart';
|
export 'src/entities/orders/order_type.dart';
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import '../../../entities/shifts/break/break.dart';
|
||||||
|
|
||||||
|
/// Adapter for Break related data.
|
||||||
|
class BreakAdapter {
|
||||||
|
/// Maps break data to a Break entity.
|
||||||
|
///
|
||||||
|
/// [isPaid] whether the break is paid.
|
||||||
|
/// [breakTime] the string representation of the break duration (e.g., 'MIN_10', 'MIN_30').
|
||||||
|
static Break fromData({
|
||||||
|
required bool isPaid,
|
||||||
|
required String? breakTime,
|
||||||
|
}) {
|
||||||
|
return Break(
|
||||||
|
isBreakPaid: isPaid,
|
||||||
|
duration: _parseDuration(breakTime),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BreakDuration _parseDuration(String? breakTime) {
|
||||||
|
if (breakTime == null) return BreakDuration.none;
|
||||||
|
|
||||||
|
switch (breakTime.toUpperCase()) {
|
||||||
|
case 'MIN_10':
|
||||||
|
return BreakDuration.ten;
|
||||||
|
case 'MIN_15':
|
||||||
|
return BreakDuration.fifteen;
|
||||||
|
case 'MIN_20':
|
||||||
|
return BreakDuration.twenty;
|
||||||
|
case 'MIN_30':
|
||||||
|
return BreakDuration.thirty;
|
||||||
|
case 'MIN_45':
|
||||||
|
return BreakDuration.fortyFive;
|
||||||
|
case 'MIN_60':
|
||||||
|
return BreakDuration.sixty;
|
||||||
|
default:
|
||||||
|
return BreakDuration.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// Enum representing common break durations in minutes.
|
||||||
|
enum BreakDuration {
|
||||||
|
/// No break.
|
||||||
|
none(0),
|
||||||
|
|
||||||
|
/// 10 minutes break.
|
||||||
|
ten(10),
|
||||||
|
|
||||||
|
/// 15 minutes break.
|
||||||
|
fifteen(15),
|
||||||
|
|
||||||
|
/// 20 minutes break.
|
||||||
|
twenty(20),
|
||||||
|
|
||||||
|
/// 30 minutes break.
|
||||||
|
thirty(30),
|
||||||
|
|
||||||
|
/// 45 minutes break.
|
||||||
|
fortyFive(45),
|
||||||
|
|
||||||
|
/// 60 minutes break.
|
||||||
|
sixty(60);
|
||||||
|
|
||||||
|
/// The duration in minutes.
|
||||||
|
final int minutes;
|
||||||
|
|
||||||
|
const BreakDuration(this.minutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a break configuration for a shift.
|
||||||
|
class Break extends Equatable {
|
||||||
|
const Break({
|
||||||
|
required this.duration,
|
||||||
|
required this.isBreakPaid,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The duration of the break.
|
||||||
|
final BreakDuration duration;
|
||||||
|
|
||||||
|
/// Whether the break is paid or unpaid.
|
||||||
|
final bool isBreakPaid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[duration, isBreakPaid];
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/src/entities/shifts/break/break.dart';
|
||||||
|
|
||||||
class Shift extends Equatable {
|
class Shift extends Equatable {
|
||||||
final String id;
|
final String id;
|
||||||
@@ -29,6 +30,7 @@ class Shift extends Equatable {
|
|||||||
final String? roleId;
|
final String? roleId;
|
||||||
final bool? hasApplied;
|
final bool? hasApplied;
|
||||||
final double? totalValue;
|
final double? totalValue;
|
||||||
|
final Break? breakInfo;
|
||||||
|
|
||||||
const Shift({
|
const Shift({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -59,48 +61,49 @@ class Shift extends Equatable {
|
|||||||
this.roleId,
|
this.roleId,
|
||||||
this.hasApplied,
|
this.hasApplied,
|
||||||
this.totalValue,
|
this.totalValue,
|
||||||
|
this.breakInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => <Object?>[
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
clientName,
|
clientName,
|
||||||
logoUrl,
|
logoUrl,
|
||||||
hourlyRate,
|
hourlyRate,
|
||||||
location,
|
location,
|
||||||
locationAddress,
|
locationAddress,
|
||||||
date,
|
date,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
createdDate,
|
createdDate,
|
||||||
tipsAvailable,
|
tipsAvailable,
|
||||||
travelTime,
|
travelTime,
|
||||||
mealProvided,
|
mealProvided,
|
||||||
parkingAvailable,
|
parkingAvailable,
|
||||||
gasCompensation,
|
gasCompensation,
|
||||||
description,
|
description,
|
||||||
instructions,
|
instructions,
|
||||||
managers,
|
managers,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
status,
|
status,
|
||||||
durationDays,
|
durationDays,
|
||||||
requiredSlots,
|
requiredSlots,
|
||||||
filledSlots,
|
filledSlots,
|
||||||
roleId,
|
roleId,
|
||||||
hasApplied,
|
hasApplied,
|
||||||
totalValue,
|
totalValue,
|
||||||
];
|
breakInfo,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShiftManager extends Equatable {
|
class ShiftManager extends Equatable {
|
||||||
|
const ShiftManager({required this.name, required this.phone, this.avatar});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final String phone;
|
final String phone;
|
||||||
final String? avatar;
|
final String? avatar;
|
||||||
|
|
||||||
const ShiftManager({required this.name, required this.phone, this.avatar});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [name, phone, avatar];
|
List<Object?> get props => <Object?>[name, phone, avatar];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ class ShiftsRepositoryImpl
|
|||||||
requiredSlots: app.shiftRole.count,
|
requiredSlots: app.shiftRole.count,
|
||||||
filledSlots: app.shiftRole.assigned ?? 0,
|
filledSlots: app.shiftRole.assigned ?? 0,
|
||||||
hasApplied: true,
|
hasApplied: true,
|
||||||
|
breakInfo: BreakAdapter.fromData(
|
||||||
|
isPaid: app.shiftRole.isBreakPaid ?? false,
|
||||||
|
breakTime: app.shiftRole.breakType?.stringValue,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -208,6 +212,10 @@ class ShiftsRepositoryImpl
|
|||||||
requiredSlots: app.shiftRole.count,
|
requiredSlots: app.shiftRole.count,
|
||||||
filledSlots: app.shiftRole.assigned ?? 0,
|
filledSlots: app.shiftRole.assigned ?? 0,
|
||||||
hasApplied: true,
|
hasApplied: true,
|
||||||
|
breakInfo: BreakAdapter.fromData(
|
||||||
|
isPaid: app.shiftRole.isBreakPaid ?? false,
|
||||||
|
breakTime: app.shiftRole.breakType?.stringValue,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -277,6 +285,10 @@ class ShiftsRepositoryImpl
|
|||||||
durationDays: sr.shift.durationDays,
|
durationDays: sr.shift.durationDays,
|
||||||
requiredSlots: sr.count,
|
requiredSlots: sr.count,
|
||||||
filledSlots: sr.assigned ?? 0,
|
filledSlots: sr.assigned ?? 0,
|
||||||
|
breakInfo: BreakAdapter.fromData(
|
||||||
|
isPaid: sr.isBreakPaid ?? false,
|
||||||
|
breakTime: sr.breakType?.stringValue,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -350,6 +362,10 @@ class ShiftsRepositoryImpl
|
|||||||
filledSlots: sr.assigned ?? 0,
|
filledSlots: sr.assigned ?? 0,
|
||||||
hasApplied: hasApplied,
|
hasApplied: hasApplied,
|
||||||
totalValue: sr.totalValue,
|
totalValue: sr.totalValue,
|
||||||
|
breakInfo: BreakAdapter.fromData(
|
||||||
|
isPaid: sr.isBreakPaid ?? false,
|
||||||
|
breakTime: sr.breakType?.stringValue,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +376,7 @@ class ShiftsRepositoryImpl
|
|||||||
|
|
||||||
int? required;
|
int? required;
|
||||||
int? filled;
|
int? filled;
|
||||||
|
Break? breakInfo;
|
||||||
try {
|
try {
|
||||||
final rolesRes = await executeProtected(() =>
|
final rolesRes = await executeProtected(() =>
|
||||||
_dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute());
|
_dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute());
|
||||||
@@ -370,6 +387,12 @@ class ShiftsRepositoryImpl
|
|||||||
required = (required ?? 0) + r.count;
|
required = (required ?? 0) + r.count;
|
||||||
filled = (filled ?? 0) + (r.assigned ?? 0);
|
filled = (filled ?? 0) + (r.assigned ?? 0);
|
||||||
}
|
}
|
||||||
|
// Use the first role's break info as a representative
|
||||||
|
final firstRole = rolesRes.data.shiftRoles.first;
|
||||||
|
breakInfo = BreakAdapter.fromData(
|
||||||
|
isPaid: firstRole.isBreakPaid ?? false,
|
||||||
|
breakTime: firstRole.breakType?.stringValue,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
@@ -394,6 +417,7 @@ class ShiftsRepositoryImpl
|
|||||||
durationDays: s.durationDays,
|
durationDays: s.durationDays,
|
||||||
requiredSlots: required,
|
requiredSlots: required,
|
||||||
filledSlots: filled,
|
filledSlots: filled,
|
||||||
|
breakInfo: breakInfo,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
return BlocProvider<ShiftDetailsBloc>(
|
return BlocProvider<ShiftDetailsBloc>(
|
||||||
create: (_) => Modular.get<ShiftDetailsBloc>()
|
create: (_) => Modular.get<ShiftDetailsBloc>()
|
||||||
..add(
|
..add(
|
||||||
LoadShiftDetailsEvent(widget.shiftId, roleId: widget.shift?.roleId),
|
LoadShiftDetailsEvent(widget.shiftId, roleId: widget.shift.roleId),
|
||||||
),
|
),
|
||||||
child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>(
|
child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
@@ -148,7 +148,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
);
|
);
|
||||||
Modular.to.toShifts(selectedDate: state.shiftDate);
|
Modular.to.toShifts(selectedDate: state.shiftDate);
|
||||||
} else if (state is ShiftDetailsError) {
|
} else if (state is ShiftDetailsError) {
|
||||||
if (_isApplying || widget.shift == null) {
|
if (_isApplying) {
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
context,
|
context,
|
||||||
message: translateErrorKey(state.message),
|
message: translateErrorKey(state.message),
|
||||||
@@ -240,7 +240,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
|
||||||
// Date Section
|
// Date & Time Section
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -248,8 +248,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
i18n.shift_date,
|
i18n.shift_date,
|
||||||
style: UiTypography
|
style: UiTypography.titleUppercase4b
|
||||||
.titleUppercase4b
|
|
||||||
.textSecondary,
|
.textSecondary,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space2),
|
const SizedBox(height: UiConstants.space2),
|
||||||
@@ -268,6 +267,24 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildTimeBox(
|
||||||
|
"CLOCK IN TIME",
|
||||||
|
displayShift.startTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space4),
|
||||||
|
Expanded(
|
||||||
|
child: _buildTimeBox(
|
||||||
|
"CLOCK OUT TIME",
|
||||||
|
displayShift.endTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -308,30 +325,6 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
|
||||||
// Time Section (New)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildTimeBox(
|
|
||||||
"CLOCK IN TIME",
|
|
||||||
displayShift.startTime,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space4),
|
|
||||||
Expanded(
|
|
||||||
child: _buildTimeBox(
|
|
||||||
"CLOCK OUT TIME",
|
|
||||||
displayShift.endTime,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
|
||||||
|
|
||||||
// Location Section (New with Map)
|
// Location Section (New with Map)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
@@ -344,7 +337,6 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
.titleUppercase4b
|
.titleUppercase4b
|
||||||
.textSecondary,
|
.textSecondary,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
MainAxisAlignment.spaceBetween,
|
MainAxisAlignment.spaceBetween,
|
||||||
@@ -366,12 +358,10 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
).showSnackBar(
|
).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
displayShift!
|
displayShift.locationAddress
|
||||||
.locationAddress
|
|
||||||
.isNotEmpty
|
.isNotEmpty
|
||||||
? displayShift!
|
? displayShift.locationAddress
|
||||||
.locationAddress
|
: displayShift.location,
|
||||||
: displayShift!.location,
|
|
||||||
),
|
),
|
||||||
duration: const Duration(
|
duration: const Duration(
|
||||||
seconds: 3,
|
seconds: 3,
|
||||||
@@ -509,36 +499,6 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _declineShift(BuildContext context, String id) {
|
|
||||||
final i18n = Translations.of(
|
|
||||||
context,
|
|
||||||
).staff_shifts.shift_details.decline_dialog;
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (ctx) => AlertDialog(
|
|
||||||
title: Text(i18n.title),
|
|
||||||
content: Text(i18n.message),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Modular.to.pop(),
|
|
||||||
child: Text(Translations.of(context).common.cancel),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
BlocProvider.of<ShiftDetailsBloc>(
|
|
||||||
context,
|
|
||||||
).add(DeclineShiftDetailsEvent(id));
|
|
||||||
},
|
|
||||||
style: TextButton.styleFrom(foregroundColor: UiColors.destructive),
|
|
||||||
child: Text(
|
|
||||||
Translations.of(context).staff_shifts.shift_details.decline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showApplyingDialog(BuildContext context, Shift shift) {
|
void _showApplyingDialog(BuildContext context, Shift shift) {
|
||||||
if (_actionDialogOpen) return;
|
if (_actionDialogOpen) return;
|
||||||
_actionDialogOpen = true;
|
_actionDialogOpen = true;
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ query listApplications @auth(level: USER) {
|
|||||||
startTime
|
startTime
|
||||||
endTime
|
endTime
|
||||||
hours
|
hours
|
||||||
|
breakType
|
||||||
|
isBreakPaid
|
||||||
totalValue
|
totalValue
|
||||||
role {
|
role {
|
||||||
id
|
id
|
||||||
@@ -341,6 +343,8 @@ query getApplicationsByStaffId(
|
|||||||
startTime
|
startTime
|
||||||
endTime
|
endTime
|
||||||
hours
|
hours
|
||||||
|
breakType
|
||||||
|
isBreakPaid
|
||||||
totalValue
|
totalValue
|
||||||
role {
|
role {
|
||||||
id
|
id
|
||||||
@@ -352,7 +356,6 @@ query getApplicationsByStaffId(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
query vaidateDayStaffApplication(
|
query vaidateDayStaffApplication(
|
||||||
$staffId: UUID!
|
$staffId: UUID!
|
||||||
$offset: Int
|
$offset: Int
|
||||||
@@ -692,6 +695,8 @@ query listCompletedApplicationsByStaffId(
|
|||||||
startTime
|
startTime
|
||||||
endTime
|
endTime
|
||||||
hours
|
hours
|
||||||
|
breakType
|
||||||
|
isBreakPaid
|
||||||
totalValue
|
totalValue
|
||||||
|
|
||||||
role {
|
role {
|
||||||
|
|||||||
Reference in New Issue
Block a user