refactor of usecases

This commit is contained in:
2026-02-23 17:18:50 +05:30
parent 56666ece30
commit 13f8003bda
37 changed files with 1563 additions and 105 deletions

View File

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

View File

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

View File

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