little advance and corrections
This commit is contained in:
@@ -78,8 +78,15 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getMyShifts() async {
|
||||
return _fetchApplications(dc.ApplicationStatus.ACCEPTED);
|
||||
Future<List<Shift>> getMyShifts({
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
}) async {
|
||||
return _fetchApplications(
|
||||
dc.ApplicationStatus.ACCEPTED,
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -97,12 +104,20 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
return _fetchApplications(dc.ApplicationStatus.CHECKED_OUT);
|
||||
}
|
||||
|
||||
Future<List<Shift>> _fetchApplications(dc.ApplicationStatus status) async {
|
||||
Future<List<Shift>> _fetchApplications(
|
||||
dc.ApplicationStatus status, {
|
||||
DateTime? start,
|
||||
DateTime? end,
|
||||
}) async {
|
||||
try {
|
||||
final staffId = await _getStaffId();
|
||||
final response = await _dataConnect
|
||||
.getApplicationsByStaffId(staffId: staffId)
|
||||
.execute();
|
||||
var query = _dataConnect.getApplicationsByStaffId(staffId: staffId);
|
||||
if (start != null && end != null) {
|
||||
query = query
|
||||
.dayStart(_toTimestamp(start))
|
||||
.dayEnd(_toTimestamp(end));
|
||||
}
|
||||
final response = await query.execute();
|
||||
|
||||
final apps = response.data.applications.where(
|
||||
(app) => app.status.stringValue == status.name,
|
||||
@@ -113,30 +128,40 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||
|
||||
final shift = await _getShiftDetails(app.shift.id);
|
||||
if (shift != null) {
|
||||
// Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED)
|
||||
shifts.add(
|
||||
Shift(
|
||||
id: shift.id,
|
||||
title: shift.title,
|
||||
clientName: shift.clientName,
|
||||
logoUrl: shift.logoUrl,
|
||||
hourlyRate: shift.hourlyRate,
|
||||
location: shift.location,
|
||||
locationAddress: shift.locationAddress,
|
||||
date: shift.date,
|
||||
startTime: shift.startTime,
|
||||
endTime: shift.endTime,
|
||||
createdDate: shift.createdDate,
|
||||
status: _mapStatus(status),
|
||||
description: shift.description,
|
||||
durationDays: shift.durationDays,
|
||||
requiredSlots: shift.requiredSlots,
|
||||
filledSlots: shift.filledSlots,
|
||||
),
|
||||
);
|
||||
}
|
||||
final String roleName = app.shiftRole.role.name;
|
||||
final String orderName =
|
||||
(app.shift.order.eventName ?? '').trim().isNotEmpty
|
||||
? app.shift.order.eventName!
|
||||
: app.shift.order.business.businessName;
|
||||
final String title = '$roleName - $orderName';
|
||||
final DateTime? shiftDate = _toDateTime(app.shift.date);
|
||||
final DateTime? startDt = _toDateTime(app.shiftRole.startTime);
|
||||
final DateTime? endDt = _toDateTime(app.shiftRole.endTime);
|
||||
final DateTime? createdDt = _toDateTime(app.createdAt);
|
||||
|
||||
// Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED)
|
||||
shifts.add(
|
||||
Shift(
|
||||
id: app.shift.id,
|
||||
roleId: app.shiftRole.roleId,
|
||||
title: title,
|
||||
clientName: app.shift.order.business.businessName,
|
||||
logoUrl: app.shift.order.business.companyLogoUrl,
|
||||
hourlyRate: app.shiftRole.role.costPerHour,
|
||||
location: app.shift.location ?? '',
|
||||
locationAddress: app.shift.order.teamHub.hubName,
|
||||
date: shiftDate?.toIso8601String() ?? '',
|
||||
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: _mapStatus(status),
|
||||
description: app.shift.description,
|
||||
durationDays: app.shift.durationDays,
|
||||
requiredSlots: app.shiftRole.count,
|
||||
filledSlots: app.shiftRole.assigned ?? 0,
|
||||
hasApplied: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
return shifts;
|
||||
} catch (e) {
|
||||
@@ -144,6 +169,13 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
}
|
||||
}
|
||||
|
||||
Timestamp _toTimestamp(DateTime dateTime) {
|
||||
final DateTime utc = dateTime.toUtc();
|
||||
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||
return Timestamp(nanoseconds, seconds);
|
||||
}
|
||||
|
||||
String _mapStatus(dc.ApplicationStatus status) {
|
||||
switch (status) {
|
||||
case dc.ApplicationStatus.ACCEPTED:
|
||||
@@ -359,6 +391,16 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
if (role == null) {
|
||||
throw Exception('Shift role not found');
|
||||
}
|
||||
final existingApplicationResult = await _dataConnect
|
||||
.getApplicationByStaffShiftAndRole(
|
||||
staffId: staffId,
|
||||
shiftId: shiftId,
|
||||
roleId: targetRoleId,
|
||||
)
|
||||
.execute();
|
||||
if (existingApplicationResult.data.applications.isNotEmpty) {
|
||||
throw Exception('Application already exists.');
|
||||
}
|
||||
final int assigned = role.assigned ?? 0;
|
||||
if (assigned >= role.count) {
|
||||
throw Exception('This shift is full.');
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
class GetMyShiftsArguments extends UseCaseArgument {
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
||||
const GetMyShiftsArguments({
|
||||
required this.start,
|
||||
required this.end,
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,10 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
/// Implementations of this interface should reside in the data layer.
|
||||
abstract interface class ShiftsRepositoryInterface {
|
||||
/// Retrieves the list of shifts assigned to the current user.
|
||||
Future<List<Shift>> getMyShifts();
|
||||
Future<List<Shift>> getMyShifts({
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
});
|
||||
|
||||
/// Retrieves available shifts matching the given [query] and [type].
|
||||
Future<List<Shift>> getAvailableShifts(String query, String type);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../arguments/get_my_shifts_arguments.dart';
|
||||
import '../repositories/shifts_repository_interface.dart';
|
||||
|
||||
/// Use case for retrieving the user's assigned shifts.
|
||||
///
|
||||
/// This use case delegates to [ShiftsRepositoryInterface].
|
||||
class GetMyShiftsUseCase extends NoInputUseCase<List<Shift>> {
|
||||
class GetMyShiftsUseCase extends UseCase<GetMyShiftsArguments, List<Shift>> {
|
||||
final ShiftsRepositoryInterface repository;
|
||||
|
||||
GetMyShiftsUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<List<Shift>> call() async {
|
||||
return repository.getMyShifts();
|
||||
Future<List<Shift>> call(GetMyShiftsArguments arguments) async {
|
||||
return repository.getMyShifts(
|
||||
start: arguments.start,
|
||||
end: arguments.end,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../../domain/arguments/get_available_shifts_arguments.dart';
|
||||
import '../../../domain/arguments/get_my_shifts_arguments.dart';
|
||||
import '../../../domain/usecases/get_available_shifts_usecase.dart';
|
||||
import '../../../domain/usecases/get_cancelled_shifts_usecase.dart';
|
||||
import '../../../domain/usecases/get_history_shifts_usecase.dart';
|
||||
@@ -28,6 +29,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
required this.getHistoryShifts,
|
||||
}) : super(ShiftsInitial()) {
|
||||
on<LoadShiftsEvent>(_onLoadShifts);
|
||||
on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange);
|
||||
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
|
||||
}
|
||||
|
||||
@@ -43,7 +45,10 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
// Or load all for simplicity as per prototype logic which had them all in memory.
|
||||
|
||||
try {
|
||||
final myShiftsResult = await getMyShifts();
|
||||
final List<DateTime> days = _getCalendarDaysForOffset(0);
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: days.first, end: days.last),
|
||||
);
|
||||
final pendingResult = await getPendingAssignments();
|
||||
final cancelledResult = await getCancelledShifts();
|
||||
final historyResult = await getHistoryShifts();
|
||||
@@ -65,6 +70,41 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadShiftsForRange(
|
||||
LoadShiftsForRangeEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
try {
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: event.start, end: event.end),
|
||||
);
|
||||
|
||||
if (state is ShiftsLoaded) {
|
||||
final currentState = state as ShiftsLoaded;
|
||||
emit(currentState.copyWith(myShifts: myShiftsResult));
|
||||
return;
|
||||
}
|
||||
|
||||
final pendingResult = await getPendingAssignments();
|
||||
final cancelledResult = await getCancelledShifts();
|
||||
final historyResult = await getHistoryShifts();
|
||||
final availableResult =
|
||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
|
||||
emit(ShiftsLoaded(
|
||||
myShifts: myShiftsResult,
|
||||
pendingShifts: pendingResult,
|
||||
cancelledShifts: cancelledResult,
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
historyShifts: historyResult,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
} catch (_) {
|
||||
emit(const ShiftsError('Failed to load shifts'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFilterAvailableShifts(
|
||||
FilterAvailableShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
@@ -91,6 +131,17 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
}
|
||||
}
|
||||
|
||||
List<DateTime> _getCalendarDaysForOffset(int weekOffset) {
|
||||
final now = DateTime.now();
|
||||
final int reactDayIndex = now.weekday == 7 ? 0 : now.weekday;
|
||||
final int daysSinceFriday = (reactDayIndex + 2) % 7;
|
||||
final start = now
|
||||
.subtract(Duration(days: daysSinceFriday))
|
||||
.add(Duration(days: weekOffset * 7));
|
||||
final startDate = DateTime(start.year, start.month, start.day);
|
||||
return List.generate(7, (index) => startDate.add(Duration(days: index)));
|
||||
}
|
||||
|
||||
List<Shift> _filterPastShifts(List<Shift> shifts) {
|
||||
final now = DateTime.now();
|
||||
return shifts.where((shift) {
|
||||
|
||||
@@ -10,6 +10,19 @@ sealed class ShiftsEvent extends Equatable {
|
||||
|
||||
class LoadShiftsEvent extends ShiftsEvent {}
|
||||
|
||||
class LoadShiftsForRangeEvent extends ShiftsEvent {
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
||||
const LoadShiftsForRangeEvent({
|
||||
required this.start,
|
||||
required this.end,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [start, end];
|
||||
}
|
||||
|
||||
class FilterAvailableShiftsEvent extends ShiftsEvent {
|
||||
final String? query;
|
||||
final String? jobType;
|
||||
|
||||
@@ -444,11 +444,11 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
if (displayShift!.status != 'confirmed' &&
|
||||
(displayShift!.hasApplied == true ||
|
||||
(displayShift!.requiredSlots == null ||
|
||||
displayShift!.filledSlots == null ||
|
||||
displayShift!.filledSlots! <
|
||||
displayShift!.requiredSlots!)))
|
||||
displayShift!.hasApplied != true &&
|
||||
(displayShift!.requiredSlots == null ||
|
||||
displayShift!.filledSlots == null ||
|
||||
displayShift!.filledSlots! <
|
||||
displayShift!.requiredSlots!))
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -470,31 +470,26 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
if ((displayShift!.hasApplied != true) &&
|
||||
(displayShift!.requiredSlots == null ||
|
||||
displayShift!.filledSlots == null ||
|
||||
displayShift!.filledSlots! <
|
||||
displayShift!.requiredSlots!))
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => _bookShift(
|
||||
context,
|
||||
displayShift!.id,
|
||||
displayShift!.roleId,
|
||||
DateTime.tryParse(displayShift!.date),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(
|
||||
0xFF10B981,
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
child: const Text("Book Shift"),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => _bookShift(
|
||||
context,
|
||||
displayShift!.id,
|
||||
displayShift!.roleId,
|
||||
DateTime.tryParse(displayShift!.date),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(
|
||||
0xFF10B981,
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
child: const Text("Book Shift"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
|
||||
@@ -38,6 +38,9 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
if (widget.initialDate != null) {
|
||||
_applyInitialDate(widget.initialDate!);
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadShiftsForCurrentWeek();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -69,6 +72,9 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
setState(() {
|
||||
_weekOffset = (diff / 7).floor();
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadShiftsForCurrentWeek();
|
||||
});
|
||||
}
|
||||
|
||||
List<DateTime> _getCalendarDays() {
|
||||
@@ -82,6 +88,16 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
return List.generate(7, (index) => startDate.add(Duration(days: index)));
|
||||
}
|
||||
|
||||
void _loadShiftsForCurrentWeek() {
|
||||
final List<DateTime> calendarDays = _getCalendarDays();
|
||||
context.read<ShiftsBloc>().add(
|
||||
LoadShiftsForRangeEvent(
|
||||
start: calendarDays.first,
|
||||
end: calendarDays.last,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isSameDay(DateTime a, DateTime b) {
|
||||
return a.year == b.year && a.month == b.month && a.day == b.day;
|
||||
}
|
||||
@@ -211,7 +227,11 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
size: 20,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
onPressed: () => setState(() => _weekOffset--),
|
||||
onPressed: () => setState(() {
|
||||
_weekOffset--;
|
||||
_selectedDate = _getCalendarDays().first;
|
||||
_loadShiftsForCurrentWeek();
|
||||
}),
|
||||
constraints: const BoxConstraints(),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
@@ -229,7 +249,11 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
size: 20,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
onPressed: () => setState(() => _weekOffset++),
|
||||
onPressed: () => setState(() {
|
||||
_weekOffset++;
|
||||
_selectedDate = _getCalendarDays().first;
|
||||
_loadShiftsForCurrentWeek();
|
||||
}),
|
||||
constraints: const BoxConstraints(),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user