feat: integrate Google Maps Places autocomplete for hub address validation and remove activity log functionality
This commit is contained in:
@@ -129,6 +129,8 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||
createdDate: _toDateTime(fullShift.createdAt)?.toIso8601String() ?? '',
|
||||
status: fullShift.status?.stringValue,
|
||||
description: fullShift.description,
|
||||
latitude: fullShift.latitude,
|
||||
longitude: fullShift.longitude,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -186,10 +188,4 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||
|
||||
return getAttendanceStatus();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> getActivityLog() async {
|
||||
// Placeholder as this wasn't main focus and returns raw maps
|
||||
return <Map<String, dynamic>>[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,4 @@ abstract class ClockInRepositoryInterface {
|
||||
/// Checks the user out for the currently active shift.
|
||||
/// Optionally accepts [breakTimeMinutes] if tracked.
|
||||
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes});
|
||||
|
||||
/// Retrieves a list of recent clock-in/out activities.
|
||||
Future<List<Map<String, dynamic>>> getActivityLog();
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../repositories/clock_in_repository_interface.dart';
|
||||
|
||||
/// Use case for retrieving the activity log.
|
||||
class GetActivityLogUseCase implements NoInputUseCase<List<Map<String, dynamic>>> {
|
||||
final ClockInRepositoryInterface _repository;
|
||||
|
||||
GetActivityLogUseCase(this._repository);
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> call() {
|
||||
return _repository.getActivityLog();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import '../../domain/usecases/get_todays_shift_usecase.dart';
|
||||
import '../../domain/usecases/get_attendance_status_usecase.dart';
|
||||
import '../../domain/usecases/clock_in_usecase.dart';
|
||||
import '../../domain/usecases/clock_out_usecase.dart';
|
||||
import '../../domain/usecases/get_activity_log_usecase.dart';
|
||||
import '../../domain/arguments/clock_in_arguments.dart';
|
||||
import '../../domain/arguments/clock_out_arguments.dart';
|
||||
import 'clock_in_event.dart';
|
||||
@@ -15,11 +14,8 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
final GetAttendanceStatusUseCase _getAttendanceStatus;
|
||||
final ClockInUseCase _clockIn;
|
||||
final ClockOutUseCase _clockOut;
|
||||
final GetActivityLogUseCase _getActivityLog;
|
||||
|
||||
// Mock Venue Location (e.g., Grand Hotel, NYC)
|
||||
static const double venueLat = 40.7128;
|
||||
static const double venueLng = -74.0060;
|
||||
static const double allowedRadiusMeters = 500;
|
||||
|
||||
ClockInBloc({
|
||||
@@ -27,12 +23,10 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
required GetAttendanceStatusUseCase getAttendanceStatus,
|
||||
required ClockInUseCase clockIn,
|
||||
required ClockOutUseCase clockOut,
|
||||
required GetActivityLogUseCase getActivityLog,
|
||||
}) : _getTodaysShift = getTodaysShift,
|
||||
_getAttendanceStatus = getAttendanceStatus,
|
||||
_clockIn = clockIn,
|
||||
_clockOut = clockOut,
|
||||
_getActivityLog = getActivityLog,
|
||||
super(ClockInState(selectedDate: DateTime.now())) {
|
||||
on<ClockInPageLoaded>(_onLoaded);
|
||||
on<DateSelected>(_onDateSelected);
|
||||
@@ -54,7 +48,6 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
try {
|
||||
final shift = await _getTodaysShift();
|
||||
final status = await _getAttendanceStatus();
|
||||
final activity = await _getActivityLog();
|
||||
|
||||
// Check permissions silently on load? Maybe better to wait for user interaction or specific event
|
||||
// However, if shift exists, we might want to check permission state
|
||||
@@ -63,7 +56,6 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
status: ClockInStatus.success,
|
||||
todayShift: shift,
|
||||
attendance: status,
|
||||
activityLog: activity,
|
||||
));
|
||||
|
||||
if (shift != null && !status.isCheckedIn) {
|
||||
@@ -103,13 +95,23 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
void _startLocationUpdates() async {
|
||||
try {
|
||||
final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
|
||||
final distance = Geolocator.distanceBetween(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
venueLat,
|
||||
venueLng,
|
||||
);
|
||||
final isVerified = distance <= allowedRadiusMeters;
|
||||
|
||||
double distance = 0;
|
||||
bool isVerified = false; // Require location match by default if shift has location
|
||||
|
||||
if (state.todayShift != null && state.todayShift!.latitude != null && state.todayShift!.longitude != null) {
|
||||
distance = Geolocator.distanceBetween(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
state.todayShift!.latitude!,
|
||||
state.todayShift!.longitude!,
|
||||
);
|
||||
isVerified = distance <= allowedRadiusMeters;
|
||||
} else {
|
||||
// If no shift location, assume verified or don't restrict?
|
||||
// For strict clock-in, maybe false? but let's default to verified to avoid blocking if data missing
|
||||
isVerified = true;
|
||||
}
|
||||
|
||||
if (!isClosed) {
|
||||
add(LocationUpdated(position: position, distance: distance, isVerified: isVerified));
|
||||
|
||||
@@ -9,7 +9,6 @@ class ClockInState extends Equatable {
|
||||
final ClockInStatus status;
|
||||
final Shift? todayShift;
|
||||
final AttendanceStatus attendance;
|
||||
final List<Map<String, dynamic>> activityLog;
|
||||
final DateTime selectedDate;
|
||||
final String checkInMode;
|
||||
final String? errorMessage;
|
||||
@@ -25,7 +24,6 @@ class ClockInState extends Equatable {
|
||||
this.status = ClockInStatus.initial,
|
||||
this.todayShift,
|
||||
this.attendance = const AttendanceStatus(),
|
||||
this.activityLog = const [],
|
||||
required this.selectedDate,
|
||||
this.checkInMode = 'swipe',
|
||||
this.errorMessage,
|
||||
@@ -41,7 +39,6 @@ class ClockInState extends Equatable {
|
||||
ClockInStatus? status,
|
||||
Shift? todayShift,
|
||||
AttendanceStatus? attendance,
|
||||
List<Map<String, dynamic>>? activityLog,
|
||||
DateTime? selectedDate,
|
||||
String? checkInMode,
|
||||
String? errorMessage,
|
||||
@@ -56,7 +53,6 @@ class ClockInState extends Equatable {
|
||||
status: status ?? this.status,
|
||||
todayShift: todayShift ?? this.todayShift,
|
||||
attendance: attendance ?? this.attendance,
|
||||
activityLog: activityLog ?? this.activityLog,
|
||||
selectedDate: selectedDate ?? this.selectedDate,
|
||||
checkInMode: checkInMode ?? this.checkInMode,
|
||||
errorMessage: errorMessage,
|
||||
@@ -74,7 +70,6 @@ class ClockInState extends Equatable {
|
||||
status,
|
||||
todayShift,
|
||||
attendance,
|
||||
activityLog,
|
||||
selectedDate,
|
||||
checkInMode,
|
||||
errorMessage,
|
||||
|
||||
@@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
|
||||
import '../bloc/clock_in_bloc.dart';
|
||||
import '../bloc/clock_in_event.dart';
|
||||
import '../bloc/clock_in_state.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
import '../widgets/attendance_card.dart';
|
||||
import '../widgets/commute_tracker.dart';
|
||||
import '../widgets/date_selector.dart';
|
||||
import '../widgets/lunch_break_modal.dart';
|
||||
@@ -135,13 +135,6 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
border: Border.all(
|
||||
color: const Color(0xFFE2E8F0),
|
||||
), // slate-200
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -417,78 +410,7 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Recent Activity List
|
||||
if (state.activityLog.isNotEmpty)
|
||||
...state.activityLog.map(
|
||||
(activity) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8F9FA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
), // slate-100
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.krowBlue.withOpacity(
|
||||
0.1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.mapPin,
|
||||
color: AppColors.krowBlue,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('MMM d').format(
|
||||
activity['date'] as DateTime,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(
|
||||
0xFF0F172A,
|
||||
), // slate-900
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${activity['start']} - ${activity['end']}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(
|
||||
0xFF64748B,
|
||||
), // slate-500
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
activity['hours'] as String,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.krowBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Recent Activity List (Temporarily removed)
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
@@ -552,87 +474,6 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: AppColors.krowBlue.withOpacity(0.2),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: AppColors.krowBlue.withOpacity(0.1),
|
||||
child: const Text(
|
||||
'K',
|
||||
style: TextStyle(
|
||||
color: AppColors.krowBlue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Good Morning',
|
||||
style: TextStyle(color: AppColors.krowMuted, fontSize: 12),
|
||||
),
|
||||
Text(
|
||||
'Krower',
|
||||
style: TextStyle(
|
||||
color: AppColors.krowCharcoal,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Warehouse Assistant',
|
||||
style: TextStyle(color: AppColors.krowMuted, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
20,
|
||||
), // Rounded full for this page per design
|
||||
border: Border.all(color: Colors.grey.shade100),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.bell,
|
||||
color: AppColors.krowMuted,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showNFCDialog(BuildContext context) async {
|
||||
bool scanned = false;
|
||||
|
||||
@@ -739,23 +580,32 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
// --- Helper Methods ---
|
||||
|
||||
String _formatTime(String timeStr) {
|
||||
// Expecting HH:mm or HH:mm:ss
|
||||
if (timeStr.isEmpty) return '';
|
||||
try {
|
||||
if (timeStr.isEmpty) return '';
|
||||
final parts = timeStr.split(':');
|
||||
final dt = DateTime(2022, 1, 1, int.parse(parts[0]), int.parse(parts[1]));
|
||||
// Try parsing as ISO string first (which contains date)
|
||||
final dt = DateTime.parse(timeStr);
|
||||
return DateFormat('h:mm a').format(dt);
|
||||
} catch (e) {
|
||||
return timeStr;
|
||||
} catch (_) {
|
||||
// Fallback for strict "HH:mm" or "HH:mm:ss" strings
|
||||
try {
|
||||
final parts = timeStr.split(':');
|
||||
if (parts.length >= 2) {
|
||||
final dt = DateTime(2022, 1, 1, int.parse(parts[0]), int.parse(parts[1]));
|
||||
return DateFormat('h:mm a').format(dt);
|
||||
}
|
||||
return timeStr;
|
||||
} catch (e) {
|
||||
return timeStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isCheckInAllowed(dynamic shift) {
|
||||
bool _isCheckInAllowed(Shift shift) {
|
||||
if (shift == null) return false;
|
||||
try {
|
||||
// Parse shift date (e.g. 2024-01-31T09:00:00)
|
||||
// The Shift entity has 'date' which is the start DateTime string
|
||||
final shiftStart = DateTime.parse(shift.date);
|
||||
final shiftStart = DateTime.parse(shift.startTime);
|
||||
final windowStart = shiftStart.subtract(const Duration(minutes: 15));
|
||||
return DateTime.now().isAfter(windowStart);
|
||||
} catch (e) {
|
||||
@@ -764,10 +614,10 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
}
|
||||
}
|
||||
|
||||
String _getCheckInAvailabilityTime(dynamic shift) {
|
||||
String _getCheckInAvailabilityTime(Shift shift) {
|
||||
if (shift == null) return '';
|
||||
try {
|
||||
final shiftStart = DateTime.parse(shift.date);
|
||||
final shiftStart = DateTime.parse(shift.endTime);
|
||||
final windowStart = shiftStart.subtract(const Duration(minutes: 15));
|
||||
return DateFormat('h:mm a').format(windowStart);
|
||||
} catch (e) {
|
||||
|
||||
@@ -66,9 +66,14 @@ class _CommuteTrackerState extends State<CommuteTracker> {
|
||||
|
||||
// For demo purposes, check if we're within 24 hours of shift
|
||||
final now = DateTime.now();
|
||||
final shiftStart = DateTime.parse(
|
||||
'${widget.shift!.date} ${widget.shift!.startTime}',
|
||||
);
|
||||
DateTime shiftStart;
|
||||
try {
|
||||
shiftStart = DateTime.parse(widget.shift!.startTime);
|
||||
} catch (_) {
|
||||
shiftStart = DateTime.parse(
|
||||
'${widget.shift!.date} ${widget.shift!.startTime}',
|
||||
);
|
||||
}
|
||||
final hoursUntilShift = shiftStart.difference(now).inHours;
|
||||
final inCommuteWindow = hoursUntilShift <= 24 && hoursUntilShift >= 0;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'data/repositories_impl/clock_in_repository_impl.dart';
|
||||
import 'domain/repositories/clock_in_repository_interface.dart';
|
||||
import 'domain/usecases/clock_in_usecase.dart';
|
||||
import 'domain/usecases/clock_out_usecase.dart';
|
||||
import 'domain/usecases/get_activity_log_usecase.dart';
|
||||
import 'domain/usecases/get_attendance_status_usecase.dart';
|
||||
import 'domain/usecases/get_todays_shift_usecase.dart';
|
||||
import 'presentation/bloc/clock_in_bloc.dart';
|
||||
@@ -28,7 +27,6 @@ class StaffClockInModule extends Module {
|
||||
i.add<GetAttendanceStatusUseCase>(GetAttendanceStatusUseCase.new);
|
||||
i.add<ClockInUseCase>(ClockInUseCase.new);
|
||||
i.add<ClockOutUseCase>(ClockOutUseCase.new);
|
||||
i.add<GetActivityLogUseCase>(GetActivityLogUseCase.new);
|
||||
|
||||
// BLoC
|
||||
i.add<ClockInBloc>(ClockInBloc.new);
|
||||
|
||||
@@ -2,7 +2,11 @@ import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
extension ShiftsNavigator on IModularNavigator {
|
||||
void navigateToShiftsHome() {
|
||||
navigate('/worker-main/shifts/');
|
||||
}
|
||||
|
||||
void pushShiftDetails(Shift shift) {
|
||||
pushNamed('/worker-main/shift-details/${shift.id}', arguments: shift);
|
||||
navigate('/worker-main/shift-details/${shift.id}', arguments: shift);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user