feat: add ShiftAssignmentCard widget and StaffShifts module
- Implemented ShiftAssignmentCard widget for displaying shift assignments with client details, pay calculation, and confirmation actions. - Created StaffShiftsModule to manage dependencies, routes, and use cases related to staff shifts. - Added necessary dependencies in pubspec.yaml and generated pubspec.lock.
This commit is contained in:
@@ -662,6 +662,48 @@
|
||||
"upload_required": "✓ Upload photos of required items",
|
||||
"accept_attestation": "✓ Accept attestation"
|
||||
}
|
||||
},
|
||||
"staff_shifts": {
|
||||
"title": "Shifts",
|
||||
"tabs": {
|
||||
"my_shifts": "My Shifts",
|
||||
"find_work": "Find Work"
|
||||
},
|
||||
"list": {
|
||||
"no_shifts": "No shifts found",
|
||||
"pending_offers": "PENDING OFFERS",
|
||||
"available_jobs": "$count AVAILABLE JOBS",
|
||||
"search_hint": "Search jobs..."
|
||||
},
|
||||
"filter": {
|
||||
"all": "All Jobs",
|
||||
"one_day": "One Day",
|
||||
"multi_day": "Multi Day",
|
||||
"long_term": "Long Term"
|
||||
},
|
||||
"status": {
|
||||
"confirmed": "CONFIRMED",
|
||||
"act_now": "ACT NOW",
|
||||
"swap_requested": "SWAP REQUESTED",
|
||||
"completed": "COMPLETED",
|
||||
"no_show": "NO SHOW",
|
||||
"pending_warning": "Please confirm assignment"
|
||||
},
|
||||
"action": {
|
||||
"decline": "Decline",
|
||||
"confirm": "Confirm",
|
||||
"request_swap": "Request Swap"
|
||||
},
|
||||
"details": {
|
||||
"additional": "ADDITIONAL DETAILS",
|
||||
"days": "$days Days",
|
||||
"exp_total": "(exp.total \\$$amount)",
|
||||
"pending_time": "Pending $time ago"
|
||||
},
|
||||
"tags": {
|
||||
"immediate_start": "Immediate start",
|
||||
"no_experience": "No experience"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -661,5 +661,47 @@
|
||||
"upload_required": "✓ Subir fotos de artículos requeridos",
|
||||
"accept_attestation": "✓ Aceptar certificación"
|
||||
}
|
||||
},
|
||||
"staff_shifts": {
|
||||
"title": "Shifts",
|
||||
"tabs": {
|
||||
"my_shifts": "My Shifts",
|
||||
"find_work": "Find Work"
|
||||
},
|
||||
"list": {
|
||||
"no_shifts": "No shifts found",
|
||||
"pending_offers": "PENDING OFFERS",
|
||||
"available_jobs": "$count AVAILABLE JOBS",
|
||||
"search_hint": "Search jobs..."
|
||||
},
|
||||
"filter": {
|
||||
"all": "All Jobs",
|
||||
"one_day": "One Day",
|
||||
"multi_day": "Multi Day",
|
||||
"long_term": "Long Term"
|
||||
},
|
||||
"status": {
|
||||
"confirmed": "CONFIRMED",
|
||||
"act_now": "ACT NOW",
|
||||
"swap_requested": "SWAP REQUESTED",
|
||||
"completed": "COMPLETED",
|
||||
"no_show": "NO SHOW",
|
||||
"pending_warning": "Please confirm assignment"
|
||||
},
|
||||
"action": {
|
||||
"decline": "Decline",
|
||||
"confirm": "Confirm",
|
||||
"request_swap": "Request Swap"
|
||||
},
|
||||
"details": {
|
||||
"additional": "ADDITIONAL DETAILS",
|
||||
"days": "$days Days",
|
||||
"exp_total": "(exp.total \\$$amount)",
|
||||
"pending_time": "Pending $time ago"
|
||||
},
|
||||
"tags": {
|
||||
"immediate_start": "Immediate start",
|
||||
"no_experience": "No experience"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
library;
|
||||
|
||||
export 'src/mocks/auth_repository_mock.dart';
|
||||
export 'src/mocks/shifts_repository_mock.dart';
|
||||
export 'src/mocks/staff_repository_mock.dart';
|
||||
export 'src/mocks/profile_repository_mock.dart';
|
||||
export 'src/mocks/event_repository_mock.dart';
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
// TODO: Implement EventRepositoryInterface once defined in a feature package.
|
||||
class EventRepositoryMock {
|
||||
Future<Assignment> applyForPosition(String positionId, String staffId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 600));
|
||||
return Assignment(
|
||||
id: 'assign_1',
|
||||
positionId: positionId,
|
||||
@@ -13,12 +13,12 @@ class EventRepositoryMock {
|
||||
}
|
||||
|
||||
Future<Event?> getEvent(String id) async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||
return _mockEvent;
|
||||
}
|
||||
|
||||
Future<List<EventShift>> getEventShifts(String eventId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||
return <EventShift>[
|
||||
const EventShift(
|
||||
id: 'shift_1',
|
||||
@@ -30,7 +30,7 @@ class EventRepositoryMock {
|
||||
}
|
||||
|
||||
Future<List<Assignment>> getStaffAssignments(String staffId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
return <Assignment>[
|
||||
const Assignment(
|
||||
id: 'assign_1',
|
||||
@@ -42,7 +42,7 @@ class EventRepositoryMock {
|
||||
}
|
||||
|
||||
Future<List<Event>> getUpcomingEvents() async {
|
||||
await Future.delayed(const Duration(milliseconds: 800));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 800));
|
||||
return <Event>[_mockEvent];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
// Mock Implementation for now.
|
||||
class ShiftsRepositoryMock {
|
||||
|
||||
Future<List<Shift>> getMyShifts() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
return [
|
||||
Shift(
|
||||
id: 'm1',
|
||||
title: 'Warehouse Assistant',
|
||||
clientName: 'Amazon',
|
||||
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Amazon_2024.svg/500px-Amazon_2024.svg.png',
|
||||
hourlyRate: 22.5,
|
||||
date: DateFormat('yyyy-MM-dd').format(DateTime.now().add(const Duration(days: 1))),
|
||||
startTime: '09:00',
|
||||
endTime: '17:00',
|
||||
location: 'Logistics Park',
|
||||
locationAddress: '456 Industrial Way',
|
||||
status: 'confirmed',
|
||||
createdDate: DateTime.now().toIso8601String(),
|
||||
description: 'Standard warehouse duties. Safety boots required.',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<Shift>> getAvailableShifts() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
return [
|
||||
Shift(
|
||||
id: 'a1',
|
||||
title: 'Bartender',
|
||||
clientName: 'Club Luxe',
|
||||
logoUrl: null,
|
||||
hourlyRate: 30.0,
|
||||
date: DateFormat('yyyy-MM-dd').format(DateTime.now().add(const Duration(days: 3))),
|
||||
startTime: '20:00',
|
||||
endTime: '02:00',
|
||||
location: 'City Center',
|
||||
locationAddress: '789 Nightlife Blvd',
|
||||
status: 'open',
|
||||
createdDate: DateTime.now().toIso8601String(),
|
||||
description: 'Experience mixing cocktails required.',
|
||||
),
|
||||
// Add more mocks if needed
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<Shift>> getPendingAssignments() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
return [
|
||||
Shift(
|
||||
id: 'p1',
|
||||
title: 'Event Server',
|
||||
clientName: 'Grand Hotel',
|
||||
logoUrl: null,
|
||||
hourlyRate: 25.0,
|
||||
date: DateFormat('yyyy-MM-dd').format(DateTime.now().add(const Duration(days: 2))),
|
||||
startTime: '16:00',
|
||||
endTime: '22:00',
|
||||
location: 'Downtown',
|
||||
locationAddress: '123 Main St',
|
||||
status: 'pending',
|
||||
createdDate: DateTime.now().toIso8601String(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<Shift?> getShiftDetails(String shiftId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
return Shift(
|
||||
id: shiftId,
|
||||
title: 'Event Server',
|
||||
clientName: 'Grand Hotel',
|
||||
logoUrl: null,
|
||||
hourlyRate: 25.0,
|
||||
date: DateFormat('yyyy-MM-dd').format(DateTime.now()),
|
||||
startTime: '16:00',
|
||||
endTime: '22:00',
|
||||
location: 'Downtown',
|
||||
locationAddress: '123 Main St, New York, NY',
|
||||
status: 'open',
|
||||
createdDate: DateTime.now().toIso8601String(),
|
||||
description: 'Provide exceptional customer service. Respond to guest requests or concerns promptly and professionally.',
|
||||
managers: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,17 @@ export 'src/entities/business/business.dart';
|
||||
export 'src/entities/business/business_setting.dart';
|
||||
export 'src/entities/business/hub.dart';
|
||||
export 'src/entities/business/hub_department.dart';
|
||||
export 'src/entities/business/biz_contract.dart';
|
||||
export 'src/entities/business/vendor.dart';
|
||||
|
||||
// Events & Shifts
|
||||
// Events & Assignments
|
||||
export 'src/entities/events/event.dart';
|
||||
export 'src/entities/events/event_shift.dart';
|
||||
export 'src/entities/events/event_shift_position.dart';
|
||||
export 'src/entities/events/assignment.dart';
|
||||
export 'src/entities/events/work_session.dart';
|
||||
|
||||
// Shifts
|
||||
export 'src/entities/shifts/shift.dart';
|
||||
|
||||
// Orders & Requests
|
||||
export 'src/entities/orders/order_type.dart';
|
||||
export 'src/entities/orders/one_time_order.dart';
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class Shift extends Equatable {
|
||||
final String id;
|
||||
final String title;
|
||||
final String clientName;
|
||||
final String? logoUrl;
|
||||
final double hourlyRate;
|
||||
final String location;
|
||||
final String locationAddress;
|
||||
final String date;
|
||||
final String startTime;
|
||||
final String endTime;
|
||||
final String createdDate;
|
||||
final bool? tipsAvailable;
|
||||
final bool? travelTime;
|
||||
final bool? mealProvided;
|
||||
final bool? parkingAvailable;
|
||||
final bool? gasCompensation;
|
||||
final String? description;
|
||||
final String? instructions;
|
||||
final List<ShiftManager>? managers;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final String? status;
|
||||
final int? durationDays; // For multi-day shifts
|
||||
|
||||
const Shift({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.clientName,
|
||||
this.logoUrl,
|
||||
required this.hourlyRate,
|
||||
required this.location,
|
||||
required this.locationAddress,
|
||||
required this.date,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.createdDate,
|
||||
this.tipsAvailable,
|
||||
this.travelTime,
|
||||
this.mealProvided,
|
||||
this.parkingAvailable,
|
||||
this.gasCompensation,
|
||||
this.description,
|
||||
this.instructions,
|
||||
this.managers,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.status,
|
||||
this.durationDays,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
title,
|
||||
clientName,
|
||||
logoUrl,
|
||||
hourlyRate,
|
||||
location,
|
||||
locationAddress,
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
createdDate,
|
||||
tipsAvailable,
|
||||
travelTime,
|
||||
mealProvided,
|
||||
parkingAvailable,
|
||||
gasCompensation,
|
||||
description,
|
||||
instructions,
|
||||
managers,
|
||||
latitude,
|
||||
longitude,
|
||||
status,
|
||||
durationDays,
|
||||
];
|
||||
}
|
||||
|
||||
class ShiftManager extends Equatable {
|
||||
final String name;
|
||||
final String phone;
|
||||
final String? avatar;
|
||||
|
||||
const ShiftManager({required this.name, required this.phone, this.avatar});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, phone, avatar];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# Add project specific rules here
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/shifts_repository_interface.dart';
|
||||
|
||||
/// Implementation of [ShiftsRepositoryInterface] that delegates to [ShiftsRepositoryMock].
|
||||
///
|
||||
/// This class resides in the data layer and handles the communication with
|
||||
/// the external data sources (currently mocks).
|
||||
class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
final ShiftsRepositoryMock _mock;
|
||||
|
||||
ShiftsRepositoryImpl({ShiftsRepositoryMock? mock}) : _mock = mock ?? ShiftsRepositoryMock();
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getMyShifts() async {
|
||||
return _mock.getMyShifts();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getAvailableShifts(String query, String type) async {
|
||||
// Delegates to mock. Logic kept here temporarily as per architecture constraints
|
||||
// on data_connect modifications, mimicking a query capable datasource.
|
||||
var shifts = await _mock.getAvailableShifts();
|
||||
|
||||
// Simple in-memory filtering for mock adapter
|
||||
if (query.isNotEmpty) {
|
||||
shifts = shifts.where((s) =>
|
||||
s.title.toLowerCase().contains(query.toLowerCase()) ||
|
||||
s.clientName.toLowerCase().contains(query.toLowerCase())
|
||||
).toList();
|
||||
}
|
||||
|
||||
if (type != 'all') {
|
||||
if (type == 'one-day') {
|
||||
shifts = shifts.where((s) => !s.title.contains('Multi-Day') && !s.title.contains('Long Term')).toList();
|
||||
} else if (type == 'multi-day') {
|
||||
shifts = shifts.where((s) => s.title.contains('Multi-Day')).toList();
|
||||
} else if (type == 'long-term') {
|
||||
shifts = shifts.where((s) => s.title.contains('Long Term')).toList();
|
||||
}
|
||||
}
|
||||
|
||||
return shifts;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getPendingAssignments() async {
|
||||
return _mock.getPendingAssignments();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Shift?> getShiftDetails(String shiftId) async {
|
||||
return _mock.getShiftDetails(shiftId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> applyForShift(String shiftId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> acceptShift(String shiftId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> declineShift(String shiftId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// Arguments for [GetAvailableShiftsUseCase].
|
||||
class GetAvailableShiftsArguments extends UseCaseArgument {
|
||||
/// The search query to filter shifts.
|
||||
final String query;
|
||||
|
||||
/// The job type filter (e.g., 'all', 'one-day', 'multi-day', 'long-term').
|
||||
final String type;
|
||||
|
||||
/// Creates a [GetAvailableShiftsArguments] instance.
|
||||
const GetAvailableShiftsArguments({
|
||||
this.query = '',
|
||||
this.type = 'all',
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [query, type];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Interface for the Shifts Repository.
|
||||
///
|
||||
/// Defines the contract for accessing and modifying shift-related data.
|
||||
/// 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();
|
||||
|
||||
/// Retrieves available shifts matching the given [query] and [type].
|
||||
Future<List<Shift>> getAvailableShifts(String query, String type);
|
||||
|
||||
/// Retrieves shifts that are pending acceptance by the user.
|
||||
Future<List<Shift>> getPendingAssignments();
|
||||
|
||||
/// Retrieves detailed information for a specific shift by [shiftId].
|
||||
Future<Shift?> getShiftDetails(String shiftId);
|
||||
|
||||
/// Applies for a specific open shift.
|
||||
Future<void> applyForShift(String shiftId);
|
||||
|
||||
/// Accepts a pending shift assignment.
|
||||
Future<void> acceptShift(String shiftId);
|
||||
|
||||
/// Declines a pending shift assignment.
|
||||
Future<void> declineShift(String shiftId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../repositories/shifts_repository_interface.dart';
|
||||
import '../arguments/get_available_shifts_arguments.dart';
|
||||
|
||||
/// Use case for retrieving available shifts with filters.
|
||||
///
|
||||
/// This use case delegates to [ShiftsRepositoryInterface].
|
||||
class GetAvailableShiftsUseCase extends UseCase<GetAvailableShiftsArguments, List<Shift>> {
|
||||
final ShiftsRepositoryInterface repository;
|
||||
|
||||
GetAvailableShiftsUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<List<Shift>> call(GetAvailableShiftsArguments arguments) async {
|
||||
return repository.getAvailableShifts(arguments.query, arguments.type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.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>> {
|
||||
final ShiftsRepositoryInterface repository;
|
||||
|
||||
GetMyShiftsUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<List<Shift>> call() async {
|
||||
return repository.getMyShifts();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../repositories/shifts_repository_interface.dart';
|
||||
|
||||
/// Use case for retrieving pending shift assignments.
|
||||
///
|
||||
/// This use case delegates to [ShiftsRepositoryInterface].
|
||||
class GetPendingAssignmentsUseCase extends NoInputUseCase<List<Shift>> {
|
||||
final ShiftsRepositoryInterface repository;
|
||||
|
||||
GetPendingAssignmentsUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<List<Shift>> call() async {
|
||||
return repository.getPendingAssignments();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../../domain/usecases/get_available_shifts_usecase.dart';
|
||||
import '../../../domain/arguments/get_available_shifts_arguments.dart';
|
||||
import '../../../domain/usecases/get_my_shifts_usecase.dart';
|
||||
import '../../../domain/usecases/get_pending_assignments_usecase.dart';
|
||||
|
||||
part 'shifts_event.dart';
|
||||
part 'shifts_state.dart';
|
||||
|
||||
class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
final GetMyShiftsUseCase getMyShifts;
|
||||
final GetAvailableShiftsUseCase getAvailableShifts;
|
||||
final GetPendingAssignmentsUseCase getPendingAssignments;
|
||||
|
||||
ShiftsBloc({
|
||||
required this.getMyShifts,
|
||||
required this.getAvailableShifts,
|
||||
required this.getPendingAssignments,
|
||||
}) : super(ShiftsInitial()) {
|
||||
on<LoadShiftsEvent>(_onLoadShifts);
|
||||
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
|
||||
}
|
||||
|
||||
Future<void> _onLoadShifts(
|
||||
LoadShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
if (state is! ShiftsLoaded) {
|
||||
emit(ShiftsLoading());
|
||||
}
|
||||
|
||||
// Determine what to load based on current tab?
|
||||
// Or load all for simplicity as per prototype logic which had them all in memory.
|
||||
|
||||
try {
|
||||
final myShiftsResult = await getMyShifts();
|
||||
final pendingResult = await getPendingAssignments();
|
||||
|
||||
// Initial available with defaults
|
||||
final availableResult = await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
|
||||
emit(ShiftsLoaded(
|
||||
myShifts: myShiftsResult,
|
||||
pendingShifts: pendingResult,
|
||||
availableShifts: availableResult,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
} catch (_) {
|
||||
emit(const ShiftsError('Failed to load shifts'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFilterAvailableShifts(
|
||||
FilterAvailableShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is ShiftsLoaded) {
|
||||
// Optimistic update or loading indicator?
|
||||
// Since it's filtering, we can just reload available.
|
||||
|
||||
try {
|
||||
final result = await getAvailableShifts(GetAvailableShiftsArguments(
|
||||
query: event.query ?? currentState.searchQuery,
|
||||
type: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
|
||||
emit(currentState.copyWith(
|
||||
availableShifts: result,
|
||||
searchQuery: event.query ?? currentState.searchQuery,
|
||||
jobType: event.jobType ?? currentState.jobType,
|
||||
));
|
||||
} catch (_) {
|
||||
// Error handling if filter fails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
part of 'shifts_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class ShiftsEvent extends Equatable {
|
||||
const ShiftsEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadShiftsEvent extends ShiftsEvent {}
|
||||
|
||||
class FilterAvailableShiftsEvent extends ShiftsEvent {
|
||||
final String? query;
|
||||
final String? jobType;
|
||||
|
||||
const FilterAvailableShiftsEvent({this.query, this.jobType});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [query, jobType];
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
part of 'shifts_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class ShiftsState extends Equatable {
|
||||
const ShiftsState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class ShiftsInitial extends ShiftsState {}
|
||||
|
||||
class ShiftsLoading extends ShiftsState {}
|
||||
|
||||
class ShiftsLoaded extends ShiftsState {
|
||||
final List<Shift> myShifts;
|
||||
final List<Shift> pendingShifts;
|
||||
final List<Shift> availableShifts;
|
||||
final String searchQuery;
|
||||
final String jobType;
|
||||
|
||||
const ShiftsLoaded({
|
||||
required this.myShifts,
|
||||
required this.pendingShifts,
|
||||
required this.availableShifts,
|
||||
required this.searchQuery,
|
||||
required this.jobType,
|
||||
});
|
||||
|
||||
ShiftsLoaded copyWith({
|
||||
List<Shift>? myShifts,
|
||||
List<Shift>? pendingShifts,
|
||||
List<Shift>? availableShifts,
|
||||
String? searchQuery,
|
||||
String? jobType,
|
||||
}) {
|
||||
return ShiftsLoaded(
|
||||
myShifts: myShifts ?? this.myShifts,
|
||||
pendingShifts: pendingShifts ?? this.pendingShifts,
|
||||
availableShifts: availableShifts ?? this.availableShifts,
|
||||
searchQuery: searchQuery ?? this.searchQuery,
|
||||
jobType: jobType ?? this.jobType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [myShifts, pendingShifts, availableShifts, searchQuery, jobType];
|
||||
}
|
||||
|
||||
class ShiftsError extends ShiftsState {
|
||||
final String message;
|
||||
|
||||
const ShiftsError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
extension ShiftsNavigator on IModularNavigator {
|
||||
void pushShiftDetails(Shift shift) {
|
||||
pushNamed('/shifts/details/${shift.id}', arguments: shift);
|
||||
}
|
||||
|
||||
// Example for going back or internal navigation if needed
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
// Shim to match POC styles locally
|
||||
class AppColors {
|
||||
static const Color krowBlue = UiColors.primary;
|
||||
static const Color krowYellow = Color(0xFFFFED4A);
|
||||
static const Color krowCharcoal = UiColors.textPrimary; // 121826
|
||||
static const Color krowMuted = UiColors.textSecondary; // 6A7382
|
||||
static const Color krowBorder = UiColors.border; // E3E6E9
|
||||
static const Color krowBackground = UiColors.background; // FAFBFC
|
||||
static const Color white = Colors.white;
|
||||
}
|
||||
|
||||
class ShiftDetailsPage extends StatefulWidget {
|
||||
final String shiftId;
|
||||
final Shift? shift;
|
||||
|
||||
const ShiftDetailsPage({super.key, required this.shiftId, this.shift});
|
||||
|
||||
@override
|
||||
State<ShiftDetailsPage> createState() => _ShiftDetailsPageState();
|
||||
}
|
||||
|
||||
class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
||||
late Shift _shift;
|
||||
bool _isLoading = true;
|
||||
bool _showDetails = true;
|
||||
bool _isApplying = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadShift();
|
||||
}
|
||||
|
||||
void _loadShift() async {
|
||||
if (widget.shift != null) {
|
||||
_shift = widget.shift!;
|
||||
setState(() => _isLoading = false);
|
||||
} else {
|
||||
// Simulate fetch or logic to handle missing data
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
if (mounted) {
|
||||
// Mock data from POC if needed, but assuming shift is always passed in this context
|
||||
// based on ShiftsPage navigation.
|
||||
// If generic fetch needed, we would use a Repo/Bloc here.
|
||||
// For now, stop loading.
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double _calculateHours(String start, String end) {
|
||||
try {
|
||||
final startParts = start.split(':').map(int.parse).toList();
|
||||
final endParts = end.split(':').map(int.parse).toList();
|
||||
double h =
|
||||
(endParts[0] - startParts[0]) + (endParts[1] - startParts[1]) / 60;
|
||||
if (h < 0) h += 24;
|
||||
return h;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTag(IconData icon, String label, Color bg, Color activeColor) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 14, color: activeColor),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: activeColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return const Scaffold(
|
||||
backgroundColor: AppColors.krowBackground,
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
final hours = _calculateHours(_shift.startTime, _shift.endTime);
|
||||
final totalPay = _shift.hourlyRate * hours;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.krowBackground,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, color: AppColors.krowMuted),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: AppColors.krowBorder, height: 1.0),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 120),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Pending Badge (Mock logic)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.krowYellow.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Text(
|
||||
'Pending 6h ago',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Header
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.krowBorder),
|
||||
),
|
||||
child: _shift.logoUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.network(
|
||||
_shift.logoUrl!,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
_shift.clientName.isNotEmpty ? _shift.clientName[0] : 'K',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.krowBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_shift.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'\$${_shift.hourlyRate.toStringAsFixed(0)}/h',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'(exp.total \$${totalPay.toStringAsFixed(0)})',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.krowMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
_shift.clientName,
|
||||
style: const TextStyle(color: AppColors.krowMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Tags
|
||||
Row(
|
||||
children: [
|
||||
_buildTag(
|
||||
UiIcons.zap,
|
||||
'Immediate start',
|
||||
AppColors.krowBlue.withOpacity(0.1),
|
||||
AppColors.krowBlue,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildTag(
|
||||
UiIcons.star,
|
||||
'No experience',
|
||||
AppColors.krowYellow.withOpacity(0.3),
|
||||
AppColors.krowCharcoal,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Additional Details
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.krowBorder),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () =>
|
||||
setState(() => _showDetails = !_showDetails),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'ADDITIONAL DETAILS',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.5,
|
||||
color: AppColors.krowMuted,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
_showDetails
|
||||
? UiIcons.chevronUp
|
||||
: UiIcons.chevronDown,
|
||||
color: AppColors.krowMuted,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_showDetails && _shift.description != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Divider(height: 1, color: AppColors.krowBorder),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_shift.description!,
|
||||
style: const TextStyle(
|
||||
color: AppColors.krowCharcoal,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Action Button
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(top: BorderSide(color: AppColors.krowBorder)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _isApplying ? null : () {
|
||||
setState(() {
|
||||
_isApplying = true;
|
||||
});
|
||||
// Simulate Apply
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() => _isApplying = false);
|
||||
Modular.to.pop();
|
||||
}
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.krowBlue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: _isApplying
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)
|
||||
)
|
||||
: const Text(
|
||||
'Apply Now',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../blocs/shifts/shifts_bloc.dart';
|
||||
import '../widgets/my_shift_card.dart';
|
||||
import '../widgets/shift_assignment_card.dart';
|
||||
|
||||
// Shim to match POC styles locally
|
||||
class AppColors {
|
||||
static const Color krowBlue = UiColors.primary;
|
||||
static const Color krowYellow = Color(0xFFFFED4A);
|
||||
static const Color krowCharcoal = UiColors.textPrimary;
|
||||
static const Color krowMuted = UiColors.textSecondary;
|
||||
static const Color krowBorder = UiColors.border;
|
||||
static const Color krowBackground = UiColors.background;
|
||||
static const Color white = Colors.white;
|
||||
static const Color black = Colors.black;
|
||||
}
|
||||
|
||||
class ShiftsPage extends StatefulWidget {
|
||||
final String? initialTab;
|
||||
const ShiftsPage({super.key, this.initialTab});
|
||||
|
||||
@override
|
||||
State<ShiftsPage> createState() => _ShiftsPageState();
|
||||
}
|
||||
|
||||
class _ShiftsPageState extends State<ShiftsPage> {
|
||||
late String _activeTab;
|
||||
String _searchQuery = '';
|
||||
// ignore: unused_field
|
||||
String? _cancelledShiftDemo; // 'lastMinute' or 'advance'
|
||||
String _jobType = 'all'; // all, one-day, multi-day, long-term
|
||||
|
||||
// Calendar State
|
||||
DateTime _selectedDate = DateTime.now();
|
||||
int _weekOffset = 0;
|
||||
|
||||
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_activeTab = widget.initialTab ?? 'myshifts';
|
||||
_bloc.add(LoadShiftsEvent());
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ShiftsPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.initialTab != null && widget.initialTab != _activeTab) {
|
||||
setState(() {
|
||||
_activeTab = widget.initialTab!;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List<DateTime> _getCalendarDays() {
|
||||
final now = DateTime.now();
|
||||
int reactDayIndex = now.weekday == 7 ? 0 : now.weekday;
|
||||
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)));
|
||||
}
|
||||
|
||||
bool _isSameDay(DateTime a, DateTime b) {
|
||||
return a.year == b.year && a.month == b.month && a.day == b.day;
|
||||
}
|
||||
|
||||
void _confirmShift(String id) {
|
||||
// TODO: Implement Bloc event
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Shift confirmed! (Placeholder)')),
|
||||
);
|
||||
}
|
||||
|
||||
void _declineShift(String id) {
|
||||
// TODO: Implement Bloc event
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Shift declined. (Placeholder)')),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _bloc,
|
||||
child: BlocBuilder<ShiftsBloc, ShiftsState>(
|
||||
builder: (context, state) {
|
||||
final List<Shift> myShifts = (state is ShiftsLoaded) ? state.myShifts : [];
|
||||
final List<Shift> availableJobs = (state is ShiftsLoaded) ? state.availableShifts : [];
|
||||
final List<Shift> pendingAssignments = (state is ShiftsLoaded) ? state.pendingShifts : [];
|
||||
final List<Shift> historyShifts = []; // Not in state yet, placeholder
|
||||
|
||||
// Filter logic from POC
|
||||
final filteredJobs = availableJobs.where((s) {
|
||||
final matchesSearch =
|
||||
s.title.toLowerCase().contains(_searchQuery.toLowerCase()) ||
|
||||
s.location.toLowerCase().contains(_searchQuery.toLowerCase()) ||
|
||||
s.clientName.toLowerCase().contains(_searchQuery.toLowerCase());
|
||||
|
||||
if (!matchesSearch) return false;
|
||||
|
||||
if (_jobType == 'all') return true;
|
||||
if (_jobType == 'one-day') {
|
||||
return !s.title.contains('Long Term') && !s.title.contains('Multi-Day');
|
||||
}
|
||||
if (_jobType == 'multi-day') return s.title.contains('Multi-Day');
|
||||
if (_jobType == 'long-term') return s.title.contains('Long Term');
|
||||
return true;
|
||||
}).toList();
|
||||
|
||||
final calendarDays = _getCalendarDays();
|
||||
final weekStartDate = calendarDays.first;
|
||||
final weekEndDate = calendarDays.last;
|
||||
|
||||
final visibleMyShifts = myShifts.where((s) {
|
||||
// Primitive check if shift date string compare
|
||||
// In real app use DateTime logic
|
||||
final sDateStr = s.date;
|
||||
final wStartStr = DateFormat('yyyy-MM-dd').format(weekStartDate);
|
||||
final wEndStr = DateFormat('yyyy-MM-dd').format(weekEndDate);
|
||||
return sDateStr.compareTo(wStartStr) >= 0 &&
|
||||
sDateStr.compareTo(wEndStr) <= 0;
|
||||
}).toList();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.krowBackground,
|
||||
body: Column(
|
||||
children: [
|
||||
// Header (Blue)
|
||||
Container(
|
||||
color: AppColors.krowBlue,
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
20,
|
||||
MediaQuery.of(context).padding.top + 20,
|
||||
20,
|
||||
24,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"Shifts",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_buildDemoButton("Demo: Cancel <4hr", const Color(0xFFEF4444), () {
|
||||
setState(() => _cancelledShiftDemo = 'lastMinute');
|
||||
_showCancelledModal('lastMinute');
|
||||
}),
|
||||
const SizedBox(width: 8),
|
||||
_buildDemoButton("Demo: Cancel >4hr", const Color(0xFFF59E0B), () {
|
||||
setState(() => _cancelledShiftDemo = 'advance');
|
||||
_showCancelledModal('advance');
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Tabs
|
||||
Row(
|
||||
children: [
|
||||
_buildTab("myshifts", "My Shifts", LucideIcons.calendar, myShifts.length),
|
||||
const SizedBox(width: 8),
|
||||
_buildTab("find", "Find Shifts", LucideIcons.search, filteredJobs.length),
|
||||
const SizedBox(width: 8),
|
||||
_buildTab("history", "History", LucideIcons.clock, historyShifts.length),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar Selector
|
||||
if (_activeTab == 'myshifts')
|
||||
Container(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => setState(() => _weekOffset--),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(LucideIcons.chevronLeft, size: 20, color: AppColors.krowCharcoal),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
DateFormat('MMMM yyyy').format(weekStartDate),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => setState(() => _weekOffset++),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(LucideIcons.chevronRight, size: 20, color: AppColors.krowCharcoal),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Days Grid
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: calendarDays.map((date) {
|
||||
final isSelected = _isSameDay(date, _selectedDate);
|
||||
final dateStr = DateFormat('yyyy-MM-dd').format(date);
|
||||
final hasShifts = myShifts.any((s) => s.date == dateStr);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _selectedDate = date),
|
||||
child: Container(
|
||||
width: 44,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.krowBlue : Colors.white,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.krowBlue : AppColors.krowBorder,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
date.day.toString().padLeft(2, '0'),
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSelected ? Colors.white : AppColors.krowCharcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
DateFormat('E').format(date),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isSelected ? Colors.white.withOpacity(0.8) : AppColors.krowMuted,
|
||||
),
|
||||
),
|
||||
if (hasShifts)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? Colors.white : AppColors.krowBlue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
if (_activeTab == 'myshifts')
|
||||
const Divider(height: 1, color: AppColors.krowBorder),
|
||||
|
||||
// Body Content
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
if (_activeTab == 'find') ...[
|
||||
// Search & Filter
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Container(
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.krowBorder),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
onChanged: (val) => setState(() => _searchQuery = val), // Local filter for now
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(LucideIcons.search, size: 20, color: AppColors.krowMuted),
|
||||
border: InputBorder.none,
|
||||
hintText: "Search jobs...",
|
||||
hintStyle: TextStyle(color: AppColors.krowMuted, fontSize: 14),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F3F5),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildFilterTab('all', 'All Jobs'),
|
||||
_buildFilterTab('one-day', 'One Day'),
|
||||
_buildFilterTab('multi-day', 'Multi-Day'),
|
||||
_buildFilterTab('long-term', 'Long Term'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
if (_activeTab == 'myshifts') ...[
|
||||
if (pendingAssignments.isNotEmpty) ...[
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(width: 8, height: 8, decoration: const BoxDecoration(color: Color(0xFFF59E0B), shape: BoxShape.circle)),
|
||||
const SizedBox(width: 8),
|
||||
const Text("Awaiting Confirmation", style: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFFD97706)
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
...pendingAssignments.map((shift) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: ShiftAssignmentCard(
|
||||
shift: shift,
|
||||
onConfirm: () => _confirmShift(shift.id),
|
||||
onDecline: () => _declineShift(shift.id),
|
||||
),
|
||||
)),
|
||||
],
|
||||
|
||||
// Cancelled Shifts Demo (Visual only as per POC)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: const Text("Cancelled Shifts", style: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.krowMuted
|
||||
)),
|
||||
),
|
||||
),
|
||||
_buildCancelledCard(
|
||||
title: "Annual Tech Conference", client: "TechCorp Inc.", pay: "\$200", rate: "\$25/hr · 8h",
|
||||
date: "Today", time: "10:00 AM - 6:00 PM", address: "123 Convention Center Dr", isLastMinute: true,
|
||||
onTap: () => setState(() => _cancelledShiftDemo = 'lastMinute')
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildCancelledCard(
|
||||
title: "Morning Catering Setup", client: "EventPro Services", pay: "\$120", rate: "\$20/hr · 6h",
|
||||
date: "Tomorrow", time: "8:00 AM - 2:00 PM", address: "456 Grand Ballroom Ave", isLastMinute: false,
|
||||
onTap: () => setState(() => _cancelledShiftDemo = 'advance')
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Confirmed Shifts
|
||||
if (visibleMyShifts.isNotEmpty) ...[
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: const Text("Confirmed Shifts", style: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.krowMuted
|
||||
)),
|
||||
),
|
||||
),
|
||||
...visibleMyShifts.map((shift) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: MyShiftCard(shift: shift),
|
||||
)),
|
||||
],
|
||||
],
|
||||
|
||||
if (_activeTab == 'find') ...[
|
||||
if (filteredJobs.isEmpty)
|
||||
_buildEmptyState(LucideIcons.search, "No jobs available", "Check back later", null, null)
|
||||
else
|
||||
...filteredJobs.map((shift) => GestureDetector(
|
||||
onTap: () => Modular.to.pushNamed('details/${shift.id}', arguments: shift),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: MyShiftCard(shift: shift),
|
||||
),
|
||||
)),
|
||||
],
|
||||
|
||||
if (_activeTab == 'history')
|
||||
_buildEmptyState(LucideIcons.clock, "No shift history", "Completed shifts appear here", null, null),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterTab(String id, String label) {
|
||||
final isSelected = _jobType == id;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _jobType = id),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.krowBlue : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
boxShadow: isSelected ? [BoxShadow(color: AppColors.krowBlue.withOpacity(0.2), blurRadius: 4, offset: const Offset(0, 2))] : null,
|
||||
),
|
||||
child: Text(label, textAlign: TextAlign.center, style: TextStyle(
|
||||
fontSize: 11, fontWeight: FontWeight.w600, color: isSelected ? Colors.white : AppColors.krowMuted
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(String id, String label, IconData icon, int count) {
|
||||
final isActive = _activeTab == id;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _activeTab = id),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? Colors.white : Colors.white.withAlpha((0.2 * 255).round()),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 14, color: isActive ? AppColors.krowBlue : Colors.white),
|
||||
const SizedBox(width: 6),
|
||||
Flexible(child: Text(label, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: isActive ? AppColors.krowBlue : Colors.white), overflow: TextOverflow.ellipsis)),
|
||||
const SizedBox(width: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
constraints: const BoxConstraints(minWidth: 18),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? AppColors.krowBlue.withAlpha((0.1 * 255).round()) : Colors.white.withAlpha((0.2 * 255).round()),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Center(child: Text("$count", style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: isActive ? AppColors.krowBlue : Colors.white))),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDemoButton(String label, Color color, VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(4)),
|
||||
child: Text(label, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Colors.white)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(IconData icon, String title, String subtitle, String? actionLabel, VoidCallback? onAction) {
|
||||
return Center(child: Padding(padding: const EdgeInsets.symmetric(vertical: 64), child: Column(children: [
|
||||
Container(width: 64, height: 64, decoration: BoxDecoration(color: const Color(0xFFF1F3F5), borderRadius: BorderRadius.circular(12)), child: Icon(icon, size: 32, color: AppColors.krowMuted)),
|
||||
const SizedBox(height: 16),
|
||||
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.krowCharcoal)),
|
||||
const SizedBox(height: 4),
|
||||
Text(subtitle, style: const TextStyle(fontSize: 14, color: AppColors.krowMuted)),
|
||||
if (actionLabel != null && onAction != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(onPressed: onAction, style: ElevatedButton.styleFrom(backgroundColor: AppColors.krowBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), child: Text(actionLabel)),
|
||||
]
|
||||
])));
|
||||
}
|
||||
|
||||
Widget _buildCancelledCard({required String title, required String client, required String pay, required String rate, required String date, required String time, required String address, required bool isLastMinute, required VoidCallback onTap}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.krowBorder)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(children: [Container(width: 6, height: 6, decoration: const BoxDecoration(color: Color(0xFFEF4444), shape: BoxShape.circle)), const SizedBox(width: 6), const Text("CANCELLED", style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Color(0xFFEF4444))), if (isLastMinute) ...[const SizedBox(width: 4), const Text("• 4hr compensation", style: TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xFF10B981)))]]),
|
||||
const SizedBox(height: 12),
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Container(width: 44, height: 44, decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.krowBlue.withAlpha((0.15 * 255).round()), AppColors.krowBlue.withAlpha((0.08 * 255).round())]), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.krowBlue.withAlpha((0.15 * 255).round()))), child: const Center(child: Icon(LucideIcons.briefcase, color: AppColors.krowBlue, size: 20))),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.krowCharcoal)), Text(client, style: const TextStyle(fontSize: 12, color: AppColors.krowMuted))])), Column(crossAxisAlignment: CrossAxisAlignment.end, children: [Text(pay, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.krowCharcoal)), Text(rate, style: const TextStyle(fontSize: 10, color: AppColors.krowMuted))])]),
|
||||
const SizedBox(height: 8),
|
||||
Row(children: [const Icon(LucideIcons.calendar, size: 12, color: AppColors.krowMuted), const SizedBox(width: 4), Text(date, style: const TextStyle(fontSize: 12, color: AppColors.krowMuted)), const SizedBox(width: 12), const Icon(LucideIcons.clock, size: 12, color: AppColors.krowMuted), const SizedBox(width: 4), Text(time, style: const TextStyle(fontSize: 12, color: AppColors.krowMuted))]),
|
||||
const SizedBox(height: 4),
|
||||
Row(children: [const Icon(LucideIcons.mapPin, size: 12, color: AppColors.krowMuted), const SizedBox(width: 4), Expanded(child: Text(address, style: const TextStyle(fontSize: 12, color: AppColors.krowMuted), overflow: TextOverflow.ellipsis))]),
|
||||
])),
|
||||
]),
|
||||
])),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCancelledModal(String type) {
|
||||
final isLastMinute = type == 'lastMinute';
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(LucideIcons.xCircle, color: Color(0xFFEF4444)),
|
||||
const SizedBox(width: 8),
|
||||
const Text("Shift Cancelled"),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"We're sorry, but the following shift has been cancelled by the client:",
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Annual Tech Conference", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text("Today, 10:00 AM - 6:00 PM"),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (isLastMinute)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFECFDF5),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFF10B981)),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(LucideIcons.checkCircle, color: Color(0xFF10B981), size: 16),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"You are eligible for 4hr cancellation compensation.",
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Color(0xFF065F46), fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
const Text(
|
||||
"Reduced schedule at the venue. No compensation is due as this was cancelled more than 4 hours in advance.",
|
||||
style: TextStyle(fontSize: 12, color: AppColors.krowMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text("Close"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
class MyShiftCard extends StatefulWidget {
|
||||
final Shift shift;
|
||||
final bool historyMode;
|
||||
final VoidCallback? onAccept;
|
||||
final VoidCallback? onDecline;
|
||||
final VoidCallback? onRequestSwap;
|
||||
final int index;
|
||||
|
||||
const MyShiftCard({
|
||||
super.key,
|
||||
required this.shift,
|
||||
this.historyMode = false,
|
||||
this.onAccept,
|
||||
this.onDecline,
|
||||
this.onRequestSwap,
|
||||
this.index = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MyShiftCard> createState() => _MyShiftCardState();
|
||||
}
|
||||
|
||||
class _MyShiftCardState extends State<MyShiftCard> {
|
||||
bool _isExpanded = false;
|
||||
|
||||
String _formatTime(String time) {
|
||||
if (time.isEmpty) return '';
|
||||
try {
|
||||
final parts = time.split(':');
|
||||
final hour = int.parse(parts[0]);
|
||||
final minute = int.parse(parts[1]);
|
||||
final dt = DateTime(2022, 1, 1, hour, minute);
|
||||
return DateFormat('h:mm a').format(dt);
|
||||
} catch (e) {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDate(String dateStr) {
|
||||
if (dateStr.isEmpty) return '';
|
||||
try {
|
||||
final date = DateTime.parse(dateStr);
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final tomorrow = today.add(const Duration(days: 1));
|
||||
final d = DateTime(date.year, date.month, date.day);
|
||||
|
||||
if (d == today) return 'Today';
|
||||
if (d == tomorrow) return 'Tomorrow';
|
||||
return DateFormat('EEE, MMM d').format(date);
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
double _calculateDuration() {
|
||||
if (widget.shift.startTime.isEmpty || widget.shift.endTime.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
final s = widget.shift.startTime.split(':').map(int.parse).toList();
|
||||
final e = widget.shift.endTime.split(':').map(int.parse).toList();
|
||||
double hours = ((e[0] * 60 + e[1]) - (s[0] * 60 + s[1])) / 60;
|
||||
if (hours < 0) hours += 24;
|
||||
return hours.roundToDouble();
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
String _getShiftType() {
|
||||
// Check title for type indicators (for mock data)
|
||||
if (widget.shift.title.contains('Long Term')) return t.staff_shifts.filter.long_term;
|
||||
if (widget.shift.title.contains('Multi-Day')) return t.staff_shifts.filter.multi_day;
|
||||
return t.staff_shifts.filter.one_day;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// ignore: unused_local_variable
|
||||
final duration = _calculateDuration();
|
||||
|
||||
// Status Logic
|
||||
String? status = widget.shift.status;
|
||||
Color statusColor = UiColors.primary;
|
||||
Color statusBg = UiColors.primary;
|
||||
String statusText = '';
|
||||
IconData? statusIcon;
|
||||
|
||||
if (status == 'confirmed') {
|
||||
statusText = t.staff_shifts.status.confirmed;
|
||||
statusColor = UiColors.textLink;
|
||||
statusBg = UiColors.primary;
|
||||
} else if (status == 'pending' || status == 'open') {
|
||||
statusText = t.staff_shifts.status.act_now;
|
||||
statusColor = UiColors.destructive;
|
||||
statusBg = UiColors.destructive;
|
||||
} else if (status == 'swap') {
|
||||
statusText = t.staff_shifts.status.swap_requested;
|
||||
statusColor = UiColors.textWarning;
|
||||
statusBg = UiColors.textWarning;
|
||||
statusIcon = UiIcons.swap;
|
||||
} else if (status == 'completed') {
|
||||
statusText = t.staff_shifts.status.completed;
|
||||
statusColor = UiColors.textSuccess;
|
||||
statusBg = UiColors.iconSuccess;
|
||||
} else if (status == 'no_show') {
|
||||
statusText = t.staff_shifts.status.no_show;
|
||||
statusColor = UiColors.destructive;
|
||||
statusBg = UiColors.destructive;
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _isExpanded = !_isExpanded),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Collapsed Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Status Badge
|
||||
if (statusText.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
if (statusIcon != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Icon(
|
||||
statusIcon,
|
||||
size: 12,
|
||||
color: statusColor,
|
||||
),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
margin: const EdgeInsets.only(right: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: statusBg,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
statusText,
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: statusColor,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
// Shift Type Badge for available/pending shifts
|
||||
if (status == 'open' || status == 'pending') ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
_getShiftType(),
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Main Content
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Date/Time Column
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
_formatDate(widget.shift.date),
|
||||
style: UiTypography.display2m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (widget.shift.durationDays != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
t.staff_shifts.details.days(days: widget.shift.durationDays!),
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${_formatTime(widget.shift.startTime)} - ${_formatTime(widget.shift.endTime)}',
|
||||
style: UiTypography.body2r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
widget.shift.title,
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
UiIcons.mapPin,
|
||||
size: 12,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
widget.shift.clientName,
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Logo Box
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: widget.shift.logoUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.network(
|
||||
widget.shift.logoUrl!,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
widget.shift.clientName.isNotEmpty
|
||||
? widget.shift.clientName[0]
|
||||
: 'K',
|
||||
style: UiTypography.title1m.textLink,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Expanded Actions
|
||||
AnimatedCrossFade(
|
||||
firstChild: const SizedBox(height: 0),
|
||||
secondChild: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(color: UiColors.border),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Warning for Pending
|
||||
if (status == 'pending' || status == 'open')
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
color: UiColors.accent.withOpacity(0.1),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
UiIcons.warning,
|
||||
size: 14,
|
||||
color: UiColors.textWarning,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
t.staff_shifts.status.pending_warning,
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.textWarning,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (status == 'pending' || status == 'open') ...[
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: widget.onDecline,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
side: const BorderSide(color: UiColors.border),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Text(t.staff_shifts.action.decline),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: widget.onAccept,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UiColors.primary,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Text(t.staff_shifts.action.confirm),
|
||||
),
|
||||
),
|
||||
] else if (status == 'confirmed') ...[
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: widget.onRequestSwap,
|
||||
icon: const Icon(UiIcons.swap, size: 16),
|
||||
label: Text(t.staff_shifts.action.request_swap),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: UiColors.textPrimary,
|
||||
side: const BorderSide(color: UiColors.border),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
crossFadeState: _isExpanded
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
class ShiftAssignmentCard extends StatelessWidget {
|
||||
final Shift shift;
|
||||
final VoidCallback onConfirm;
|
||||
final VoidCallback onDecline;
|
||||
final bool isConfirming;
|
||||
|
||||
const ShiftAssignmentCard({
|
||||
super.key,
|
||||
required this.shift,
|
||||
required this.onConfirm,
|
||||
required this.onDecline,
|
||||
this.isConfirming = false,
|
||||
});
|
||||
|
||||
String _formatTime(String time) {
|
||||
if (time.isEmpty) return '';
|
||||
try {
|
||||
final parts = time.split(':');
|
||||
final hour = int.parse(parts[0]);
|
||||
final minute = int.parse(parts[1]);
|
||||
final dt = DateTime(2022, 1, 1, hour, minute);
|
||||
return DateFormat('h:mm a').format(dt);
|
||||
} catch (e) {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDate(String dateStr) {
|
||||
if (dateStr.isEmpty) return '';
|
||||
try {
|
||||
final date = DateTime.parse(dateStr);
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final tomorrow = today.add(const Duration(days: 1));
|
||||
final d = DateTime(date.year, date.month, date.day);
|
||||
|
||||
if (d == today) return 'Today';
|
||||
if (d == tomorrow) return 'Tomorrow';
|
||||
return DateFormat('EEE, MMM d').format(date);
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
double _calculateHours(String start, String end) {
|
||||
if (start.isEmpty || end.isEmpty) return 0;
|
||||
try {
|
||||
final s = start.split(':').map(int.parse).toList();
|
||||
final e = end.split(':').map(int.parse).toList();
|
||||
return ((e[0] * 60 + e[1]) - (s[0] * 60 + s[1])) / 60;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hours = _calculateHours(shift.startTime, shift.endTime);
|
||||
final totalPay = shift.hourlyRate * hours;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.secondary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
shift.clientName.isNotEmpty
|
||||
? shift.clientName[0]
|
||||
: 'K',
|
||||
style: UiTypography.body2b.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
shift.title,
|
||||
style: UiTypography.body2b.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
shift.clientName,
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"\$${totalPay.toStringAsFixed(0)}",
|
||||
style: UiTypography.display2m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"\$${shift.hourlyRate}/hr · ${hours}h",
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
UiIcons.calendar,
|
||||
size: 14,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
_formatDate(shift.date),
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const Icon(
|
||||
UiIcons.clock,
|
||||
size: 14,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
"${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}",
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
UiIcons.mapPin,
|
||||
size: 14,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
shift.location,
|
||||
style: UiTypography.display3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
if (isConfirming) ...[
|
||||
const Divider(height: 1),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: onDecline,
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: Text(t.staff_shifts.action.decline),
|
||||
),
|
||||
),
|
||||
Container(width: 1, height: 48, color: UiColors.border),
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: onConfirm,
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.primary,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: Text(t.staff_shifts.action.confirm),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'domain/repositories/shifts_repository_interface.dart';
|
||||
import 'data/repositories_impl/shifts_repository_impl.dart';
|
||||
import 'domain/usecases/get_my_shifts_usecase.dart';
|
||||
import 'domain/usecases/get_available_shifts_usecase.dart';
|
||||
import 'domain/usecases/get_pending_assignments_usecase.dart';
|
||||
import 'presentation/blocs/shifts/shifts_bloc.dart';
|
||||
import 'presentation/pages/shifts_page.dart';
|
||||
import 'presentation/pages/shift_details_page.dart';
|
||||
|
||||
class StaffShiftsModule extends Module {
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repository
|
||||
i.add<ShiftsRepositoryInterface>(ShiftsRepositoryImpl.new);
|
||||
|
||||
// UseCases
|
||||
i.add(GetMyShiftsUseCase.new);
|
||||
i.add(GetAvailableShiftsUseCase.new);
|
||||
i.add(GetPendingAssignmentsUseCase.new);
|
||||
|
||||
// Bloc
|
||||
i.add(ShiftsBloc.new);
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(RouteManager r) {
|
||||
r.child('/', child: (_) => const ShiftsPage());
|
||||
r.child('/details/:id', child: (_) => ShiftDetailsPage(shiftId: r.args.params['id'], shift: r.args.data));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
library staff_shifts;
|
||||
|
||||
export 'src/staff_shifts_module.dart';
|
||||
|
||||
650
apps/mobile/packages/features/staff/shifts/pubspec.lock
Normal file
650
apps/mobile/packages/features/staff/shifts/pubspec.lock
Normal file
@@ -0,0 +1,650 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
auto_injector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: auto_injector
|
||||
sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
bloc:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bloc
|
||||
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.4"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
core_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../core_localization"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
csv:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csv
|
||||
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
design_system:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../design_system"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_bloc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_bloc
|
||||
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.6"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_modular:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_modular
|
||||
sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
font_awesome_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: font_awesome_flutter
|
||||
sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.12.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
google_fonts:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: "6996212014b996eaa17074e02b1b925b212f5e053832d9048970dc27255a8fb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
krow_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../core"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
krow_data_connect:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../data_connect"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
krow_domain:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../domain"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
lucide_icons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lucide_icons
|
||||
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.257.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
modular_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: modular_core
|
||||
sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_c
|
||||
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.4"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "7fd0c4d8ac8980011753b9bdaed2bf15111365924cdeeeaeb596214ea2b03537"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.4"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5+1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
result_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: result_dart
|
||||
sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.18"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
slang:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: slang
|
||||
sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
slang_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: slang_flutter
|
||||
sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.10.7 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
33
apps/mobile/packages/features/staff/shifts/pubspec.yaml
Normal file
33
apps/mobile/packages/features/staff/shifts/pubspec.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
name: staff_shifts
|
||||
description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
flutter: ">=3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_modular: ^6.3.2
|
||||
flutter_bloc: ^8.1.3
|
||||
equatable: ^2.0.5
|
||||
intl: ^0.20.2
|
||||
|
||||
# Internal packages
|
||||
krow_core:
|
||||
path: ../../../core
|
||||
design_system:
|
||||
path: ../../../design_system
|
||||
krow_domain:
|
||||
path: ../../../domain
|
||||
krow_data_connect:
|
||||
path: ../../../data_connect
|
||||
core_localization:
|
||||
path: ../../../core_localization
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
@@ -10,6 +10,7 @@ import 'package:staff_tax_forms/staff_tax_forms.dart';
|
||||
import 'package:staff_documents/staff_documents.dart';
|
||||
import 'package:staff_certificates/staff_certificates.dart';
|
||||
import 'package:staff_attire/staff_attire.dart';
|
||||
import 'package:staff_shifts/staff_shifts.dart';
|
||||
|
||||
import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart';
|
||||
import 'package:staff_main/src/presentation/constants/staff_main_routes.dart';
|
||||
@@ -28,10 +29,9 @@ class StaffMainModule extends Module {
|
||||
'/',
|
||||
child: (BuildContext context) => const StaffMainPage(),
|
||||
children: <ParallelRoute<dynamic>>[
|
||||
ChildRoute<dynamic>(
|
||||
ModuleRoute<dynamic>(
|
||||
StaffMainRoutes.shifts,
|
||||
child: (BuildContext context) =>
|
||||
const PlaceholderPage(title: 'Shifts'),
|
||||
module: StaffShiftsModule(),
|
||||
),
|
||||
ChildRoute<dynamic>(
|
||||
StaffMainRoutes.payments,
|
||||
|
||||
@@ -43,8 +43,8 @@ dependencies:
|
||||
path: ../profile_sections/compliance/certificates
|
||||
staff_attire:
|
||||
path: ../profile_sections/onboarding/attire
|
||||
# staff_shifts:
|
||||
# path: ../shifts
|
||||
staff_shifts:
|
||||
path: ../shifts
|
||||
# staff_payments:
|
||||
# path: ../payments
|
||||
|
||||
|
||||
@@ -1100,6 +1100,13 @@ packages:
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
staff_shifts:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "packages/features/staff/shifts"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
staff_tax_forms:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user