little advance and corrections

This commit is contained in:
José Salazar
2026-02-02 06:00:39 +09:00
parent 5a2721eebb
commit a2020f9f98
14 changed files with 18588 additions and 17615 deletions

View File

@@ -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.');

View File

@@ -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,
});
}

View File

@@ -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);

View File

@@ -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,
);
}
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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(

View File

@@ -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,
),