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",
|
"upload_required": "✓ Upload photos of required items",
|
||||||
"accept_attestation": "✓ Accept attestation"
|
"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",
|
"upload_required": "✓ Subir fotos de artículos requeridos",
|
||||||
"accept_attestation": "✓ Aceptar certificación"
|
"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;
|
library;
|
||||||
|
|
||||||
export 'src/mocks/auth_repository_mock.dart';
|
export 'src/mocks/auth_repository_mock.dart';
|
||||||
|
export 'src/mocks/shifts_repository_mock.dart';
|
||||||
export 'src/mocks/staff_repository_mock.dart';
|
export 'src/mocks/staff_repository_mock.dart';
|
||||||
export 'src/mocks/profile_repository_mock.dart';
|
export 'src/mocks/profile_repository_mock.dart';
|
||||||
export 'src/mocks/event_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.
|
// TODO: Implement EventRepositoryInterface once defined in a feature package.
|
||||||
class EventRepositoryMock {
|
class EventRepositoryMock {
|
||||||
Future<Assignment> applyForPosition(String positionId, String staffId) async {
|
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(
|
return Assignment(
|
||||||
id: 'assign_1',
|
id: 'assign_1',
|
||||||
positionId: positionId,
|
positionId: positionId,
|
||||||
@@ -13,12 +13,12 @@ class EventRepositoryMock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Event?> getEvent(String id) async {
|
Future<Event?> getEvent(String id) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||||
return _mockEvent;
|
return _mockEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<EventShift>> getEventShifts(String eventId) async {
|
Future<List<EventShift>> getEventShifts(String eventId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||||
return <EventShift>[
|
return <EventShift>[
|
||||||
const EventShift(
|
const EventShift(
|
||||||
id: 'shift_1',
|
id: 'shift_1',
|
||||||
@@ -30,7 +30,7 @@ class EventRepositoryMock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Assignment>> getStaffAssignments(String staffId) async {
|
Future<List<Assignment>> getStaffAssignments(String staffId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||||
return <Assignment>[
|
return <Assignment>[
|
||||||
const Assignment(
|
const Assignment(
|
||||||
id: 'assign_1',
|
id: 'assign_1',
|
||||||
@@ -42,7 +42,7 @@ class EventRepositoryMock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Event>> getUpcomingEvents() async {
|
Future<List<Event>> getUpcomingEvents() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 800));
|
await Future<void>.delayed(const Duration(milliseconds: 800));
|
||||||
return <Event>[_mockEvent];
|
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/business_setting.dart';
|
||||||
export 'src/entities/business/hub.dart';
|
export 'src/entities/business/hub.dart';
|
||||||
export 'src/entities/business/hub_department.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.dart';
|
||||||
export 'src/entities/events/event_shift.dart';
|
export 'src/entities/events/event_shift.dart';
|
||||||
export 'src/entities/events/event_shift_position.dart';
|
export 'src/entities/events/event_shift_position.dart';
|
||||||
export 'src/entities/events/assignment.dart';
|
export 'src/entities/events/assignment.dart';
|
||||||
export 'src/entities/events/work_session.dart';
|
export 'src/entities/events/work_session.dart';
|
||||||
|
|
||||||
|
// Shifts
|
||||||
|
export 'src/entities/shifts/shift.dart';
|
||||||
|
|
||||||
// Orders & Requests
|
// Orders & Requests
|
||||||
export 'src/entities/orders/order_type.dart';
|
export 'src/entities/orders/order_type.dart';
|
||||||
export 'src/entities/orders/one_time_order.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_documents/staff_documents.dart';
|
||||||
import 'package:staff_certificates/staff_certificates.dart';
|
import 'package:staff_certificates/staff_certificates.dart';
|
||||||
import 'package:staff_attire/staff_attire.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/blocs/staff_main_cubit.dart';
|
||||||
import 'package:staff_main/src/presentation/constants/staff_main_routes.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(),
|
child: (BuildContext context) => const StaffMainPage(),
|
||||||
children: <ParallelRoute<dynamic>>[
|
children: <ParallelRoute<dynamic>>[
|
||||||
ChildRoute<dynamic>(
|
ModuleRoute<dynamic>(
|
||||||
StaffMainRoutes.shifts,
|
StaffMainRoutes.shifts,
|
||||||
child: (BuildContext context) =>
|
module: StaffShiftsModule(),
|
||||||
const PlaceholderPage(title: 'Shifts'),
|
|
||||||
),
|
),
|
||||||
ChildRoute<dynamic>(
|
ChildRoute<dynamic>(
|
||||||
StaffMainRoutes.payments,
|
StaffMainRoutes.payments,
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ dependencies:
|
|||||||
path: ../profile_sections/compliance/certificates
|
path: ../profile_sections/compliance/certificates
|
||||||
staff_attire:
|
staff_attire:
|
||||||
path: ../profile_sections/onboarding/attire
|
path: ../profile_sections/onboarding/attire
|
||||||
# staff_shifts:
|
staff_shifts:
|
||||||
# path: ../shifts
|
path: ../shifts
|
||||||
# staff_payments:
|
# staff_payments:
|
||||||
# path: ../payments
|
# path: ../payments
|
||||||
|
|
||||||
|
|||||||
@@ -1100,6 +1100,13 @@ packages:
|
|||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
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:
|
staff_tax_forms:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user