refactor of usecases
This commit is contained in:
@@ -101,11 +101,17 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
||||
);
|
||||
} else if (state is ShiftDetailsError) {
|
||||
if (_isApplying) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: translateErrorKey(state.message),
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
final String errorMessage = state.message.toUpperCase();
|
||||
if (errorMessage.contains('ELIGIBILITY') ||
|
||||
errorMessage.contains('COMPLIANCE')) {
|
||||
_showEligibilityErrorDialog(context);
|
||||
} else {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: translateErrorKey(state.message),
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
_isApplying = false;
|
||||
}
|
||||
@@ -300,4 +306,38 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
_actionDialogOpen = false;
|
||||
}
|
||||
|
||||
void _showEligibilityErrorDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) => AlertDialog(
|
||||
backgroundColor: UiColors.bgPopup,
|
||||
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(UiIcons.warning, color: UiColors.error),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(child: Text("Eligibility Requirements")),
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
"You are missing required certifications or documents to claim this shift. Please upload them to continue.",
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
actions: [
|
||||
UiButton.secondary(
|
||||
text: "Cancel",
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
),
|
||||
UiButton.primary(
|
||||
text: "Go to Certificates",
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
Modular.to.pushNamed(StaffPaths.certificates);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ class MyShiftCard extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyShiftCardState extends State<MyShiftCard> {
|
||||
bool _isSubmitted = false;
|
||||
|
||||
String _formatTime(String time) {
|
||||
if (time.isEmpty) return '';
|
||||
try {
|
||||
@@ -477,6 +479,37 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (status == 'completed') ...[
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const Divider(),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
_isSubmitted ? 'SUBMITTED' : 'READY TO SUBMIT',
|
||||
style: UiTypography.footnote2b.copyWith(
|
||||
color: _isSubmitted ? UiColors.textSuccess : UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (!_isSubmitted)
|
||||
UiButton.secondary(
|
||||
text: 'Submit for Approval',
|
||||
size: UiButtonSize.small,
|
||||
onPressed: () {
|
||||
setState(() => _isSubmitted = true);
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: 'Timesheet submitted for client approval',
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
const Icon(UiIcons.success, color: UiColors.iconSuccess, size: 20),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../blocs/shifts/shifts_bloc.dart';
|
||||
import '../my_shift_card.dart';
|
||||
import '../shared/empty_state_view.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
class FindShiftsTab extends StatefulWidget {
|
||||
final List<Shift> availableJobs;
|
||||
@@ -20,6 +21,109 @@ class FindShiftsTab extends StatefulWidget {
|
||||
class _FindShiftsTabState extends State<FindShiftsTab> {
|
||||
String _searchQuery = '';
|
||||
String _jobType = 'all';
|
||||
double? _maxDistance; // miles
|
||||
Position? _currentPosition;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initLocation();
|
||||
}
|
||||
|
||||
Future<void> _initLocation() async {
|
||||
try {
|
||||
final LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.always ||
|
||||
permission == LocationPermission.whileInUse) {
|
||||
final Position pos = await Geolocator.getCurrentPosition();
|
||||
if (mounted) {
|
||||
setState(() => _currentPosition = pos);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
double _calculateDistance(double lat, double lng) {
|
||||
if (_currentPosition == null) return -1;
|
||||
final double distMeters = Geolocator.distanceBetween(
|
||||
_currentPosition!.latitude,
|
||||
_currentPosition!.longitude,
|
||||
lat,
|
||||
lng,
|
||||
);
|
||||
return distMeters / 1609.34; // meters to miles
|
||||
}
|
||||
|
||||
void _showDistanceFilter() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: UiColors.bgPopup,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setModalState) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.staff_shifts.find_shifts.radius_filter_title,
|
||||
style: UiTypography.headline4m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_maxDistance == null
|
||||
? context.t.staff_shifts.find_shifts.unlimited_distance
|
||||
: context.t.staff_shifts.find_shifts.within_miles(
|
||||
miles: _maxDistance!.round().toString(),
|
||||
),
|
||||
style: UiTypography.body2m.textSecondary,
|
||||
),
|
||||
Slider(
|
||||
value: _maxDistance ?? 100,
|
||||
min: 5,
|
||||
max: 100,
|
||||
divisions: 19,
|
||||
activeColor: UiColors.primary,
|
||||
onChanged: (double val) {
|
||||
setModalState(() => _maxDistance = val);
|
||||
setState(() => _maxDistance = val);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiButton.secondary(
|
||||
text: context.t.staff_shifts.find_shifts.clear,
|
||||
onPressed: () {
|
||||
setModalState(() => _maxDistance = null);
|
||||
setState(() => _maxDistance = null);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: UiButton.primary(
|
||||
text: context.t.staff_shifts.find_shifts.apply,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool _isRecurring(Shift shift) =>
|
||||
(shift.orderType ?? '').toUpperCase() == 'RECURRING';
|
||||
@@ -178,6 +282,11 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
||||
|
||||
if (!matchesSearch) return false;
|
||||
|
||||
if (_maxDistance != null && s.latitude != null && s.longitude != null) {
|
||||
final double dist = _calculateDistance(s.latitude!, s.longitude!);
|
||||
if (dist > _maxDistance!) return false;
|
||||
}
|
||||
|
||||
if (_jobType == 'all') return true;
|
||||
if (_jobType == 'one-day') {
|
||||
if (_isRecurring(s) || _isPermanent(s)) return false;
|
||||
@@ -248,20 +357,31 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Container(
|
||||
height: 48,
|
||||
width: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
GestureDetector(
|
||||
onTap: _showDistanceFilter,
|
||||
child: Container(
|
||||
height: 48,
|
||||
width: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: _maxDistance != null
|
||||
? UiColors.primary.withValues(alpha: 0.1)
|
||||
: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
border: Border.all(
|
||||
color: _maxDistance != null
|
||||
? UiColors.primary
|
||||
: UiColors.border,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
UiIcons.filter,
|
||||
size: 18,
|
||||
color: _maxDistance != null
|
||||
? UiColors.primary
|
||||
: UiColors.textSecondary,
|
||||
),
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: const Icon(
|
||||
UiIcons.filter,
|
||||
size: 18,
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user