correction of change view for recurring and permant - show permanet and recurring in find shift

This commit is contained in:
José Salazar
2026-02-18 19:23:15 -05:00
parent 83457a0a9b
commit 8b6061cb30
6 changed files with 323 additions and 13 deletions

View File

@@ -31,6 +31,9 @@ class Shift extends Equatable {
final bool? hasApplied;
final double? totalValue;
final Break? breakInfo;
final String? orderId;
final String? orderType;
final List<ShiftSchedule>? schedules;
const Shift({
required this.id,
@@ -62,6 +65,9 @@ class Shift extends Equatable {
this.hasApplied,
this.totalValue,
this.breakInfo,
this.orderId,
this.orderType,
this.schedules,
});
@override
@@ -95,9 +101,27 @@ class Shift extends Equatable {
hasApplied,
totalValue,
breakInfo,
orderId,
orderType,
schedules,
];
}
class ShiftSchedule extends Equatable {
const ShiftSchedule({
required this.date,
required this.startTime,
required this.endTime,
});
final String date;
final String startTime;
final String endTime;
@override
List<Object?> get props => <Object?>[date, startTime, endTime];
}
class ShiftManager extends Equatable {
const ShiftManager({required this.name, required this.phone, this.avatar});

View File

@@ -20,6 +20,42 @@ class PermanentOrderView extends StatelessWidget {
/// Creates a [PermanentOrderView].
const PermanentOrderView({super.key});
DateTime _firstPermanentShiftDate(
DateTime startDate,
List<String> permanentDays,
) {
final DateTime start = DateTime(startDate.year, startDate.month, startDate.day);
final DateTime end = start.add(const Duration(days: 29));
final Set<String> selected = permanentDays.toSet();
for (DateTime day = start; !day.isAfter(end); day = day.add(const Duration(days: 1))) {
if (selected.contains(_weekdayLabel(day))) {
return day;
}
}
return start;
}
String _weekdayLabel(DateTime date) {
switch (date.weekday) {
case DateTime.monday:
return 'MON';
case DateTime.tuesday:
return 'TUE';
case DateTime.wednesday:
return 'WED';
case DateTime.thursday:
return 'THU';
case DateTime.friday:
return 'FRI';
case DateTime.saturday:
return 'SAT';
case DateTime.sunday:
return 'SUN';
default:
return 'SUN';
}
}
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderPermanentEn labels =
@@ -42,6 +78,10 @@ class PermanentOrderView extends StatelessWidget {
},
builder: (BuildContext context, PermanentOrderState state) {
if (state.status == PermanentOrderStatus.success) {
final DateTime initialDate = _firstPermanentShiftDate(
state.startDate,
state.permanentDays,
);
return PermanentOrderSuccessView(
title: labels.title,
message: labels.subtitle,
@@ -50,7 +90,7 @@ class PermanentOrderView extends StatelessWidget {
ClientPaths.orders,
(_) => false,
arguments: <String, dynamic>{
'initialDate': state.startDate.toIso8601String(),
'initialDate': initialDate.toIso8601String(),
},
),
);

View File

@@ -20,6 +20,43 @@ class RecurringOrderView extends StatelessWidget {
/// Creates a [RecurringOrderView].
const RecurringOrderView({super.key});
DateTime _firstRecurringShiftDate(
DateTime startDate,
DateTime endDate,
List<String> recurringDays,
) {
final DateTime start = DateTime(startDate.year, startDate.month, startDate.day);
final DateTime end = DateTime(endDate.year, endDate.month, endDate.day);
final Set<String> selected = recurringDays.toSet();
for (DateTime day = start; !day.isAfter(end); day = day.add(const Duration(days: 1))) {
if (selected.contains(_weekdayLabel(day))) {
return day;
}
}
return start;
}
String _weekdayLabel(DateTime date) {
switch (date.weekday) {
case DateTime.monday:
return 'MON';
case DateTime.tuesday:
return 'TUE';
case DateTime.wednesday:
return 'WED';
case DateTime.thursday:
return 'THU';
case DateTime.friday:
return 'FRI';
case DateTime.saturday:
return 'SAT';
case DateTime.sunday:
return 'SUN';
default:
return 'SUN';
}
}
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderRecurringEn labels =
@@ -44,6 +81,15 @@ class RecurringOrderView extends StatelessWidget {
},
builder: (BuildContext context, RecurringOrderState state) {
if (state.status == RecurringOrderStatus.success) {
final DateTime maxEndDate =
state.startDate.add(const Duration(days: 29));
final DateTime effectiveEndDate =
state.endDate.isAfter(maxEndDate) ? maxEndDate : state.endDate;
final DateTime initialDate = _firstRecurringShiftDate(
state.startDate,
effectiveEndDate,
state.recurringDays,
);
return RecurringOrderSuccessView(
title: labels.title,
message: labels.subtitle,
@@ -52,7 +98,7 @@ class RecurringOrderView extends StatelessWidget {
ClientPaths.orders,
(_) => false,
arguments: <String, dynamic>{
'initialDate': state.startDate.toIso8601String(),
'initialDate': initialDate.toIso8601String(),
},
),
);

View File

@@ -227,6 +227,8 @@ class ShiftsRepositoryImpl
filledSlots: sr.assigned ?? 0,
latitude: sr.shift.latitude,
longitude: sr.shift.longitude,
orderId: sr.shift.order.id,
orderType: sr.shift.order.orderType?.stringValue,
breakInfo: BreakAdapter.fromData(
isPaid: sr.isBreakPaid ?? false,
breakTime: sr.breakType?.stringValue,

View File

@@ -77,6 +77,13 @@ class _MyShiftCardState extends State<MyShiftCard> {
String _getShiftType() {
// Handling potential localization key availability
try {
final String orderType = (widget.shift.orderType ?? '').toUpperCase();
if (orderType == 'PERMANENT') {
return t.staff_shifts.filter.long_term;
}
if (orderType == 'RECURRING') {
return t.staff_shifts.filter.multi_day;
}
if (widget.shift.durationDays != null && widget.shift.durationDays! > 30) {
return t.staff_shifts.filter.long_term;
}
@@ -133,6 +140,24 @@ class _MyShiftCardState extends State<MyShiftCard> {
statusText = status?.toUpperCase() ?? "";
}
final schedules = widget.shift.schedules ?? <ShiftSchedule>[];
final hasSchedules = schedules.isNotEmpty;
final List<ShiftSchedule> visibleSchedules = schedules.length <= 5
? schedules
: schedules.take(3).toList();
final int remainingSchedules =
schedules.length <= 5 ? 0 : schedules.length - 3;
final String scheduleRange = hasSchedules
? () {
final first = schedules.first.date;
final last = schedules.last.date;
if (first == last) {
return _formatDate(first);
}
return '${_formatDate(first)} ${_formatDate(last)}';
}()
: '';
return GestureDetector(
onTap: () {
Modular.to.pushNamed(
@@ -299,7 +324,55 @@ class _MyShiftCardState extends State<MyShiftCard> {
const SizedBox(height: UiConstants.space2),
// Date & Time
if (widget.shift.durationDays != null &&
if (hasSchedules) ...[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(
UiIcons.clock,
size: UiConstants.iconXs,
color: UiColors.primary,
),
const SizedBox(width: UiConstants.space1),
Text(
'${schedules.length} schedules',
style: UiTypography.footnote2m.copyWith(
color: UiColors.primary,
),
),
],
),
const SizedBox(height: UiConstants.space1),
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text(
scheduleRange,
style: UiTypography.footnote2r.copyWith(color: UiColors.primary),
),
),
...visibleSchedules.map(
(schedule) => Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text(
'${_formatDate(schedule.date)}, ${_formatTime(schedule.startTime)} ${_formatTime(schedule.endTime)}',
style: UiTypography.footnote2r.copyWith(
color: UiColors.primary,
),
),
),
),
if (remainingSchedules > 0)
Text(
'+$remainingSchedules more schedules',
style: UiTypography.footnote2r.copyWith(
color: UiColors.primary.withOpacity(0.7),
),
),
],
),
] else if (widget.shift.durationDays != null &&
widget.shift.durationDays! > 1) ...[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -327,13 +400,18 @@ class _MyShiftCardState extends State<MyShiftCard> {
padding: const EdgeInsets.only(bottom: 2),
child: Text(
'${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} ${_formatTime(widget.shift.endTime)}',
style: UiTypography.footnote2r.copyWith(color: UiColors.primary),
style: UiTypography.footnote2r.copyWith(
color: UiColors.primary,
),
),
),
if (widget.shift.durationDays! > 1)
Text(
'... +${widget.shift.durationDays! - 1} more days',
style: UiTypography.footnote2r.copyWith(color: UiColors.primary.withOpacity(0.7)),
style: UiTypography.footnote2r.copyWith(
color:
UiColors.primary.withOpacity(0.7),
),
)
],
),

View File

@@ -20,6 +20,119 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
String _searchQuery = '';
String _jobType = 'all';
bool _isRecurring(Shift shift) =>
(shift.orderType ?? '').toUpperCase() == 'RECURRING';
bool _isPermanent(Shift shift) =>
(shift.orderType ?? '').toUpperCase() == 'PERMANENT';
DateTime? _parseShiftDate(String date) {
if (date.isEmpty) return null;
try {
return DateTime.parse(date);
} catch (_) {
return null;
}
}
List<Shift> _groupMultiDayShifts(List<Shift> shifts) {
final Map<String, List<Shift>> grouped = <String, List<Shift>>{};
for (final shift in shifts) {
if (!_isRecurring(shift) && !_isPermanent(shift)) {
continue;
}
final orderId = shift.orderId;
final roleId = shift.roleId;
if (orderId == null || roleId == null) {
continue;
}
final key = '$orderId::$roleId';
grouped.putIfAbsent(key, () => <Shift>[]).add(shift);
}
final Set<String> addedGroups = <String>{};
final List<Shift> result = <Shift>[];
for (final shift in shifts) {
if (!_isRecurring(shift) && !_isPermanent(shift)) {
result.add(shift);
continue;
}
final orderId = shift.orderId;
final roleId = shift.roleId;
if (orderId == null || roleId == null) {
result.add(shift);
continue;
}
final key = '$orderId::$roleId';
if (addedGroups.contains(key)) {
continue;
}
addedGroups.add(key);
final List<Shift> group = grouped[key] ?? <Shift>[];
if (group.isEmpty) {
result.add(shift);
continue;
}
group.sort((a, b) {
final ad = _parseShiftDate(a.date);
final bd = _parseShiftDate(b.date);
if (ad == null && bd == null) return 0;
if (ad == null) return 1;
if (bd == null) return -1;
return ad.compareTo(bd);
});
final Shift first = group.first;
final List<ShiftSchedule> schedules = group
.map((s) => ShiftSchedule(
date: s.date,
startTime: s.startTime,
endTime: s.endTime,
))
.toList();
result.add(
Shift(
id: first.id,
roleId: first.roleId,
title: first.title,
clientName: first.clientName,
logoUrl: first.logoUrl,
hourlyRate: first.hourlyRate,
location: first.location,
locationAddress: first.locationAddress,
date: first.date,
startTime: first.startTime,
endTime: first.endTime,
createdDate: first.createdDate,
tipsAvailable: first.tipsAvailable,
travelTime: first.travelTime,
mealProvided: first.mealProvided,
parkingAvailable: first.parkingAvailable,
gasCompensation: first.gasCompensation,
description: first.description,
instructions: first.instructions,
managers: first.managers,
latitude: first.latitude,
longitude: first.longitude,
status: first.status,
durationDays: schedules.length,
requiredSlots: first.requiredSlots,
filledSlots: first.filledSlots,
hasApplied: first.hasApplied,
totalValue: first.totalValue,
breakInfo: first.breakInfo,
orderId: first.orderId,
orderType: first.orderType,
schedules: schedules,
),
);
}
return result;
}
Widget _buildFilterTab(String id, String label) {
final isSelected = _jobType == id;
return GestureDetector(
@@ -49,8 +162,10 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
@override
Widget build(BuildContext context) {
final groupedJobs = _groupMultiDayShifts(widget.availableJobs);
// Filter logic
final filteredJobs = widget.availableJobs.where((s) {
final filteredJobs = groupedJobs.where((s) {
final matchesSearch =
s.title.toLowerCase().contains(_searchQuery.toLowerCase()) ||
s.location.toLowerCase().contains(_searchQuery.toLowerCase()) ||
@@ -60,10 +175,15 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
if (_jobType == 'all') return true;
if (_jobType == 'one-day') {
if (_isRecurring(s) || _isPermanent(s)) return false;
return s.durationDays == null || s.durationDays! <= 1;
}
if (_jobType == 'multi-day') {
return s.durationDays != null && s.durationDays! > 1;
return _isRecurring(s) ||
(s.durationDays != null && s.durationDays! > 1);
}
if (_jobType == 'long-term') {
return _isPermanent(s);
}
return true;
}).toList();